EasyImagy
EasyImagy makes it easy to process images in Swift.
var image = Image<RGBA<UInt8>>(named: "ImageName")!
print(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 {
// ...
}
// Processes images (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
Processing images by CoreGraphics is complicated: various formats, old C APIs and painful memory management. EasyImagy provides easier APIs to process images.
Typically the Image
type is used with the RGBA
type. The RGBA
is a simple structure declared as follows.
struct RGBA<Channel> {
var red: Channel
var green: Channel
var blue: Channel
var alpha: Channel
}
You can easily access to pixels using subscripts like image[x, y]
and also their channels using properties red
, green
, blue
and alpha
.
In addition, Image
and RGBA
provide some powerful APIs to process images. For example, it is possible to convert an image to grayscale combining Image.map
with RGBA.gray
in one line as shown below.
let grayscale: Image<UInt8> = image.map { $0.gray }
Another notable feature of EasyImagy is that the Image
is a struct
, 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>>(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
as well 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
Requirements
- Swift 4 or later
- Xcode 9 or later
Installation
Swift Package Manager
Package.swift
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
...
dependencies: [
.package(url: "https://github.com/koher/EasyImagy.git", from: "0.4.0"),
],
targets: [
.target(
...
dependencies: [
"EasyImagy",
]),
]
)
Carthage
Cartfile
github "koher/EasyImagy" "0.4.0"
Manually
- 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".
License
Github
link |
Stars: 225 |
Help us keep the lights on
Dependencies
Releases
0.4.0 - Mar 31, 2018
- Swift 4.0
- Generic
RGBA
ImageProtocol
- Refactored APIs
- Pointer APIs
More details: #21
0.4.0-alpha.9 - Mar 6, 2018
0.4.0-alpha.8 - Mar 6, 2018
0.4.0-alpha.7 - Dec 26, 2017
Float80
is available only on macOS.
Code using Float80
had passed tests also on iOS, tvOS and watchOS because Float80
were available on those OSs on simulators.
0.4.0-alpha.6 - Dec 20, 2017
resizedTo(width:height:interpolatedBy:)
without CoreGraphicsrotated(by:)
,rotated(byDegrees:)
androtated(by:interpolatedBy:)
,rotated(byDegrees:interpolatedBy:)
- Renamed
write(to:formatting:)
towrite(to:format:)
followingwrite(to:options:)
ofData
- Renamed
ExtrapolationMethod
cases.edging
to.edge
.repeating
to.repeat
.mirroring
to.reflection
Note: Some more renaming are still under consideration, e.g. ExtrapolationMethod
to PaddingMethod
and subscript(_:_:interpolatedBy:extrapolatedBy:)
to subscript(_:_:interpolation:padding:)
.