Swift Package Index

Swim

https://github.com/t-ae/swim.git

Cross platform image library for Swift


Compatibility

  • 3.9.0 and master
    5.3
    5.2
    5.1
    5.0
    4.2
  • 3.9.0 and master
    iOS
    macOS(Intel)
    macOS(ARM)
    Linux
    tvOS
    watchOS

Swim

Cross platform image library for Swift.

API

Generic Image type

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)

Reading & writing Data

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

resize

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

warp

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

blend

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

filter

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

fft

Histogram equalization

var image = try Image<Gray, Double>(contentsOf: url)
Histograms.equalize(image: &image)

Example: Before / After

hist

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

bayer_bggr

Application examples

VisualTests contains more examples (works only on macOS).

License

The MIT License