/swift-image

SwiftImage: A Swift library for image processing with Swifty APIs

Primary LanguageSwiftMIT LicenseMIT

EasyImagy (-> SwiftImage)

Build Status

(EasyImagy will be renamed to SwiftImage when 0.7.0 is released.)

EasyImagy is a Swift library for image processing.

var image = Image<RGBA<UInt8>>(named: "ImageName")!

let pixel: RGBA<UInt8> = image[x, y]
image[x, y] = RGBA(red: 255, green: 0, blue: 0, alpha: 127)
image[x, y] = RGBA(0xFF00007F) // red: 255, green: 0, blue: 0, alpha: 127

// Iterates over all pixels
for pixel in image {
    // ...
}

// Image processing (e.g. binarizations)
let binarized: Image<Bool> = image.map { $0.gray >= 127 }

// From/to `UIImage`
image = Image<RGBA<UInt8>>(uiImage: imageView.image!)
imageView.image = image.uiImage

Introduction

Image processing by CoreGraphics is complicated: various formats, old C APIs and painful memory management. EasyImagy provides easy and Swifty APIs for image processing.

With EasyImagy, images are handled as instances of the Image type. Image is a type similar to Array.

var image: Image<UInt8> = Image(width: 640, height: 480, pixels: [255, 248, /* ... */])

let pixel: UInt8 = image[x, y]
image[x, y] = 255

let width: Int = image.width // 640
let height: Int = image.height // 480

Typically Image is used with the RGBA type. RGBA is a simple structure declared as follows.

struct RGBA<Channel> {
    var red: Channel
    var green: Channel
    var blue: Channel
    var alpha: Channel
}

Because both Image and RGBA are generic types, concrete image types have a nested type parameter like Image<RGBA<UInt8>> when Image and RGBA are combined.

Image and RGBA provide some powerful APIs to process images. For example, it is possible to convert an RGBA image to grayscale combining Image.map with RGBA.gray in one line as shown below.

var image = Image<RGBA<UInt8>>(named: "ImageName")!
let grayscale: Image<UInt8> = image.map { $0.gray }

Another notable feature of EasyImagy is that Image is a structure, i.e. a value type, with copy-on-write. It means

  • Image instances never be shared
  • defensive copying is unnecessary
  • no wastful copying of Image instances
  • copying is executed lazily when it is required
var another = image // Not copied here because of copy-on-write
another[x, y] = RGBA(0xff0000ff) // Copied here lazily

Usage

Import

import EasyImagy

Initialization

let image = Image<RGBA<UInt8>>(named: "ImageName")!
let image = Image<RGBA<UInt8>>(contentsOfFile: "path/to/file")!
let image = Image<RGBA<UInt8>>(data: Data(/* ... */))!
let image = Image<RGBA<UInt8>>(uiImage: imageView.image!) // from a UIImage
let image = Image<RGBA<UInt8>>(nsImage: imageView.image!) // from a NSImage
let image = Image<RGBA<UInt8>>(width: 640, height: 480, pixels: pixels) // from pixels
let image = Image<RGBA<UInt8>>(width: 640, height: 480, pixel: .black) // a black RGBA image
let image = Image<UInt8>(width: 640, height: 480, pixel: .min) // a black grayscale image
let image = Image<Bool>(width: 640, height: 480, pixel: false) // a black binary image

Access to a pixel

// Gets a pixel by subscripts
let pixel = image[x, y]
// Sets a pixel by subscripts
image[x, y] = RGBA(0xFF0000FF)
image[x, y].alpha = 127
// Safe get for a pixel
if let pixel = image.pixelAt(x: x, y: y) {
    print(pixel.red)
    print(pixel.green)
    print(pixel.blue)
    print(pixel.alpha)
    
    print(pixel.gray) // (red + green + blue) / 3
    print(pixel) // formatted like "#FF0000FF"
} else {
    // `pixel` is safe: `nil` is returned when out of bounds
    print("Out of bounds")
}

Iteration

for pixel in image {
    ...
}

Rotation

let result = image.rotated(by: .pi) // Rotated clockwise by π
let result = image.rotated(byDegrees: 180) // Rotated clockwise by 180 degrees
// Rotated clockwise by π / 4 and fill the background with red
let result = image.rotated(by: .pi / 4, extrapolatedBy: .filling(.red))

Flip

let result = image.xReversed() // Flip Horizontally
let result = image.yReversed() // Flip Vertically

Resizing

let result = image.resizedTo(width: 320, height: 240)
let result = image.resizedTo(width: 320, height: 240,
    interpolatedBy: .nearestNeighbor) // Nearest neighbor

Crop

Slicing is executed with no copying costs.

let slice: ImageSlice<RGBA<UInt8>> = image[32..<64, 32..<64] // No copying costs
let cropped = Image<RGBA<UInt8>>(slice) // Copying is executed here

Conversion

Image can be converted by map in the same way as Array. Followings are the examples.

Grayscale

let result: Image<UInt8> = image.map { (pixel: RGBA<UInt8>) -> UInt8 in
    pixel.gray
}
// Shortened form
let result = image.map { $0.gray }

Binarization

let result: Image<Bool> = image.map { (pixel: RGBA<UInt8>) -> Bool in
    pixel.gray >= 128
}
// Shortened form
let result = image.map { $0.gray >= 128 }

Binarization (auto threshold)

let threshold = UInt8(image.reduce(0) { $0 + $1.grayInt } / image.count)
let result = image.map { $0.gray >= threshold }

Mean filter

let kernel = Image<Float>(width: 3, height: 3, pixel: 1.0 / 9.0)
let result = image.convoluted(kernel)

Gaussian filter

let kernel = Image<Int>(width: 5, height: 5, pixels: [
    1,  4,  6,  4, 1,
    4, 16, 24, 16, 4,
    6, 24, 36, 24, 6,
    4, 16, 24, 16, 4,
    1,  4,  6,  4, 1,
]).map { Float($0) / 256.0 }
let result = image.convoluted(kernel)

With UIImage

// From `UIImage`
let image = Image<RGBA<UInt8>>(uiImage: imageView.image!)

// To `UIImage`
imageView.image = image.uiImage

With NSImage

// From `NSImage`
let image = Image<RGBA<UInt8>>(nsImage: imageView.image!)

// To `NSImage`
imageView.image = image.nsImage

With CoreGraphics

// Drawing on images with CoreGraphics
var image = Image<PremultipliedRGBA<UInt8>>(uiImage: imageView.image!)
image.withCGContext { context in
    context.setLineWidth(1)
    context.setStrokeColor(UIColor.red.cgColor)
    context.move(to: CGPoint(x: -1, y: -1))
    context.addLine(to: CGPoint(x: 640, y: 480))
    context.strokePath()
}
imageView.image = image.uiImage

Requirements

  • Swift 4.2 or later
  • Xcode 10 or later

Installation

Swift Package Manager

.package(url: "https://github.com/koher/swift-image.git", from: "0.6.0"),
github "koher/swift-image" ~> 0.6.0

Manually

  1. Put EasyImagy.xcodeproj into your project/workspace in Xcode.
  2. Click your project icon and select the application target and the "General" tab.
  3. Add EasyImagy.framework to "Embedded Binaries".

License

The MIT License