{"id":18763759,"url":"https://github.com/dmytro-anokhin/renderable","last_synced_at":"2025-07-11T10:03:45.597Z","repository":{"id":134611077,"uuid":"105473163","full_name":"dmytro-anokhin/renderable","owner":"dmytro-anokhin","description":"Example of using protocols and protocol extensions in Swift to add functionality between different frameworks","archived":false,"fork":false,"pushed_at":"2017-10-01T20:50:17.000Z","size":11,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-20T19:24:23.461Z","etag":null,"topics":["coregraphics","coreimage","swift","uikit"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmytro-anokhin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-01T20:44:56.000Z","updated_at":"2022-05-22T10:04:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"fccf02cb-83c7-497e-9bf5-92d2ac863b59","html_url":"https://github.com/dmytro-anokhin/renderable","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dmytro-anokhin/renderable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Frenderable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Frenderable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Frenderable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Frenderable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmytro-anokhin","download_url":"https://codeload.github.com/dmytro-anokhin/renderable/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmytro-anokhin%2Frenderable/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264780619,"owners_count":23662693,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coregraphics","coreimage","swift","uikit"],"created_at":"2024-11-07T18:27:20.923Z","updated_at":"2025-07-11T10:03:45.562Z","avatar_url":"https://github.com/dmytro-anokhin.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Using Swift to access individual pixels of CGImage, CIImage, and UIImage.\n\nThe **Renderable** demonstrates how to use Swift protocol extension to convert object into bitmap and access individual pixels.\n\n### Usage\n\nInclude individual **Renderable.swift** and extension files to your project. Or everything as a framework.\n\n```swift \nlet image = UIImage(named: \"MyImage\")\nif let pixels = image.rgbaPixels() {\n    for pixel in pixels {\n        print(\"red: \\(pixel.r), green: \\(pixel.g), blue: \\(pixel.b), alpha: \\(pixel.a)\")\n    }\n}\n```\n\n### Description\n\nGoal is to explore how protocol extensions in Swift can connect types from different frameworks. In this case CoreGraphics, CoreImage, and UIKit.\n\nI use `Renderable` protocol to declare types that can be represented in a bitmap:\n\n```swift\nprotocol Renderable {\n\n    var pixelSize: (width: Int, height: Int) { get }\n\n    func render(in context: CGContext)\n}\n```\n\nThe `render(in:)` is a drawing routine. To construct a bitmap context all I need to know is size in pixels.\n\n#### Rendering\n\nImplementations for `CGImage` and `CIImage` are straight forward:\n\n```swift\nextension CGImage: Renderable {\n    \n    var pixelSize: (width: Int, height: Int) {\n        return (width: width, height: height)\n    }\n    \n    func render(in context: CGContext) {\n        let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height))\n        context.draw(self, in: rect)\n    }\n}\n\nextension CIImage: Renderable {\n    \n    public var pixelSize: (width: Int, height: Int) {\n        return (width: Int(extent.width), height: Int(extent.height))\n    }\n    \n    public func render(in context: CGContext) {\n        let ciContext = CIContext(cgContext: context, options: nil)\n        ciContext.draw(self, in: extent, from: extent)\n    }\n}\n```\n\nUnlike `CGImage`, `UIImage` uses current context and does not allow to specify it. Therefore, underlying image is used:\n\n```swift\nextension UIImage: Renderable {\n\n    var pixelSize: (width: Int, height: Int) {\n        return (width: Int(size.width), height: Int(size.height))\n    }\n\n    func render(in context: CGContext) {\n        if let image = cgImage {\n            image.render(in: context)\n            return\n        }\n        \n        if let image = ciImage {\n            image.render(in: context)\n            return\n        }\n    }\n}\n```\n\n#### Color Space and Pixel\n\nI use 8-bit RGBA color space. Every pixel is represented by 4 bytes, one for Red, Green, Blue, and Alpha components. Component value can take 0...255 therefore best type to represent it is `UInt8`. And to represent a pixel I created `RGBAPixel` structure:\n\n```swift\nstruct RGBAPixel {\n\n    typealias Byte = UInt8\n\n    var r: Byte\n    \n    var g: Byte\n    \n    var b: Byte\n    \n    var a: Byte\n}\n```\n\nNice thing that `RGBAPixel` occupy same 4 bytes of memory as pixel in a bitmap.\n\n#### Accessing Bitmap Data\n\nUnifying rendering under `Renderable` type allows me create a single routine via protocol extension.\n\nIdea is to render in RGBA context and read underlying data:\n\n```swift\nextension Renderable {\n\n    func rgbaPixels() -\u003e [RGBAPixel]? {\n\n        let width = pixelSize.width\n        let height = pixelSize.height\n    \n        // The depth of color is 8-bit. Every pixel is represented by 4 bytes: red, green, blue, and alpha.\n        let bytesPerPixel = 4\n        let bitsPerComponent = 8\n        \n        let bytesPerRow = bytesPerPixel * width\n        // Total size for the bitmap in memory\n        let byteCount = bytesPerRow * height\n    \n        let rgbaColorSpace = CGColorSpaceCreateDeviceRGB()\n    \n        // RGBA bitmap context\n        if let context = CGContext(data: nil, width: width, height: height,\n            bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow,\n            space: rgbaColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)\n        {\n            // Render in context\n            render(in: context)\n            \n            // The bitmap is now in a continous chunk of memory. We can remap pointer into bytes and iterate over it.\n            // Every pixel is stored in 4 bytes (bytesPerPixel). First byte is red component, second - green, third - blue, fourth - alpha.\n            if let bytes = context.data?.bindMemory(to: RGBAPixel.Byte.self, capacity: byteCount) {\n                var pixel: RGBAPixel!\n                var result: [RGBAPixel] = []\n                \n                for i in 0..\u003cbyteCount {\n                    let value = bytes.advanced(by: i).pointee\n                    let component = i % bytesPerPixel\n                    \n                    if component == 0 { // Red\n                        if let pixel = pixel { // Store previous pixel\n                            result.append(pixel)\n                        }\n                    \n                        pixel = RGBAPixel(r: value, g: 0, b: 0, a: 0) // Create new\n                    }\n                    else if component == 1 { // Green\n                        pixel.g = value\n                    }\n                    else if component == 2 { // Blue\n                        pixel.b = value\n                    }\n                    else if component == 3 { // Alpha\n                        pixel.a = value\n                    }\n                }\n                \n                // Store last pixel\n                result.append(pixel)\n                \n                return result\n            }\n        }\n        \n        return nil\n    }\n}\n\n```\n\n\nAbility to extend existing types with protocols allows to bridge and unify interfaces between different frameworks. And protocol extensions are elegant solution to reduce boilerplate code.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmytro-anokhin%2Frenderable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmytro-anokhin%2Frenderable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmytro-anokhin%2Frenderable/lists"}