(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
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
import EasyImagy
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
// 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")
}
for pixel in image {
...
}
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))
let result = image.xReversed() // Flip Horizontally
let result = image.yReversed() // Flip Vertically
let result = image.resizedTo(width: 320, height: 240)
let result = image.resizedTo(width: 320, height: 240,
interpolatedBy: .nearestNeighbor) // Nearest neighbor
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
Image
can be converted by map
in the same way as Array
. Followings are the examples.
let result: Image<UInt8> = image.map { (pixel: RGBA<UInt8>) -> UInt8 in
pixel.gray
}
// Shortened form
let result = image.map { $0.gray }
let result: Image<Bool> = image.map { (pixel: RGBA<UInt8>) -> Bool in
pixel.gray >= 128
}
// Shortened form
let result = image.map { $0.gray >= 128 }
let threshold = UInt8(image.reduce(0) { $0 + $1.grayInt } / image.count)
let result = image.map { $0.gray >= threshold }
let kernel = Image<Float>(width: 3, height: 3, pixel: 1.0 / 9.0)
let result = image.convoluted(kernel)
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)
// From `UIImage`
let image = Image<RGBA<UInt8>>(uiImage: imageView.image!)
// To `UIImage`
imageView.image = image.uiImage
// From `NSImage`
let image = Image<RGBA<UInt8>>(nsImage: imageView.image!)
// To `NSImage`
imageView.image = image.nsImage
// 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
- Swift 4.2 or later
- Xcode 10 or later
.package(url: "https://github.com/koher/swift-image.git", from: "0.6.0"),
github "koher/swift-image" ~> 0.6.0
- Put EasyImagy.xcodeproj into your project/workspace in Xcode.
- Click your project icon and select the application target and the "General" tab.
- Add
EasyImagy.framework
to "Embedded Binaries".