{"id":13812764,"url":"https://github.com/koher/swift-image","last_synced_at":"2026-02-21T19:34:57.354Z","repository":{"id":31861080,"uuid":"35428820","full_name":"koher/swift-image","owner":"koher","description":"SwiftImage: an image library in Swift with Swifty APIs and value semantics","archived":false,"fork":false,"pushed_at":"2024-09-15T06:23:23.000Z","size":833,"stargazers_count":541,"open_issues_count":8,"forks_count":57,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-05-14T13:06:27.920Z","etag":null,"topics":["image","image-processing","swift"],"latest_commit_sha":null,"homepage":"","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/koher.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}},"created_at":"2015-05-11T14:24:52.000Z","updated_at":"2025-05-12T20:27:45.000Z","dependencies_parsed_at":"2022-08-17T20:00:31.935Z","dependency_job_id":null,"html_url":"https://github.com/koher/swift-image","commit_stats":null,"previous_names":["koher/easyimagy"],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koher%2Fswift-image","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koher%2Fswift-image/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koher%2Fswift-image/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koher%2Fswift-image/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koher","download_url":"https://codeload.github.com/koher/swift-image/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254150016,"owners_count":22022855,"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":["image","image-processing","swift"],"created_at":"2024-08-04T04:00:55.211Z","updated_at":"2025-10-21T10:48:39.674Z","avatar_url":"https://github.com/koher.png","language":"Swift","funding_links":[],"categories":["The Index"],"sub_categories":["Signal and Image Processing"],"readme":"![SwiftImage CI](https://github.com/koher/swift-image/actions/workflows/tests.yml/badge.svg)\n# SwiftImage\n\nSwiftImage is an image library written in Swift, which provides Swifty APIs and image types with [value semantics](https://developer.apple.com/videos/play/wwdc2015/414/).\n\n```swift\nvar image = Image\u003cRGBA\u003cUInt8\u003e\u003e(named: \"ImageName\")!\n\nlet pixel: RGBA\u003cUInt8\u003e = image[x, y]\nimage[x, y] = RGBA(red: 255, green: 0, blue: 0, alpha: 127)\nimage[x, y] = RGBA(0xFF00007F) // red: 255, green: 0, blue: 0, alpha: 127\n\n// Iterates over all pixels\nfor pixel in image {\n    // ...\n}\n\n// Image processing (e.g. binarizations)\nlet binarized: Image\u003cBool\u003e = image.map { $0.gray \u003e= 127 }\n\n// From/to `UIImage`\nimage = Image\u003cRGBA\u003cUInt8\u003e\u003e(uiImage: imageView.image!)\nimageView.image = image.uiImage\n```\n\n## Introduction\n\nSwiftImage makes it easy to access pixels of images. The `Image` type in SwiftImage can be used intuitively like 2D `Array`.\n\n```swift\nvar image: Image\u003cUInt8\u003e = Image(width: 640, height: 480, pixels: [255, 248, /* ... */])\n\nlet pixel: UInt8 = image[x, y]\nimage[x, y] = 255\n\nlet width: Int = image.width // 640\nlet height: Int = image.height // 480\n```\n\nWe can also access pixels of images using CoreGraphics. However, CoreGraphics requires us to struggle with complicated formats, old C APIs and painful memory management. SwiftImage provides clear and Swifty APIs for images.\n\nTypically `Image` is used with the `RGBA` type. `RGBA` is a simple `struct` declared as follows.\n\n```swift\nstruct RGBA\u003cChannel\u003e {\n    var red: Channel\n    var green: Channel\n    var blue: Channel\n    var alpha: Channel\n}\n```\n\nBecause `RGBA` is a generic type, it can represent various formats of pixels. For example, `RGBA\u003cUInt8\u003e` represents a pixel of 8-bit RGBA image (each channel has a value in `0...255`). Similarly, `RGBA\u003cUInt16\u003e` represents a pixel of 16-bit RGBA image (`0...65535`). `RGBA\u003cFloat\u003e` can represent a pixel whose channels are `Float`s, which is often used for machine learning. A pixel of binary images, which have only black or white pixels and are used for fax, can be represented using `RGBA\u003cBool\u003e`.\n\nWhen `RGBA` is used with `Image`, type parameters are nested like `Image\u003cRGBA\u003cUInt8\u003e\u003e` because both `Image` and `RGBA` are generic types. On the other hand, grayscale images can be represented without nested parameters: `Image\u003cUInt8\u003e` for 8-bit grayscale images and `Image\u003cUInt16\u003e` for 16-bit grayscale images.\n\n`Image` and `RGBA` provide powerful APIs to handle images. For example, it is possible to convert a RGBA image to grayscale combining `Image.map` with `RGBA.gray` in one line.\n\n```swift\nlet image: Image\u003cRGBA\u003cUInt8\u003e\u003e = // ...\nlet grayscale: Image\u003cUInt8\u003e = image.map { $0.gray }\n```\n\nAnother notable feature of SwiftImage is that `Image` is a `struct` with [value semantics](https://developer.apple.com/videos/play/wwdc2015/414/), which is achieved using copy-on-write. Therefore,\n\n- `Image` instances never be shared\n- defensive copying is unnecessary\n- there are no wasteful copying of `Image` instances\n- copying is executed lazily only when it is inevitable\n\n```swift\nvar another: Image\u003cUInt8\u003e = image // Not copied here because of copy-on-write\nanother[x, y] = 255               // Copied here lazily\nanother[x, y] == image[x, y]      // false: Instances are never shared\n```\n\n## Usage\n\n### Import\n\n```swift\nimport SwiftImage\n```\n\n### Initialization\n\n```swift\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(named: \"ImageName\")!\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(contentsOfFile: \"path/to/file\")!\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(data: Data(/* ... */))!\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(uiImage: imageView.image!) // from a UIImage\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(nsImage: imageView.image!) // from a NSImage\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(cgImage: cgImage) // from a CGImage\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(width: 640, height: 480, pixels: pixels) // from a pixel array\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(width: 640, height: 480, pixel: .black) // a black RGBA image\nlet image = Image\u003cUInt8\u003e(width: 640, height: 480, pixel: 0) // a black grayscale image\nlet image = Image\u003cBool\u003e(width: 640, height: 480, pixel: false) // a black binary image\n```\n\n### Access to a pixel\n\n```swift\n// Gets a pixel by subscripts\nlet pixel = image[x, y]\n```\n\n```swift\n// Sets a pixel by subscripts\nimage[x, y] = RGBA(0xFF0000FF)\nimage[x, y].alpha = 127\n```\n\n```swift\n// Safe get for a pixel\nif let pixel = image.pixelAt(x: x, y: y) {\n    print(pixel.red)\n    print(pixel.green)\n    print(pixel.blue)\n    print(pixel.alpha)\n    \n    print(pixel.gray) // (red + green + blue) / 3\n    print(pixel) // formatted like \"#FF0000FF\"\n} else {\n    // `pixel` is safe: `nil` is returned when out of bounds\n    print(\"Out of bounds\")\n}\n```\n\n### Iteration\n\n```swift\nfor pixel in image {\n    ...\n}\n```\n\n### Rotation\n\n```swift\nlet result = image.rotated(by: .pi) // Rotated clockwise by π\n```\n\n```swift\nlet result = image.rotated(byDegrees: 180) // Rotated clockwise by 180 degrees\n```\n\n```swift\n// Rotated clockwise by π / 4 and fill the background with red\nlet result = image.rotated(by: .pi / 4, extrapolatedBy: .filling(.red))\n```\n\n### Flip\n\n```swift\nlet result = image.xReversed() // Flip Horizontally\n```\n\n```swift\nlet result = image.yReversed() // Flip Vertically\n```\n\n### Resizing\n\n```swift\nlet result = image.resizedTo(width: 320, height: 240)\n```\n\n```swift\nlet result = image.resizedTo(width: 320, height: 240,\n    interpolatedBy: .nearestNeighbor) // Nearest neighbor\n```\n\n### Crop\n\nSlicing is executed with no copying costs.\n\n```swift\nlet slice: ImageSlice\u003cRGBA\u003cUInt8\u003e\u003e = image[32..\u003c64, 32..\u003c64] // No copying costs\nlet cropped = Image\u003cRGBA\u003cUInt8\u003e\u003e(slice) // Copying is executed here\n```\n\n### Conversion\n\n`Image` can be converted by `map` in the same way as `Array`. Followings are the examples.\n\n#### Grayscale\n\n```swift\nlet result: Image\u003cUInt8\u003e = image.map { (pixel: RGBA\u003cUInt8\u003e) -\u003e UInt8 in\n    pixel.gray\n}\n```\n\n```swift\n// Shortened form\nlet result = image.map { $0.gray }\n```\n\n#### Binarization\n\n```swift\nlet result: Image\u003cBool\u003e = image.map { (pixel: RGBA\u003cUInt8\u003e) -\u003e Bool in\n    pixel.gray \u003e= 128\n}\n```\n\n```swift\n// Shortened form\nlet result = image.map { $0.gray \u003e= 128 }\n```\n\n#### Binarization (auto threshold)\n\n```swift\nlet threshold = UInt8(image.reduce(0) { $0 + $1.grayInt } / image.count)\nlet result = image.map { $0.gray \u003e= threshold }\n```\n\n#### Mean filter\n\n```swift\nlet kernel = Image\u003cFloat\u003e(width: 3, height: 3, pixel: 1.0 / 9.0)\nlet result = image.convoluted(kernel)\n```\n\n#### Gaussian filter\n\n```swift\nlet kernel = Image\u003cInt\u003e(width: 5, height: 5, pixels: [\n    1,  4,  6,  4, 1,\n    4, 16, 24, 16, 4,\n    6, 24, 36, 24, 6,\n    4, 16, 24, 16, 4,\n    1,  4,  6,  4, 1,\n]).map { Float($0) / 256.0 }\nlet result = image.convoluted(kernel)\n```\n\n### With `UIImage`\n\n```swift\n// From `UIImage`\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(uiImage: imageView.image!)\n\n// To `UIImage`\nimageView.image = image.uiImage\n```\n\n### With `NSImage`\n\n```swift\n// From `NSImage`\nlet image = Image\u003cRGBA\u003cUInt8\u003e\u003e(nsImage: imageView.image!)\n\n// To `NSImage`\nimageView.image = image.nsImage\n```\n\n### With CoreGraphics\n\n```swift\n// Drawing on images with CoreGraphics\nvar image = Image\u003cPremultipliedRGBA\u003cUInt8\u003e\u003e(uiImage: imageView.image!)\nimage.withCGContext { context in\n    context.setLineWidth(1)\n    context.setStrokeColor(UIColor.red.cgColor)\n    context.move(to: CGPoint(x: -1, y: -1))\n    context.addLine(to: CGPoint(x: 640, y: 480))\n    context.strokePath()\n}\nimageView.image = image.uiImage\n```\n\n## Requirements\n\n- Swift 5.9 or later\n\n## License\n\n[The MIT License](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoher%2Fswift-image","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoher%2Fswift-image","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoher%2Fswift-image/lists"}