Fast, memory-safe WebP encoding and decoding for Swift.
Correct colors, full resolution, no unsafe pointers. From UIImage, NSImage, CGImage, or raw bytes.
Quick start • Why Swift-WebP? • Installation • Usage • FAQ • Changelog
import WebP
// Encode: UIImage / NSImage / CGImage → WebP
let webPData = try WebPEncoder().encode(image, config: .preset(.photo, quality: 80))
// Decode: WebP → UIImage / NSImage / CGImage
let decoded = try WebPDecoder().decodeUIImage(from: webPData, options: WebPDecoderOptions())That's all you need. No pointers to manage, no buffers to free, and no color conversion code to write.
WebP files are often 25–34% smaller than JPEG and support transparency, so you save bandwidth, storage, and load time. Calling libwebp from Swift directly is error-prone, though. Swift-WebP takes care of the hard parts.
- 🎛️ Full libwebp control. Presets, lossless levels 0–9, target size or PSNR, near-lossless, alpha quality, and more.
- 🧵 Built for Swift 6. Uses Swift 6 language mode with strict memory-safety checking, and the public types are
Sendable. - ⚡️ Low allocation. Output is written straight into the returned
Datawithout an extra copy, and you can decode into your own reusable buffer. - 🐧 Runs on Linux. The core APIs work on Linux with a lightweight
FoundationEssentialsdependency.
Swift-WebP is a focused codec library. It converts between WebP and pixels or images, and leaves downloading, caching, and display to you. That makes it a good fit for image pipelines, upload processing, export features, and server-side conversion.
If you want WebP support inside an image-loading framework, or animated WebP playback, SDWebImageWebPCoder is a better fit.
| Minimum | |
|---|---|
| Swift | 6.2 (Swift 6 language mode) |
| iOS | 13.0 |
| macOS | 11.0 |
| Linux | Any platform supported by Swift 6.2 (core APIs) |
| libwebp | 1.6.0, via libwebp-Xcode (resolved automatically) |
Add Swift-WebP to your Package.swift:
dependencies: [
.package(url: "https://github.com/ainame/Swift-WebP.git", from: "0.7.0")
],
targets: [
.target(name: "YourApp", dependencies: [
.product(name: "WebP", package: "Swift-WebP")
])
]File → Add Package Dependencies…, then paste:
https://github.com/ainame/Swift-WebP.git
Important
Upgrading to 0.7.0? Encoder input validation is stricter, and UIImage encoding now outputs the full pixel size instead of the point size. See the CHANGELOG for migration details.
import WebP
let encoder = WebPEncoder()
// iOS: UIImage, macOS: NSImage
let data = try encoder.encode(image, config: .preset(.photo, quality: 80))
// Resize while encoding (set both width and height; 0 keeps the original size)
let thumb = try encoder.encode(image, config: .preset(.photo, quality: 80), width: 320, height: 240)// Works with any bitmap layout: premultiplied, BGRA, and 16-bit images are converted first.
let data = try encoder.encode(normalizing: cgImage, config: .preset(.picture, quality: 95))
// Encodes the backing bytes as-is (no copy). `format` must match the image's layout with straight alpha.
let raw = try encoder.encode(cgImage, format: .rgba, config: .preset(.picture, quality: 95))Tip
If you're not sure which one to use, use encode(normalizing:). Images from drawing or screen capture are premultiplied. The UIImage and NSImage encoders use encode(normalizing:) too.
let data = try encoder.encode(
rgbaBytes, // [UInt8], Data, or a borrowed Span<UInt8>
format: .rgba, // .rgb, .rgba, .rgbx, .bgr, .bgra, .bgrx
config: .preset(.picture, quality: 95),
originWidth: width,
originHeight: height,
stride: width * 4
)The encoder validates dimensions, stride, and input capacity. Provide at least stride * originHeight bytes, including padding after the final row. Borrowed spans avoid copying the input storage.
// Lossy, tuned for a type of content: .default, .picture, .photo, .drawing, .icon, .text
let lossy = WebPEncoderConfig.preset(.photo, quality: 80)
// Lossless, level 0 (fastest) to 9 (smallest)
let lossless = try WebPEncoderConfig.losslessPreset(level: 6)
// Or adjust individual libwebp options
var custom = WebPEncoderConfig.preset(.picture, quality: 90)
custom.method = 6 // slower encoding, smaller output
custom.alphaQuality = 80Note
Encoding and decoding are single-threaded. The libwebp-Xcode package that Swift-WebP depends on compiles libwebp without threading support, so WebPEncoderConfig.threadLevel and WebPDecoderOptions.useThreads currently have no effect. To use more cores, run independent images concurrently.
let decoder = WebPDecoder()
let options = WebPDecoderOptions()
#if canImport(UIKit)
let image = try decoder.decodeUIImage(from: webPData, options: options)
#elseif canImport(AppKit)
let image = try decoder.decodeNSImage(from: webPData, options: options)
#endif
let cgImage = try decoder.decodeCGImage(from: webPData, options: options)var options = WebPDecoderOptions()
options.useScaling = true
options.scaledWidth = 200
options.scaledHeight = 0 // 0 = infer from the aspect ratio
let thumbnail = try decoder.decodeCGImage(from: webPData, options: options)Set either scaled dimension to 0 to infer it while preserving the aspect ratio (after cropping, if enabled). Enable useCropping and set cropLeft, cropTop, cropWidth, and cropHeight to crop before scaling. Invalid decoder options throw WebPDecodingError.invalidParam.
let rgbaData = try decoder.decode(webPData, options: options, format: .rgba)Allocate the buffer once and reuse it for every frame or tile:
let required = try decoder.requiredOutputByteCount(for: webPData, options: options, format: .rgba)
var output = [UInt8](repeating: 0, count: required)
let written = try decoder.decode(webPData, into: &output, options: options, format: .rgba)decode(_:into:) also accepts an inout MutableSpan<UInt8>.
Read the dimensions and flags from the header without decoding any pixels. For example, you can reject large uploads before they use memory:
let info = try WebPImageInspector.inspect(webPData)
guard info.width * info.height <= 4096 * 4096 else { throw UploadError.tooLarge }
print(info.width, info.height, info.hasAlpha, info.hasAnimation, info.format) // format: .lossy / .losslessAll APIs throw typed Swift errors, so a bad input doesn't crash your app:
do {
let data = try encoder.encode(bytes, format: .rgba, config: config,
originWidth: w, originHeight: h, stride: w * 4)
} catch WebPEncoderError.invalidParameter {
// Buffer too small, bad stride, or bad dimensions
} catch let status as WebPEncodeStatusCode {
// libwebp encoder error, e.g. .badDimension or .fileTooBig
}| Feature | iOS | macOS | Linux |
|---|---|---|---|
Encode [UInt8] / Data / Span |
✅ | ✅ | ✅ |
Decode to Data / [UInt8] / MutableSpan |
✅ | ✅ | ✅ |
| Scale and crop while decoding | ✅ | ✅ | ✅ |
| Header inspection | ✅ | ✅ | ✅ |
CGImage encode and decode |
✅ | ✅ | — |
UIImage encode and decode |
✅ | — | — |
NSImage encode and decode |
— | ✅ | — |
| Animated WebP | Detection only (hasAnimation) |
My translucent pixels look darker, or red and blue are swapped.
You're probably passing a premultiplied or BGRA CGImage to encode(_:format:). Use encode(normalizing:) instead, or the UIImage/NSImage overloads, which normalize the image for you. Upgrading to 0.7.0+ fixes this for platform images.
My encoded UIImage got bigger after upgrading to 0.7.0.
0.7.0 encodes the full pixel size (for example, 3× the point size on a @3x device). To keep the old size, pass width and height in points.
Why does encoding throw invalidParameter for my buffer?
The input must contain at least stride * originHeight bytes, including any padding after the last row, as libwebp requires. Earlier versions didn't check this and could read past the end of the buffer.
Which libwebp version am I running?
print(WebPEncoder.libwebpVersion, WebPDecoder.libwebpVersion) // e.g. 1.6.0Demo/ contains an iOS app that shows the library in action, including before/after comparisons of the color and resolution fixes. Open Demo/SwiftWebPDemo.xcodeproj and run the SwiftWebPDemo scheme.
make format # toolchain-bundled swift-format
swift build
swift testThe local toolchain is Swift 6.4.0, selected by .swift-version. It doesn't define the minimum supported Swift version. See CONTRIBUTING.md for the full workflow.
The library enables strict memory-safety checking. Intentional C operations are marked with unsafe at the interoperability boundary. The pointer-based APIs and low-level buffer configuration remain unsafe interfaces. The Array, Data, and Span entry points handle those requirements internally.
Benchmarks
See the benchmark guide for reproducible version comparisons, pipeline benchmarks, metric definitions, and interpretation limits.
Scripts/benchmark-resource.sh
Scripts/validate-resource.sh
Scripts/compare-with-cwebp.shYou can tune benchmark parameters with environment variables such as
MODE=pipeline|source-decode-only|encode-only|decode-only,
WIDTH, HEIGHT, ITERATIONS, WARMUP, QUALITY, THREADS_FLAG=off,
INPUT=/absolute/path/to/image, and SOURCE_DECODE_PER_ITERATION=on.
Tune validation thresholds with
MAX_SOURCE_DECODE_AVG_MS, MAX_ENCODE_AVG_MS, MAX_DECODE_AVG_MS,
MAX_PIPELINE_ENCODE_AVG_MS, MAX_ENCODE_P95_MS, MAX_DECODE_P95_MS,
MAX_STAGE_PEAK_RSS_MB, and MAX_PIPELINE_PEAK_RSS_MB.
Issues and pull requests are welcome. Please read CONTRIBUTING.md first, and add user-facing changes to CHANGELOG.md.
Swift-WebP is available under the MIT license. See LICENSE.