Swim
Cross platform image library for Swift.
API
Image
type
Generic struct Image<P: PixelType, T: DataType>
Supported types
PixelType
: Gray
, GrayAlpha
, RGB
, RGBA
, ARGB
DataType
: Bool
, UInt8
, Int
, Float
, Double
, Complex<T: BinaryFloatingPoint>
Some functions assume pixel values are:
- in [0, 255] range if
DataType
is integer. - in [0, 1] range if
DataType
is floating point.
Creation
let image = Image<RGBA, UInt8>(width: 3, height: 5, data: uint8Array)
// Can use type inference
let gray = Image(width: 3, height: 20, gray: intArray)
let rgb = Image(width: 4, height: 5, rgb: floatArray)
let rgba = Image(width: 3, height: 5, rgba: doubleArray)
let argb = Image(width: 5, height: 3, argb: uint8Array)
// Filled with values/colors
let zero = Image<RGBA, Double>(width: 3, height: 4, value: 0)
let red = Image<RGBA, Double>(width: 3, height: 5, color: Color(r: 1, g: 0, b: 0, a: 1))
Input and output
For reading and writing image, Swim uses stb_image.h
and stb_image_write.h
.
Reading & writing files
let image = try Image<RGBA, UInt8>(contentsOf: url)
try image.write(to: dstPath)
Data
Reading & writing let data = try Data(contentsOf: url)
let image = try Image<RGB, UInt8>(fileData: data)
let jpegData = try image.fileData(format: .jpeg(quality: 80))
Platform specific operations
let image = try! Image<RGBA, UInt8>(contentsOf: url)
// on macOS
let nsImage = image.nsImage()
let imageFromNS = Image<RGBA, UInt8>(nsImage: nsImage)!
// on iOS
let uiImage = image.uiImage()
let imageFromUI = Image<RGBA, UInt8>(uiImage: uiImage)!
// with vImage
var argb = image.toARGB()
let kernel = Filter<UInt8>.mean(size: 5)
let blurred: Image<ARGB, UInt8> = try vImageUtils.createImageWithBuffer(width: argb.width, height: argb.height) { dest in
try vImageUtils.withBuffer(image: &argb) { argb in
try kernel.withUnsafeBufferPointer { kernel in
let flags: vImageProcessingFlag = [.edgeExtend,
.printDiagnosticsToConsole]
let code = vImageConvolve_ARGB8888(&argb, &dest, nil, 0, 0, kernel.baseAddress, 5, 5, nil, flags.vImage_Flags)
try vImageUtils.validateErrorCode(code)
}
}
}
// on Swift for TensorFlow
let tensor = Tensor(image: image)
Subscriptions
Pixel manipulation
let image = try Image<RGBA, UInt8>(contentsOf: url)
let color: Color<RGBA, UInt8> = image[0, 0]
let red: UInt8 = image[0, 0, 0] // red channel of (x: 0, y: 0)
let red2: UInt8 = image[0, 0, .red] // ditto
let red3: UInt8 = image[0, 0][.red] // ditto
image[1, 0] += 1 // Add 1 for each channel
image[1, 0, .green] += 1 // Add 1 for Green channel
Subimage
let image = try Image<RGBA, UInt8>(contentsOf: url)
let sub1: Image<RGBA, UInt8> = image[0..<100, 0..<100]
let sub2: Image<RGBA, UInt8> = image[rows: 0..<100]
image[col: 2] += 1
Channel extraction
let image = try Image<RGBA, UInt8>(contentsOf: url)
let red: Image<Gray, UInt8> = image[channel: 0]
image[channel: .blue] += 1
Conversion
let image = try Image<RGB, Float>(contentsOf: url)
// to gray scale
let gray1: Image<Gray, Float> = image.toGray() // with default weights
let gray2: Image<Gray, Float> = image.toGray(wr: 1/3, wg: 1/3, wb: 1/3) // with specified weights
// type conversion
let doubleImage1: Image<RGB, Double> = image.cast()
let doubleImage2 = image.cast(to: Double.self) // ditto
// pixel conversion
let redOnlyRGBA: Image<RGBA, Float> = image.pixelwiseConverted { src, dst in
dst[.red] = src[.red]
dst[.green] = 0
dst[.blue] = 0
dst[.alpha] = 1
}
Drawing
var image = try Image<RGB, Float>(contentsOf: url)
image.drawLine((0, 0), (100, 120), color: Color(r: 1, g: 0, b: 0))
image.drawRect(10..<20, 30..<50, color: .green)
image.drawCircle(center: (50, 50), radius: 30, color: .blue)
image.drawImage(origin: (80, 80), rgbImage) // simply overwrites
image.drawImage(origin: (200, 200), rgbaImage) // with alpha blending
let font = try! TrueTypeFont(url: URL(fileURLWithPath: "/System/Library/Fonts/Helvetica.ttc"),
fontSize: 30)
image.drawText(origin: (100, 100),
text: "TEXT DRAWING",
font: font,
color: .black)
For font rendering, Swim uses stb_truetype.h.
Resize
let image = try Image<RGB, Float>(contentsOf: url)
let resizedBL = image.resize(width: 512, height: 512) // default .bilinear
let resizedNN = image.resize(width: 512, height: 512, method: .nearestNeighbor)
let resizedBC = image.resize(width: 512, height: 512, method: .bicubic)
let resizedAA = image.resize(width: 512, height: 512, method: .areaAverage)
Example: NearestNeighbor / Bilinear / Bicubic / Lanczos2 / Lanczos3 / Area Average
Warp
let image = try Image<RGBA, Double>(contentsOf: url)
let affine = AffineTransformation<Double>(scale: (1, 1.5), rotation: .pi/6. translation: (100, 120))
// `edgeMode` specifies how to fill pixels outside the base image.
let interpolator = BilinearInterpolator<RGBA, Double>(edgeMode: .edge)
let warpedImage = image.warp(transformation: affine, outputSize: (500, 500), interpolator: interpolator)
Example: NN+Wrap / BL+Constant / BC+Reflect / Lanczos2+Edge / Lanczos3+Symmetric
Compare images
let image1 = try Image<Gray, Double>(contentsOf: url1)
let image2 = try Image<Gray, Double>(contentsOf: url2)
let ssd = ImageCompare.ssd(image1, image2)
let sad = ImageCompare.sad(image1, image2)
let ncc = ImageCompare.ncc(image1, image2)
let zncc = ImageCompare.zncc(image1, image2)
let psnr = ImageCompare.psnr(image1, image2)
let ssim = ImageCompare.ssim(image1, image2, windowSize: 7)
Blending
var bottomImage = try Image<RGB, Float>(contentsOf: url1)
let topimage = try Image<RGB, Float>(contentsOf: url2)
bottomImage(image: topImage, mode: .multiply)
bottomImage(image: topImage, mode: .additive)
bottomImage(image: topImage, mode: .screen)
bottomImage(image: topImage, mode: .overlay)
Example: Multiply / Additive / Screen / Overlay
Integral image
let image = try Image<Gray, Float>(contentsOf: url)
let integral = IntegralImageConverter.convert(image: image)
Convolution/Filter
let image = try Image<Gray, Float>(contentsOf: url)
let blur = image.convoluted(Filter.gaussian3x3)
let maximum = image.rankFilter(.maximum, windowSize: 3)
let bilateral = image.bilateralFilter(windowSize: 5, distanceSigma: 1, valueSigma: 0.1)
let nlmean = image.nonLocalMeanFilter(windowSize: 5, distance: 2, sigma: 0.1)
Example: Gaussian x10 / Bilateral x5 / Emboss / Sobel(Horizontal) / Laplacian
Fast Fourier transformation
let image = try Image<Gray, Double>(contentsOf: url)
// image size must be power of 2
let transformed: Image<Gray, Complex<Double>> = FourierTransformer.fft(image: image)
let inverted: Image<Gray, Double> = FourierTransformer.ifft(image: transformed)
Example: Spectrum and inverted image / Low-pass filtered / High-pass filtered
Histogram equalization
var image = try Image<Gray, Double>(contentsOf: url)
Histograms.equalize(image: &image)
Bayer filter
let image = try Image<RGB, Float>(contentsOf: url)
let converter = BayerConverter(pattern: .bggr)
let bayer = converter.convert(image: image)
let reconstruct = converter.demosaic(image: bayer)
Example: Base / Bayer format / Reconstructed
Application examples
VisualTests contains more examples (works only on macOS).