A modern Swift wrapper around libvips, the fast image processing library. SwiftVIPS provides a Swift-native API for libvips' 300+ image processing operations with full Linux compatibility.
- 🚀 Fast & Memory Efficient: Built on libvips' demand-driven, horizontally threaded architecture
- 🔢 Operator Overloading: Intuitive arithmetic operations (
+
,-
,*
,/
) - 📁 Format Support: JPEG, PNG, TIFF, WebP, HEIF, GIF, PDF, SVG and more
- 🐧 Linux Compatible: Cross-platform support
- 🧠 Memory Safe: Automatic memory management with proper reference counting
Add SwiftVIPS to your Package.swift
:
dependencies: [
.package(url: "https://github.com/t089/swift-vips.git", branch: "main")
]
Install libvips on your system:
macOS:
brew install vips
Ubuntu/Debian:
apt-get update && apt-get install -y libvips-dev
Other systems: See libvips installation guide
Container:
You can use the containers provided at ghcr.io/t089/swift-vips-builder
to get a ready-to-use development environment with both Swift and libvips installed.
import VIPS
// Initialize VIPS once per application
try VIPS.start()
// Load, resize, and save an image
try VIPSImage(fromFilePath: "input.jpg")
.thumbnailImage(width: 800, height: 600, crop: .attention)
.writeToFile("output.jpg", quality: 85)
// Load from file
let image = try VIPSImage(fromFilePath: "photo.jpg")
print("Size: \(image.size.width)×\(image.size.height)")
// Load from memory buffer
let data = try Data(contentsOf: URL(fileURLWithPath: "photo.jpg"))
let imageFromBuffer = try VIPSImage(data: data)
// Create from raw pixel data
let pixels = Array(repeating: UInt8(255), count: 100 * 100 * 3) // White 100×100 RGB
let rawImage = try VIPSImage(
data: pixels,
width: 100,
height: 100,
bands: 3,
format: .uchar
)
let image = try VIPSImage(fromFilePath: "input.jpg")
// Resize with scale factor
let resized = try image.resize(scale: 0.5)
// Create thumbnail with smart cropping
let thumbnail = try image.thumbnailImage(width: 200, height: 200, crop: .attention)
let image1 = try VIPSImage(fromFilePath: "photo1.jpg")
let image2 = try VIPSImage(fromFilePath: "photo2.jpg")
// Image arithmetic
let sum = try image1 + image2
let difference = try image1 - image2
let product = try image1 * image2
let quotient = try image1 / image2
// Scalar operations
let brightened = try image1 + 50 // Add 50 to all pixels
let dimmed = try image1 * 0.8 // Scale all pixels by 0.8
let enhanced = try image1 * [1.2, 1.0, 0.9] // Per-channel scaling
// Linear transformation: a * image + b
let linearized = try image1.linear([1.1, 1.0, 0.9], [10, 0, -5])
let image = try VIPSImage(fromFilePath: "input.tiff")
// Save to file with quality control
try image.writeToFile("output.jpg", quality: 85)
// Export to memory buffer
let jpegBuffer = try image.jpegsave(quality: 90)
let pngBuffer = try image.pngsave(compression: 6)
let webpBuffer = try image.webpsave(quality: 80, lossless: false)
// Generic format export based on file extension
let buffer = try image.writeToBuffer(suffix: ".png", quality: 95)
// Complex image processing chain
let processed = try VIPSImage(fromFilePath: "raw-photo.jpg")
.autorot() // Auto-rotate based on EXIF
.thumbnailImage(width: 1200, height: 800, crop: .smart)
.linear([1.1, 1.0, 0.9], [0, 0, 0]) // Color correction
.gamma(exponent: 1.2) // Gamma adjustment
try processed.writeToFile("processed.jpg", quality: 92)
let image = try VIPSImage(fromFilePath: "photo.jpg")
// Statistical operations
let avgValue = try image.avg() // Average pixel value
let deviation = try image.deviate() // Standard deviation
// Mathematical functions
let absolute = try image.abs() // Absolute values
let inverted = try image.invert() // Invert image
let squared = try image.square() // Square pixel values
// Get pixel value at specific coordinates
let pixelValue = try image.getpoint(x: 100, y: 50) // Returns [Double] for all bands
let rgbImage = try VIPSImage(fromFilePath: "photo.jpg")
// Extract a single band (channel)
let redChannel = try rgbImage.extractBand(0) // Extract red channel
let greenChannel = try rgbImage.extractBand(1) // Extract green channel
// Add constant values as new bands (e.g., add alpha channel)
let withAlpha = try rgbImage.bandjoinConst(c: [255]) // Add opaque alpha
The Swift API follows largely the naming and conventions of the C library with minor adjustments to make it more idiomatic in Swift.
VIPS
: Library initialization and shutdownVIPSImage
: Main image class with processing operationsVIPSBlob
: Binary data containerVIPSError
: The error thrown by all libvips operationsVIPSSource
/VIPSTarget
: Advanced I/O operations
Loading:
VIPSImage(fromFilePath:)
- Load from fileVIPSImage(data:)
- Load from memory bufferVIPSImage(data:width:height:bands:format:)
- Create from raw pixels
Transformations:
resize(scale:)
- Resize by scale factorthumbnailImage(width:height:crop:)
- Create thumbnail with croppingextractArea(left:top:width:height:)
- Extract rectangular regionrotate(angle:)
- Rotate by degreesflip(direction:)
- Flip horizontally or verticallyautorot()
- Auto-rotate based on EXIF
Arithmetic:
add()
,subtract()
,multiply()
,divide()
- Basic mathlinear()
- Linear transformation:a * image + b
gamma()
- Gamma correctioninvert()
- Invert pixel values- Operator overloading:
+
,-
,*
,/
Statistics:
avg()
- Average pixel valuedeviate()
- Standard deviationgetpoint(x:y:)
- Get pixel value at coordinates
Band Operations:
extractBand(_:)
- Extract single band/channelbandjoinConst(c:)
- Add constant values as new bands
Export:
writeToFile(_:quality:)
- Save to filewriteToBuffer(suffix:quality:)
- Export to memoryjpegsave()
,pngsave()
,webpsave()
- Format-specific export
size
- Image dimensions asSize
structwidth
,height
- Image dimensionsbands
- Number of channels (e.g., 3 for RGB, 4 for RGBA)format
- Pixel format (.uchar
,.float
, etc.)hasAlpha
- Whether image has transparencyorientation
- EXIF orientation valuespace
- Color space interpretation as string
libvips tries to avoid copies of data as much as possible. When interfacing with Swift this can be challenging. For safety reasons, most APIs copy data into the VIPS classes. For maximum performance, you may want to avoid those copies and let libvips only "borrow" your memory.
Example:
let imageData: Data = // ... some data from somewhere
let image = VIPSImage(data: imageData) // the data is copied and "owned" by VIPSImage.
To avoid the copy, you need access to the raw storage of imageData
:
let imageData: Data = // ... some data from somewhere
let jpeg: VIPSBlob = imageData.withUnsafeBytes { buffer in
let image = VIPSImage(unsafeData: buffer)
// image or any image created from it MUST not escape the closure.
return image.jpegsave() // returning data is safe
}
When SwiftVIPS returns data, it always returns an instance of VIPSBlob
. SwiftVIPS can work with VIPSBlob
directly without the need to copy data. VIPSBlob
conforms to Collection
, so you can easily convert it to other Swift types such as Array<UInt8>
or Data
:
let blob: VIPSBlob = ...
let array = Array(blob) // copies data
let data = Data(blob) // copies data
// with `Data` you can even avoid the copy:
let dataNoCopy = blob.withUnsafeBytesAndStorageManagement { buffer, storageManagement in
_ = storageManagement.retain()
return Data(
bytesNoCopy: .init(mutating: buffer.baseAddress!),
count: buffer.count,
deallocator: .custom { ptr, _ in
storageManagement.release()
}
)
}
# Build the project
swift build
# Run tests (uses Swift Testing framework)
swift test
# Build and run example tool
swift run vips-tool
# Release build
swift build -c release
- Swift 6.0+
- libvips 8.12+ installed on system
- Linux or macOS
SwiftVIPS mirrors libvips' modular structure:
Core/
: Initialization, memory management, fundamental typesArithmetic/
: Mathematical operations and operator overloadingConversion/
: Image transformations and geometric operationsForeign/
: File format support (organized by format)Generated/
: Auto-generated bindings for libvips operationsCvipsShim/
: C interoperability layer
SwiftVIPS inherits libvips' performance characteristics:
- Demand-driven: Only processes pixels when needed
- Streaming: Can handle images larger than available RAM
- Multithreaded: Automatic parallelization across CPU cores
- Minimal copying: Efficient memory usage
Copyright 2025 Tobias Haeberle