A native Swift implementation of the JPEG XL (ISO/IEC 18181) compression codec, optimized for Apple Silicon hardware.
JXLSwift provides a pure Swift implementation of the JPEG XL image compression standard with hardware-accelerated encoding for Apple Silicon (ARM NEON/SIMD) and Apple Accelerate framework integration. The library is designed for high performance with separate code paths for x86-64 that can be removed if needed.
- ✅ Native Swift Implementation - Pure Swift, no C/C++ dependencies
- 🚀 Apple Silicon Optimized - Leverages ARM NEON SIMD via portable Swift SIMD types
- ⚡ Apple Accelerate Integration - Uses vDSP for DCT and matrix operations, vImage for high-quality image resampling
- 🎯 Modular Architecture - Separate x86-64 code paths for future removal
- 🖥️ Cross-Platform GPU Acceleration - Metal on Apple platforms; Vulkan on Linux/Windows via
GPUComputeabstraction - 📦 Two Compression Modes:
- Lossless (Modular Mode) - Perfect pixel reproduction
- Lossy (VarDCT Mode) - High-quality lossy compression
- 🎨 Advanced Color Support:
- Standard: sRGB, linear RGB, grayscale
- Wide Gamut: Display P3, Rec. 2020
- HDR Transfer Functions: PQ (HDR10), HLG
- Alpha Channels: Straight and premultiplied modes
- 📊 Extra Channels - Depth maps, thermal data, spectral bands, and application-specific channels
- 🎬 Animation Support - Multi-frame encoding with frame timing and loop control
- 🔄 EXIF Orientation - Full support for all 8 EXIF orientation values (rotation/flip metadata)
- 🎯 Region-of-Interest (ROI) - Selective quality encoding with configurable feathering for smooth transitions
- 🎞️ Reference Frame Encoding - Delta encoding for animations with configurable keyframe intervals to reduce file size for video-like content
- 🔲 Patch Encoding - Copy repeated rectangular regions from reference frames for massive compression gains on screen content, slideshows, and animations with static elements
- 🎨 Noise Synthesis - Add film grain or synthetic noise to improve perceptual quality and mask quantization artifacts
- 🎨 Spline Encoding - Vector overlay rendering for smooth curves, edges, and line art with resolution-independent quality
- 📊 Quality Metrics - PSNR, SSIM, MS-SSIM, and Butteraugli perceptual distance for encoding validation
- 🧪 Validation Harness - Automated test harness with configurable criteria for quality, compression, and performance validation
- 🔗 Bitstream Compatibility - Structural validation and optional libjxl decode verification for bitstream correctness
- 📈 Benchmark Reports - JSON and HTML report generation with performance regression detection
- 🏎️ Speed Comparison - Systematic encoding speed measurement across all effort levels with throughput analysis
- 📦 Compression Comparison - Compression ratio analysis across quality levels with bits-per-pixel metrics
- 💾 Memory Comparison - Process-level memory usage tracking during encoding with per-megapixel analysis
- 🖼️ Test Image Corpus - Synthetic test image collections (Kodak-like, Tecnick-like, Wikipedia-like) for reproducible benchmarking
- 🔧 Flexible Configuration - Quality levels, effort settings, hardware acceleration control
- 📄 JPEG XL Container Format - ISOBMFF container with metadata boxes (EXIF, XMP, ICC)
- 🏷️ Metadata Extraction - Parse and extract EXIF, XMP, and ICC profiles from container files
- ✅ Progressive Encoding - Incremental rendering for faster perceived loading
- ✅ Progressive Decoding - Incremental image reconstruction from DC-only to full quality
- 🖼️ Image Export - Output decoded images to PNG, TIFF, BMP via platform image I/O
- Swift 6.2 or later
- macOS 13.0+ / iOS 16.0+ / tvOS 16.0+ / watchOS 9.0+ / visionOS 1.0+
- Apple Silicon (ARM64) recommended for optimal performance
- x86-64 supported with fallback implementations
Add JXLSwift to your Package.swift:
dependencies: [
.package(url: "https://github.com/Raster-Lab/JXLSwift.git", from: "0.1.0")
]To install the jxl-tool command-line tool:
# Build and install to /usr/local (requires sudo)
sudo make install
# Or install to a custom location
make PREFIX=~/.local install
# Install man pages (requires sudo for system-wide installation)
sudo make install-manThe Makefile provides several targets:
make build- Build the project in release modemake test- Run all testsmake coverage- Generate code coverage reportmake coverage-html- Generate HTML code coverage reportmake man- Generate man pagesmake docc- Generate API documentation with DocCmake docc-html- Generate HTML API documentationmake docc-preview- Preview API documentation in browsermake install- Install jxl-tool binarymake install-man- Install man pagesmake uninstall- Remove installed filesmake clean- Clean build artifactsmake help- Show help message
import JXLSwift
// Create an image frame
var frame = ImageFrame(
width: 1920,
height: 1080,
channels: 3,
pixelType: .uint8,
colorSpace: .sRGB
)
// Fill with image data
for y in 0..<frame.height {
for x in 0..<frame.width {
frame.setPixel(x: x, y: y, channel: 0, value: r) // Red
frame.setPixel(x: x, y: y, channel: 1, value: g) // Green
frame.setPixel(x: x, y: y, channel: 2, value: b) // Blue
}
}
// Create encoder
let encoder = JXLEncoder()
// Encode image
let result = try encoder.encode(frame)
// Access compressed data
let compressedData = result.data
let stats = result.stats
print("Compressed: \(stats.originalSize) → \(stats.compressedSize) bytes")
print("Ratio: \(stats.compressionRatio)x in \(stats.encodingTime)s")// Create RGBA frame with alpha channel
var rgbaFrame = ImageFrame(
width: 1920,
height: 1080,
channels: 4, // RGBA
pixelType: .uint8,
colorSpace: .sRGB,
hasAlpha: true,
alphaMode: .straight // or .premultiplied
)
// Set pixels including alpha channel
for y in 0..<rgbaFrame.height {
for x in 0..<rgbaFrame.width {
rgbaFrame.setPixel(x: x, y: y, channel: 0, value: r) // Red
rgbaFrame.setPixel(x: x, y: y, channel: 1, value: g) // Green
rgbaFrame.setPixel(x: x, y: y, channel: 2, value: b) // Blue
rgbaFrame.setPixel(x: x, y: y, channel: 3, value: alpha) // Alpha
}
}
// Encode with alpha - works with both lossless and lossy modes
let encoder = JXLEncoder(options: EncodingOptions(mode: .lossy(quality: 90)))
let result = try encoder.encode(rgbaFrame)// Lossless (Modular) mode - bit-perfect preservation
let options = EncodingOptions(mode: .lossless)
let encoder = JXLEncoder(options: options)
let result = try encoder.encode(frame)let encoder = JXLEncoder(options: .lossless)
let result = try encoder.encode(frame)let encoder = JXLEncoder(options: .highQuality)
let result = try encoder.encode(frame)// Fast encoding (effort level 1: lightning)
let fastOptions = EncodingOptions(
mode: .lossy(quality: 90),
effort: .lightning
)
// Balanced encoding (effort level 7: squirrel - default)
let balancedOptions = EncodingOptions(
mode: .lossy(quality: 90),
effort: .squirrel
)
// Best compression (effort level 9: tortoise)
let bestOptions = EncodingOptions(
mode: .lossy(quality: 90),
effort: .tortoise
)let options = EncodingOptions(
mode: .lossy(quality: 92),
effort: .kitten, // Highest quality
progressive: true,
useHardwareAcceleration: true,
useAccelerate: true,
useMetal: true,
numThreads: 0, // Auto-detect
useANS: true // Use rANS entropy coding
)
let encoder = JXLEncoder(options: options)
let result = try encoder.encode(frame)Progressive encoding allows images to be rendered incrementally as data arrives, showing a low-resolution preview first that gradually refines to full quality. This is particularly useful for web delivery and streaming scenarios.
// Enable progressive encoding
let progressiveOptions = EncodingOptions(
mode: .lossy(quality: 90),
effort: .squirrel,
progressive: true // Enable multi-pass encoding
)
let encoder = JXLEncoder(options: progressiveOptions)
let result = try encoder.encode(frame)Progressive encoding splits DCT coefficients into multiple passes:
- Pass 0 (DC-only): Encodes only DC coefficients, providing a low-resolution 8×8 preview
- Pass 1 (Low-frequency AC): Adds low-frequency details (coefficients 1-15)
- Pass 2 (High-frequency AC): Adds high-frequency details (coefficients 16-63)
This allows decoders to render a usable image after receiving just the first pass, then progressively refine it as more data arrives.
- Pros: Faster perceived loading, better user experience for slow connections
- Cons: Slightly larger file size (typically 5-15% overhead) due to pass structure
- Best for: Web delivery, progressive image loading, streaming scenarios
- Avoid for: Archival storage where file size is critical
Responsive encoding provides quality-layered progressive delivery, allowing images to be decoded at progressively higher quality levels. Unlike progressive encoding (which splits by frequency), responsive encoding splits by quantization quality, making it ideal for adaptive streaming and bandwidth-constrained environments.
// Enable responsive encoding with 3 quality layers
let responsiveOptions = EncodingOptions(
mode: .lossy(quality: 90),
responsiveEncoding: true,
responsiveConfig: .threeLayers // Preview → Medium → Full quality
)
let encoder = JXLEncoder(options: responsiveOptions)
let result = try encoder.encode(frame)Responsive encoding generates multiple quality layers with different distance (quantization) values:
- Layer 0 (Preview): High distance (~6× base) - fast loading, low quality preview
- Layer 1+ (Refinement): Progressively lower distances - incremental quality improvements
- Final Layer: Base distance - target quality
For quality 90 (distance ~1.0):
- Layer 0: distance 6.0 (quick preview)
- Layer 1: distance 2.45 (medium quality)
- Layer 2: distance 1.0 (full quality)
// Use preset layer counts
ResponsiveConfig.twoLayers // Fast: preview + full
ResponsiveConfig.threeLayers // Balanced: preview + medium + full (default)
ResponsiveConfig.fourLayers // Maximum refinement
// Custom layer count (2-8 layers)
let config = ResponsiveConfig(layerCount: 4)
// Custom distance values for precise control
let customConfig = ResponsiveConfig(
layerCount: 3,
layerDistances: [8.0, 4.0, 1.5] // Must be descending order
)You can combine both encoding modes for maximum flexibility:
let options = EncodingOptions(
mode: .lossy(quality: 95),
progressive: true, // Frequency-based passes (DC, low-freq, high-freq)
responsiveEncoding: true, // Quality-based layers
responsiveConfig: .threeLayers
)- Pros: Adaptive quality delivery, better UX on variable bandwidth, graceful degradation
- Cons: Minimal overhead (<3%), requires decoder support for multi-layer decoding
- Best for: Responsive web design, adaptive streaming, bandwidth-sensitive applications
- Current status: Framework complete, full bitstream encoding requires decoder support
# Enable responsive encoding with default 3 layers
jxl-tool encode --responsive input.png -o output.jxl
# Specify custom layer count
jxl-tool encode --responsive --quality-layers 4 input.png -o output.jxl
# Combine with progressive
jxl-tool encode --responsive --progressive -q 95 input.png -o output.jxlJXLSwift fully supports EXIF orientation metadata, allowing proper handling of rotated and flipped images from cameras and smartphones. The orientation is preserved in the JPEG XL file and can be used by viewers to display images correctly without modifying pixel data.
// Create a frame with specific orientation
let frame = ImageFrame(
width: 1920,
height: 1080,
channels: 3,
orientation: 6 // 90° clockwise rotation
)
// Encode with orientation metadata
let encoder = JXLEncoder()
let result = try encoder.encode(frame)| Value | Transform | Description |
|---|---|---|
| 1 | None | Normal (no rotation) |
| 2 | Flip horizontal | Mirror image |
| 3 | Rotate 180° | Upside-down |
| 4 | Flip vertical | Vertical mirror |
| 5 | Rotate 270° + flip H | Transpose |
| 6 | Rotate 90° CW | 90° clockwise |
| 7 | Rotate 90° + flip H | Transverse |
| 8 | Rotate 270° CW | 270° clockwise |
import JXLSwift
// Parse orientation from EXIF data
let exifData = Data(...) // Raw EXIF from JPEG/PNG/TIFF
let orientation = EXIFOrientation.extractOrientation(from: exifData)
// Create frame with extracted orientation
let frame = ImageFrame(
width: width,
height: height,
channels: 3,
orientation: orientation
)# Encode with specific orientation
swift run jxl-tool encode --orientation 6 --width 1920 --height 1080 -o output.jxl
# Orientation is preserved in the encoded file
swift run jxl-tool info output.jxllet caps = HardwareCapabilities.shared
print("Running on: \(CPUArchitecture.current)")
print("ARM NEON: \(caps.hasNEON)")
print("Apple Accelerate: \(caps.hasAccelerate)")
print("Metal: \(caps.hasMetal)")
print("CPU cores: \(caps.coreCount)")// Display P3 (wide gamut)
var displayP3Frame = ImageFrame(
width: 1920,
height: 1080,
channels: 3,
pixelType: .uint16,
colorSpace: .displayP3, // Display P3 with sRGB transfer function
bitsPerSample: 10
)
// HDR10 (Rec. 2020 with PQ transfer function)
var hdr10Frame = ImageFrame(
width: 3840,
height: 2160,
channels: 3,
pixelType: .float32, // HDR typically uses float
colorSpace: .rec2020PQ, // Rec. 2020 primaries with PQ (Perceptual Quantizer)
bitsPerSample: 16
)
// HLG HDR (Rec. 2020 with HLG transfer function)
var hlgFrame = ImageFrame(
width: 3840,
height: 2160,
channels: 3,
pixelType: .uint16,
colorSpace: .rec2020HLG, // Rec. 2020 primaries with HLG (Hybrid Log-Gamma)
bitsPerSample: 10
)// RGBA with straight (unassociated) alpha
var rgbaFrame = ImageFrame(
width: 1920,
height: 1080,
channels: 4, // RGB + Alpha
pixelType: .uint8,
colorSpace: .sRGB,
hasAlpha: true,
alphaMode: .straight // RGB values independent of alpha
)
// Set pixel with alpha
rgbaFrame.setPixel(x: 100, y: 100, channel: 0, value: 255) // R
rgbaFrame.setPixel(x: 100, y: 100, channel: 1, value: 128) // G
rgbaFrame.setPixel(x: 100, y: 100, channel: 2, value: 64) // B
rgbaFrame.setPixel(x: 100, y: 100, channel: 3, value: 128) // A (50% transparent)
// RGBA with premultiplied alpha
var premultFrame = ImageFrame(
width: 1920,
height: 1080,
channels: 4,
pixelType: .uint16,
colorSpace: .displayP3,
hasAlpha: true,
alphaMode: .premultiplied // RGB already multiplied by alpha
)JXLSwift supports encoding animated JPEG XL files with multiple frames, frame timing, and loop controls. This is ideal for animated images, sequences, and video-like content.
// Create animation frames
var frames: [ImageFrame] = []
for i in 0..<30 {
var frame = ImageFrame(width: 512, height: 512, channels: 3)
// Populate frame with animation data...
frames.append(frame)
}
// Configure animation settings
let animConfig = AnimationConfig(
fps: 30, // 30 frames per second
loopCount: 0 // 0 = infinite loop
)
// Create encoder with animation config
let options = EncodingOptions(
mode: .lossy(quality: 90),
effort: .falcon,
animationConfig: animConfig
)
let encoder = JXLEncoder(options: options)
// Encode all frames as animation
let result = try encoder.encode(frames)// Different frame rates
let fps24 = AnimationConfig.fps24 // Cinematic 24 FPS
let fps30 = AnimationConfig.fps30 // Standard 30 FPS
let fps60 = AnimationConfig.fps60 // Smooth 60 FPS
// Finite loop count
let loopThrice = AnimationConfig(fps: 30, loopCount: 3)
// Custom frame durations (in ticks, 1000 ticks per second)
let customDurations = AnimationConfig(
fps: 30,
frameDurations: [100, 200, 150, 300] // Different duration per frame
)- Frame Rate Control: Set FPS from 1 to any desired rate
- Loop Control: Infinite loop or specific repeat count
- Custom Timing: Different duration for each frame
- All Pixel Types: Supports uint8, uint16, and float32
- Alpha Channel: Full RGBA animation support
- Progressive: Combine with progressive encoding for streaming
- HDR/Wide Gamut: Animate HDR or wide color gamut content
- Pros: Native format support, better compression than GIF/APNG
- Cons: Larger than single frame, decoder support varies
- Best for: Web animations, UI sequences, short video clips
- Avoid for: Long videos (use proper video codecs)
For animations with temporal coherence or repeated content, JXLSwift provides reference frame encoding and patch encoding to dramatically reduce file size.
Reference frames mark certain frames as keyframes that subsequent frames can reference, enabling delta encoding.
// Create animation frames with similar content
var frames: [ImageFrame] = []
for i in 0..<100 {
var frame = ImageFrame(width: 256, height: 256, channels: 3)
// Populate with content that changes gradually...
frames.append(frame)
}
// Configure animation with reference frame encoding
let animConfig = AnimationConfig.fps30
let refConfig = ReferenceFrameConfig.balanced // Keyframe every 30 frames
let options = EncodingOptions(
mode: .lossy(quality: 90),
animationConfig: animConfig,
referenceFrameConfig: refConfig
)
let encoder = JXLEncoder(options: options)
let result = try encoder.encode(frames)Reference Frame Presets:
let aggressive = ReferenceFrameConfig.aggressive // Keyframe every 60 frames, max compression
let balanced = ReferenceFrameConfig.balanced // Keyframe every 30 frames, default
let conservative = ReferenceFrameConfig.conservative // Keyframe every 15 frames, faster seekingPatch encoding goes further by detecting and copying identical or very similar rectangular regions from reference frames, eliminating redundant encoding. This is especially effective for screen content, slideshows, and animations with static UI elements.
// Enable both reference frames and patches
let animConfig = AnimationConfig.fps30
let refConfig = ReferenceFrameConfig.balanced
let patchConfig = PatchConfig.screenContent // Optimized for UI/screen content
let options = EncodingOptions(
mode: .lossy(quality: 90),
animationConfig: animConfig,
referenceFrameConfig: refConfig,
patchConfig: patchConfig // Enable patch copying
)
let encoder = JXLEncoder(options: options)
let result = try encoder.encode(frames)Patch Encoding Presets:
let aggressive = PatchConfig.aggressive // Small patches, max compression
let balanced = PatchConfig.balanced // Default, good for most content
let conservative = PatchConfig.conservative // Larger patches, quality-focused
let screenContent = PatchConfig.screenContent // Optimized for UI, presentations, screen capturesUse Cases:
- Screen recordings: UI elements remain static across frames
- Slideshows: Large portions of slides repeat between transitions
- Video calls: Static backgrounds save significant bandwidth
- Game recordings: UI overlays and HUD elements are repeated
- Presentation videos: Title cards and repeated graphics
Performance:
- Can achieve 70-90% compression for screen content vs. reference frames alone
- 50-80% compression for presentation-style content
- 20-40% additional compression for video with static elements
CLI Usage:
# Reference frames only
jxl-tool encode frames/*.png --reference-frames --keyframe-interval 30 -o animation.jxl
# Reference frames + patches (balanced)
jxl-tool encode frames/*.png --reference-frames --patches -o animation.jxl
# Screen content optimization
jxl-tool encode screencast/*.png --reference-frames --patches --patch-preset screen -o screencast.jxl
# Conservative patches for quality-critical work
jxl-tool encode slides/*.png --reference-frames --patches --patch-preset conservative -o slides.jxlimport JXLSwift
// Decode a JPEG XL codestream
let jxlData = try Data(contentsOf: URL(fileURLWithPath: "image.jxl"))
let decoder = JXLDecoder()
// Handles both bare codestream and container formats
let codestream = try decoder.extractCodestream(jxlData)
// Parse the image header
let header = try decoder.parseImageHeader(codestream)
print("Image: \(header.width)×\(header.height), \(header.channels) channels")
// Decode to an ImageFrame (lossless modular or lossy VarDCT mode)
let frame = try decoder.decode(codestream)
let pixel = frame.getPixel(x: 0, y: 0, channel: 0)JXLSwift supports progressive decoding for VarDCT (lossy) images, allowing incremental image reconstruction:
import JXLSwift
let jxlData = try Data(contentsOf: URL(fileURLWithPath: "progressive.jxl"))
let decoder = JXLDecoder()
// Decode progressively with callback for each pass
try decoder.decodeProgressive(jxlData) { pass, frame in
print("Pass \(pass): \(frame.width)×\(frame.height)")
// Update UI with progressive refinement
// Pass 0: DC-only (8×8 blocks)
// Pass 1: Low-frequency AC coefficients (1-15)
// Pass 2: High-frequency AC coefficients (16-63)
}Progressive decoding enables:
- Faster perceived loading - Display low-resolution preview immediately
- Adaptive streaming - Stop at desired quality level
- Bandwidth optimization - Load only what's needed
import JXLSwift
let jxlData = try Data(contentsOf: URL(fileURLWithPath: "image.jxl"))
let decoder = JXLDecoder()
// Parse the container and extract all metadata
let container = try decoder.parseContainer(jxlData)
if let exif = container.exif {
let orientation = EXIFOrientation.extractOrientation(from: exif.data)
print("EXIF orientation: \(orientation)")
}
if let xmp = container.xmp {
let xml = String(data: xmp.data, encoding: .utf8) ?? ""
print("XMP: \(xml.prefix(100))…")
}
if let icc = container.iccProfile {
print("ICC profile: \(icc.data.count) bytes")
}
// Or use the convenience method
let (exif, xmp, iccProfile) = try decoder.extractMetadata(jxlData)import JXLSwift
// Decode a JPEG XL file
let jxlData = try Data(contentsOf: URL(fileURLWithPath: "image.jxl"))
let decoder = JXLDecoder()
let codestream = try decoder.extractCodestream(jxlData)
let frame = try decoder.decode(codestream)
// Export to PNG data
let pngData = try ImageExporter.export(frame, format: .png)
// Export to a file (format auto-detected from extension)
try ImageExporter.export(frame, to: URL(fileURLWithPath: "output.png"))
try ImageExporter.export(frame, to: URL(fileURLWithPath: "output.tiff"))
try ImageExporter.export(frame, to: URL(fileURLWithPath: "output.bmp"))
// Or specify format explicitly
try ImageExporter.export(frame, to: URL(fileURLWithPath: "image.dat"), format: .tiff)
// Convert planar pixel data to interleaved format (available on all platforms)
let (interleaved, bytesPerComponent, componentCount) = try PixelConversion.interleave(frame)JXLSwift adopts the Raster-Lab shared codec protocols so that code written
against RasterImageEncoder and RasterImageDecoder works with both JXLSwift
and J2KSwift (JPEG 2000) with minimal changes.
import JXLSwift
// Store encoder/decoder as protocol existential
let encoder: any RasterImageEncoder = JXLEncoder(options: .highQuality)
let decoder: any RasterImageDecoder = JXLDecoder()
// Encode — returns raw Data (same signature as J2KEncoder)
let data: Data = try encoder.encode(frame: myFrame)
let animData: Data = try encoder.encode(frames: [frame1, frame2])
// Decode — returns ImageFrame (same signature as J2KDecoder)
let decoded: ImageFrame = try decoder.decode(data: data)For richer results (including CompressionStats), use the concrete JXLEncoder
API directly:
let encoder = JXLEncoder(options: .highQuality)
let result: EncodedImage = try encoder.encode(myFrame)
print("Compression ratio: \(result.stats.compressionRatio)×")See Documentation/J2KSWIFT_MIGRATION.md for the full API comparison and migration guide.
The library is organized into several modules:
Sources/JXLSwift/
├── Core/ # Fundamental data structures
│ ├── Architecture.swift # CPU detection & capabilities
│ ├── ImageFrame.swift # Image representation
│ ├── PixelBuffer.swift # Tiled pixel buffer access
│ ├── Bitstream.swift # Bitstream I/O
│ ├── EncodingOptions.swift # Configuration
│ ├── QualityMetrics.swift # PSNR, SSIM, MS-SSIM, Butteraugli metrics
│ ├── ValidationHarness.swift # Encoding validation test harness
│ ├── BitstreamValidator.swift # Bitstream compatibility validation (structural + libjxl)
│ ├── BenchmarkReport.swift # JSON/HTML benchmark report generation
│ └── ComparisonBenchmark.swift # Speed, compression, memory comparison & test corpus
├── Encoding/ # Compression pipeline
│ ├── Encoder.swift # Main encoder interface
│ ├── Decoder.swift # Main decoder interface (JXLDecoder)
│ ├── ModularEncoder.swift # Lossless compression (with subbitstream framing)
│ ├── ModularDecoder.swift # Lossless decompression (round-trip support)
│ ├── VarDCTEncoder.swift # Lossy compression
│ ├── VarDCTDecoder.swift # Lossy decompression (VarDCT round-trip support)
│ └── ANSEncoder.swift # rANS entropy coding (ISO/IEC 18181-1 Annex A)
├── Export/ # Image format export
│ └── ImageExporter.swift # PNG, TIFF, BMP export via CoreGraphics/ImageIO
├── Hardware/ # Platform optimizations
│ ├── Accelerate.swift # Apple Accelerate framework (vDSP)
│ ├── NEONOps.swift # ARM NEON SIMD via Swift SIMD types
│ ├── DispatchBackend.swift # Runtime backend selection
│ ├── GPUCompute.swift # Cross-platform GPU abstraction (Metal ↔ Vulkan)
│ ├── x86/ # Intel x86-64 SIMD (#if arch(x86_64) guarded)
│ │ ├── SSEOps.swift # SSE2 4-wide operations (DCT, colour conversion, etc.)
│ │ └── AVXOps.swift # AVX2 8-wide operations (wider DCT, colour conversion)
│ ├── Metal/ # Apple GPU (#if canImport(Metal) guarded)
│ │ ├── MetalOps.swift # Metal device/buffer management
│ │ ├── MetalCompute.swift # Metal compute pipeline (DCT, colour conversion, quantisation)
│ │ └── Shaders.metal # MSL compute shaders
│ └── Vulkan/ # Linux/Windows GPU (#if canImport(Vulkan) guarded)
│ ├── VulkanOps.swift # Vulkan device/buffer/pipeline management
│ ├── VulkanCompute.swift# Vulkan compute operations (DCT, colour conversion, quantisation)
│ └── Shaders.comp # GLSL compute shaders (compiled to SPIR-V with glslc)
└── Format/ # JPEG XL file format (ISO/IEC 18181-2)
├── CodestreamHeader.swift # SizeHeader, ImageMetadata, ColourEncoding
├── FrameHeader.swift # Frame header, section/group framing
└── JXLContainer.swift # ISOBMFF container, metadata boxes
Sources/JXLTool/
├── JXLTool.swift # CLI entry point
├── Encode.swift # Encode subcommand
├── Decode.swift # Decode subcommand
├── Info.swift # Info subcommand
├── Hardware.swift # Hardware subcommand
├── Benchmark.swift # Benchmark subcommand
├── Batch.swift # Batch subcommand
├── Compare.swift # Compare subcommand
├── Validate.swift # Validate subcommand (quality, compression, performance)
└── Utilities.swift # Shared CLI helpers
JXLSwift is optimized for Apple Silicon:
- ARM NEON SIMD - Vectorized DCT, colour conversion, quantisation, prediction, RCT, and squeeze transforms via Swift SIMD types (both Modular and VarDCT pipelines)
- Intel SSE2/AVX2 - Parallel x86-64 code paths: SSE2 4-wide operations in
SSEOps, AVX2 8-wide DCT and colour conversion inAVXOps; runtime AVX2 detection - Apple Accelerate - vDSP DCT transforms and matrix operations; vImage high-quality Lanczos resampling
- Metal GPU - Parallel block processing with compute shaders for DCT, color conversion, and quantization (batch operations with async pipeline)
- Vulkan GPU - Cross-platform GPU compute for Linux and Windows via Vulkan compute shaders; mirrors Metal API shape,
#if canImport(Vulkan)guarded
Benchmarks on Apple M1 (256x256 image):
- Fast mode: ~0.7s per frame
- High quality: ~2-3s per frame
Metal GPU Acceleration:
- Automatically enabled on Apple platforms when available
- Async pipeline with double-buffering overlaps CPU and GPU work for improved throughput
- Best suited for batch processing of multiple images or large images (32+ blocks)
- Falls back to CPU (Accelerate/NEON/scalar) for small workloads
- Control via
EncodingOptions.useMetalflag
- Perfect pixel-by-pixel reproduction
- Uses predictive coding + entropy encoding
- Full subbitstream framing per ISO/IEC 18181-1 §7 with global + per-channel sections
- Round-trip encode → decode support via
ModularDecoder - NEON-accelerated MED prediction, RCT, and squeeze transforms on ARM64
- Ideal for archival, medical imaging, scientific data
- High-quality lossy compression
- Uses DCT transforms, quantization, and entropy coding
- Variable block sizes (8×8, 16×16, 32×32, 16×8, 8×16, etc.) with content-adaptive selection
- Natural order coefficient scanning per JPEG XL spec
- Full VarDCT frame header per ISO/IEC 18181-1 §6
- Ideal for photographs, web delivery, general use
Encoding effort controls the quality/speed tradeoff:
- Lightning - Fastest (minimal compression)
- Thunder
- Falcon - Fast preset
- Cheetah
- Hare
- Wombat
- Squirrel - Default (balanced)
- Kitten - High quality preset
- Tortoise - Maximum compression (slowest)
Region-of-Interest encoding allows you to encode a specific rectangular area at higher quality than the rest of the image. This is particularly useful for:
- Reducing file size by compressing less important areas more aggressively
- Highlighting specific parts of an image (faces, subjects, etc.)
- Creating focal point encoding for attention guidance
// Define a region of interest (center 200×200 region)
let roi = RegionOfInterest(
x: 100, // Top-left X coordinate
y: 100, // Top-left Y coordinate
width: 200, // Width of the ROI
height: 200, // Height of the ROI
qualityBoost: 15.0, // Quality improvement (0-50)
featherWidth: 16 // Transition width in pixels
)
let options = EncodingOptions(
mode: .lossy(quality: 80), // Base quality for non-ROI areas
effort: .squirrel,
regionOfInterest: roi
)
let encoder = JXLEncoder(options: options)
let result = try encoder.encode(frame)ROI encoding varies the quantization distance on a per-block basis:
- Inside ROI: Lower distance = higher quality (e.g., quality boost of 10 ≈ 0.91× distance multiplier)
- Outside ROI: Normal distance = base quality
- Feather Zone: Smooth transition using cosine interpolation for seamless quality gradients
- qualityBoost (0-50, default 10): Quality improvement for ROI region
- 10 = approximately 10% higher quality
- 20 = approximately 16.7% higher quality
- 50 = approximately 33.3% higher quality (maximum)
- featherWidth (pixels, default 16): Width of smooth transition at ROI edges
- 0 = hard edge (abrupt quality change)
- 8-16 = subtle transition (recommended)
- 32+ = very gradual transition
# Encode with ROI in center region
swift run jxl-tool encode --width 512 --height 512 \\
--roi 128,128,256,256 \\
--roi-quality-boost 20 \\
--roi-feather 16 \\
-o output.jxl
# Corner ROI with sharp edges
swift run jxl-tool encode --width 800 --height 600 \\
--roi 0,0,200,200 \\
--roi-quality-boost 15 \\
--roi-feather 0 \\
-o corner_roi.jxl- Pros: Smaller file size, maintains detail where it matters, guides viewer attention
- Cons: Potential visible quality boundaries (mitigated by feathering)
- Best for: Portrait photography, product shots, documents with focal areas
- Note: Only applies to lossy (VarDCT) encoding; ignored for lossless mode
For lossy encoding, quality ranges from 0-100:
- 90-100: Excellent quality, minimal artifacts
- 80-89: Very good quality, some compression
- 70-79: Good quality, noticeable compression
- 60-69: Acceptable quality, visible artifacts
- Below 60: Low quality, significant artifacts
The library maintains separate code paths for different architectures:
#if arch(arm64)
// Apple Silicon / ARM optimizations
return applyDCTNEON(block: block)
#elseif arch(x86_64)
// x86-64 fallback implementation
return applyDCTScalar(block: block)
#endifThis design allows easy removal of x86-64 code in the future if desired.
JXLSwift includes a command line tool jxl-tool for encoding and inspecting JPEG XL files:
# Encode a test image
swift run jxl-tool encode --quality 90 --effort 7 -o output.jxl
# Lossless encoding
swift run jxl-tool encode --lossless -o output.jxl
# Batch encode a directory
swift run jxl-tool batch /path/to/images --recursive -o /path/to/output
# Display hardware capabilities
swift run jxl-tool hardware
# Inspect a JPEG XL file
swift run jxl-tool info output.jxl
# Compare two JPEG XL files
swift run jxl-tool compare file1.jxl file2.jxl
# Run performance benchmarks
swift run jxl-tool benchmark --width 512 --height 512
# Compare ANS vs simplified entropy encoding
swift run jxl-tool benchmark --compare-entropy
# Compare hardware acceleration vs scalar
swift run jxl-tool benchmark --compare-hardware
# Compare Metal GPU vs CPU acceleration
swift run jxl-tool benchmark --compare-metal
# Validate encoding quality and performance
swift run jxl-tool validate --width 64 --height 64
# Validate with JSON report output
swift run jxl-tool validate --format json --output report.json
# Validate with HTML report output
swift run jxl-tool validate --format html --output report.html --include-lossless
# Validate with quality metrics comparison
swift run jxl-tool validate --quality-metrics --all-efforts
# Validate bitstream compatibility (structural checks + libjxl decode if available)
swift run jxl-tool validate --bitstream-compat
# Validate bitstream compatibility (structural checks only, skip libjxl)
swift run jxl-tool validate --bitstream-compat --skip-libjxlAfter installation, comprehensive man pages are available:
man jxl-tool # Main tool overview
man jxl-tool-encode # Encode subcommand
man jxl-tool-benchmark # Benchmark subcommand
# ... and moreThe tool follows standard UNIX exit code conventions:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (runtime failure) |
| 2 | Invalid arguments |
See MILESTONES.md for the detailed project milestone plan.
- Core compression pipeline
- Lossless (Modular) mode — MED, RCT, Squeeze, MA tree, context modeling, subbitstream framing (§7), round-trip decode
- Lossy (VarDCT) mode — DCT, XYB, CfL, adaptive quantization, DC prediction, variable block sizes, natural order scanning, full frame header (§6)
- Apple Silicon optimization
- Accelerate framework integration — vDSP DCT, vectorized color/quantization, vImage Lanczos resampling
- ARM NEON SIMD acceleration — portable Swift SIMD types, DCT, colour conversion, quantisation, MED prediction, RCT, squeeze (Modular + VarDCT)
- Metal GPU acceleration — compute shaders for DCT, color conversion, quantization (batch operations)
- Command line tool (jxl-tool) — encode, info, hardware, benchmark, batch, compare, validate
- JPEG XL file format (.jxl) — ISOBMFF container, codestream/frame headers
- Metadata support (EXIF, XMP, ICC profiles)
- Animation container framing (frame index, multi-frame)
- ANS entropy coding — rANS encoder/decoder, multi-context, distribution tables, histogram clustering, ANS interleaving, LZ77 hybrid mode, integrated with Modular + VarDCT
- Man pages for jxl-tool and all subcommands
- Makefile for build, test, and installation
- Advanced features — HDR support (PQ, HLG), wide gamut (Display P3, Rec. 2020), alpha channels (straight, premultiplied), EXIF orientation (all 8 values), extra channels (depth, thermal, spectral)
- Metal GPU async pipeline with double-buffering — overlapping CPU and GPU work for improved performance
- Progressive encoding — frequency-based multi-pass (DC, low-freq AC, high-freq AC)
- Responsive encoding — quality-layered progressive delivery (2-8 layers)
- Multi-frame animation encoding — frame timing, loop control, custom durations
- EXIF orientation support — reading, encoding, and CLI integration
- Region-of-Interest (ROI) encoding — selective quality with configurable feathering
- Reference frame encoding — delta encoding for animations with configurable keyframe intervals
- Patch encoding — copy repeated rectangular regions from reference frames for screen content
- Noise synthesis — add film grain or synthetic noise to mask quantization artifacts
- Spline encoding — vector overlay rendering for smooth curves and line art
- libjxl Validation & Benchmarking — quality metrics (PSNR, SSIM, MS-SSIM, Butteraugli), validation harness with configurable criteria, benchmark reports (JSON/HTML), performance regression detection (10% threshold alerting), validate CLI subcommand, test image generator, speed comparison across effort levels, compression ratio comparison across quality levels, memory usage comparison with process-level tracking, test image corpus (Kodak-like, Tecnick-like, Wikipedia-like), bitstream compatibility validation (structural checks + libjxl decode verification)
- Decoding support —
JXLDecoder(codestream/frame header parsing, container extraction, Modular + VarDCT decode, metadata extraction),decodeCLI subcommand with--metadataflag - Image export —
ImageExporterwith PNG, TIFF, BMP output via CoreGraphics/ImageIO,PixelConversionplanar→interleaved, CLI--formatoption - Production hardening — fuzzing tests (51 tests), thread safety tests (51 tests), code coverage reporting in CI, migration guide, performance tuning guide, CHANGELOG.md, VERSION file, DocC API documentation, memory safety validation (ASan, TSan, UBSan), security scanning (CodeQL), v1.0.0 release infrastructure
- ISO/IEC 18181-3 Conformance Testing (in progress) —
ConformanceRunnerwith 17 synthetic test vectors, bitstream structure checks (§6), image header checks (§11), frame header checks (§9), container format checks (Part 2 §3), lossless round-trip checks, lossy round-trip checks, bidirectional libjxl interoperability (conditional),ConformanceReportwith per-category pass/fail, CIconformancejob - Intel x86-64 SIMD Optimisation (SSE/AVX) —
SSEOpswith SSE2 4-wide operations (DCT, colour conversion, quantisation, MED prediction, RCT, squeeze) inHardware/x86/SSEOps.swift;AVXOpswith AVX2 8-wide DCT and colour conversion inHardware/x86/AVXOps.swift; runtime AVX2 CPU detection;#if arch(x86_64)dispatch in VarDCTEncoder and ModularEncoder with scalar#elsefallback - Vulkan GPU Compute (Linux/Windows) —
VulkanOps(device/queue/buffer management) andVulkanCompute(DCT, colour conversion, quantisation, async pipeline) inHardware/Vulkan/, all#if canImport(Vulkan)guarded;GPUComputecross-platform abstraction layer routes to Metal on Apple and Vulkan on Linux/Windows;hasVulkanfield added toHardwareCapabilities; Vulkan dispatch path added toVarDCTEncoder; GLSL compute shaders inShaders.comp;VulkanBufferPoolandVulkanAsyncPipelinefor double-buffered GPU execution - DICOM Awareness (DICOM-Independent) —
PixelType.int16for signed 16-bit Hounsfield units;getPixelSigned/setPixelSigned/getPixelFloat/setPixelFloataccessors;PhotometricInterpretationenum (MONOCHROME1/MONOCHROME2/RGB/YCbCr);WindowLevelwith built-in CT presets;MedicalImageMetadatapassthrough struct;MedicalImageValidatorwith dimension/bit-depth/channel checks;MedicalImageSeriesfor CT/MR stacks; convenience initialisersmedical12bit,medical16bit,medicalSigned16bit;EncodingOptions.medicalLosslesspreset; DICOM integration guide (Documentation/DICOM_INTEGRATION.md); zero DICOM dependencies - Internationalisation & Spelling Support — British English throughout all comments, help text, and error messages; dual-spelling CLI options (
--colour-space/--color-space,--optimise/--optimize) on theencodesubcommand;ColourPrimariestype alias alongsideColorPrimaries;scripts/check-spelling.shspelling consistency checker with CI integration; British English style guide inCONTRIBUTING.md - J2KSwift API Consistency —
RasterImageEncoder,RasterImageDecoder, andRasterImageCodecshared protocols;JXLEncoderconforms viaencode(frame:)/encode(frames:);JXLDecoderconforms viadecode(data:); migration guide (Documentation/J2KSWIFT_MIGRATION.md) for cross-library developers - Documentation & Examples Refresh — 16 working example scripts in
Examples/(lossless, lossy, decoding, progressive, animation, alpha, extra channels, HDR, ROI, patches, noise, splines, hardware detection, DICOM, batch processing, benchmarking); refreshedTECHNICAL.md,CONTRIBUTING.md, andCHANGELOG.md;ExamplesTests.swiftwith 18 tests verifying example logic - Performance: Exceeding libjxl —
PerformanceProfilerwith stage-level timing (colour conversion, DCT, IDCT, quantisation, entropy coding, CfL, noise, splines, patches, frame header, bitstream write),ProfilingReportwith throughput (MP/s) and stage share,PerformanceRegressionGate(10% threshold),EncoderBufferPool<T>thread-safe reusable buffer pool with best-fit free list,SharedEncodingPools(float/byte/int32),WorkStealingThreadPoolwith per-thread deques and work-stealing for multi-core scaling,SharedThreadPoolsingleton, expandedAccelerateOps(vectorised clamp, abs, sum, normalise, interleaved U8→YCbCr), expandedNEONOps(SIMD quantisation, horizontal reductions), Metal GPU pipeline optimisation (adaptive batch size, occupancy analysis, page-aligned coalesced buffers); 70 tests inMilestone21Tests.swift
This implementation follows the JPEG XL specification:
- ISO/IEC 18181-1:2024 - Core coding system
- ISO/IEC 18181-2:2021 - File format (ISOBMFF container)
- ISO/IEC 18181-3:2024 - Conformance testing (in progress)
- Supports both encoding and decoding (Modular + VarDCT)
MIT License - See LICENSE file for details
Run the test suite with:
swift testFor complete bitstream compatibility testing, install the reference libjxl implementation. JXLSwift includes 11 additional tests that verify bidirectional compatibility:
- Decode libjxl-encoded files - Tests JXLSwift decoder against files produced by
cjxl - Encode for libjxl - Tests that
djxlcan decode JXLSwift-encoded files - Quality validation - Verifies PSNR thresholds for lossy compression
- Format validation - Tests various image sizes and encoding modes
macOS (via Homebrew):
brew install jpeg-xlUbuntu/Debian:
sudo apt-get install libjxl-toolsBuilding from source:
git clone https://github.com/libjxl/libjxl.git
cd libjxl
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF ..
cmake --build . --target cjxl djxl
sudo cmake --install .After installation, the libjxl compatibility tests will automatically run with the full test suite. Tests gracefully skip if libjxl is not available.
JXLSwift maintains 95%+ code coverage on all public and internal APIs. Generate coverage reports with:
# Text-based coverage summary
make coverage
# HTML coverage report with line-by-line analysis
make coverage-htmlFor detailed information on coverage verification, see Documentation/COVERAGE.md.
Contributions welcome! Please ensure:
- Code follows Swift style guidelines
- Tests pass on both ARM64 and x86-64
- Performance-critical code has benchmarks
- Hardware-specific code is properly isolated
This is a reference implementation created for educational and research purposes. For production use, consider the official libjxl C++ implementation.