Skip to content

Raster-Lab/JXLSwift

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

157 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JXLSwift

CI Swift 6.2 Platforms License: MIT SPM Compatible

A native Swift implementation of the JPEG XL (ISO/IEC 18181) compression codec, optimized for Apple Silicon hardware.

Overview

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.

Features

  • 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 GPUCompute abstraction
  • 📦 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

Requirements

  • 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

Installation

Swift Package Manager

Add JXLSwift to your Package.swift:

dependencies: [
    .package(url: "https://github.com/Raster-Lab/JXLSwift.git", from: "0.1.0")
]

Command Line Tool Installation

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-man

The Makefile provides several targets:

  • make build - Build the project in release mode
  • make test - Run all tests
  • make coverage - Generate code coverage report
  • make coverage-html - Generate HTML code coverage report
  • make man - Generate man pages
  • make docc - Generate API documentation with DocC
  • make docc-html - Generate HTML API documentation
  • make docc-preview - Preview API documentation in browser
  • make install - Install jxl-tool binary
  • make install-man - Install man pages
  • make uninstall - Remove installed files
  • make clean - Clean build artifacts
  • make help - Show help message

Usage

Basic Encoding

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")

Alpha Channel Support

// 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 Compression

// Lossless (Modular) mode - bit-perfect preservation
let options = EncodingOptions(mode: .lossless)
let encoder = JXLEncoder(options: options)
let result = try encoder.encode(frame)

Lossy Compression

let encoder = JXLEncoder(options: .lossless)
let result = try encoder.encode(frame)

High-Quality Lossy Encoding

let encoder = JXLEncoder(options: .highQuality)
let result = try encoder.encode(frame)

Encoding Effort Levels

// 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
)

Custom Configuration

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

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)

How Progressive Encoding Works

Progressive encoding splits DCT coefficients into multiple passes:

  1. Pass 0 (DC-only): Encodes only DC coefficients, providing a low-resolution 8×8 preview
  2. Pass 1 (Low-frequency AC): Adds low-frequency details (coefficients 1-15)
  3. 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.

Trade-offs

  • 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

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)

How Responsive Encoding Works

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)

Configuration Options

// 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
)

Combining Progressive and Responsive

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
)

Trade-offs

  • 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

CLI Usage

# 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.jxl

EXIF Orientation Support

JXLSwift 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)

EXIF Orientation Values

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

Extracting Orientation from EXIF Data

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
)

Command Line Tool

# 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.jxl

Hardware Capabilities Detection

let 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)")

HDR and Wide Gamut Support

// 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
)

Alpha Channel Support

// 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
)

Multi-Frame Animation Support

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)

Animation Configuration Options

// 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
)

Animation Features

  • 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

Animation Trade-offs

  • 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)

Reference Frame and Patch Encoding

For animations with temporal coherence or repeated content, JXLSwift provides reference frame encoding and patch encoding to dramatically reduce file size.

Reference Frame Encoding

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 seeking

Patch Encoding

Patch 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 captures

Use 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.jxl

Decoding (Modular & VarDCT)

import 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)

Progressive Decoding

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

Metadata Extraction (EXIF, XMP, ICC)

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)

Exporting to PNG, TIFF, BMP

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)

Cross-Library Usage (J2KSwift API Consistency)

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.

Architecture

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

Performance

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 in AVXOps; 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.useMetal flag

Compression Modes

Modular Mode (Lossless)

  • 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

VarDCT Mode (Lossy)

  • 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

Effort Levels

Encoding effort controls the quality/speed tradeoff:

  1. Lightning - Fastest (minimal compression)
  2. Thunder
  3. Falcon - Fast preset
  4. Cheetah
  5. Hare
  6. Wombat
  7. Squirrel - Default (balanced)
  8. Kitten - High quality preset
  9. Tortoise - Maximum compression (slowest)

Region-of-Interest (ROI) Encoding

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)

How ROI Encoding Works

ROI encoding varies the quantization distance on a per-block basis:

  1. Inside ROI: Lower distance = higher quality (e.g., quality boost of 10 ≈ 0.91× distance multiplier)
  2. Outside ROI: Normal distance = base quality
  3. Feather Zone: Smooth transition using cosine interpolation for seamless quality gradients

ROI Configuration Options

  • 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

CLI Example

# 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

Trade-offs

  • 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

Quality Settings

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

Platform-Specific Code

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)
#endif

This design allows easy removal of x86-64 code in the future if desired.

Command Line Tool

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-libjxl

Man Pages

After 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 more

Exit Codes

The tool follows standard UNIX exit code conventions:

Code Meaning
0 Success
1 General error (runtime failure)
2 Invalid arguments

Roadmap

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), decode CLI subcommand with --metadata flag
  • Image export — ImageExporter with PNG, TIFF, BMP output via CoreGraphics/ImageIO, PixelConversion planar→interleaved, CLI --format option
  • 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) — ConformanceRunner with 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), ConformanceReport with per-category pass/fail, CI conformance job
  • Intel x86-64 SIMD Optimisation (SSE/AVX)SSEOps with SSE2 4-wide operations (DCT, colour conversion, quantisation, MED prediction, RCT, squeeze) in Hardware/x86/SSEOps.swift; AVXOps with AVX2 8-wide DCT and colour conversion in Hardware/x86/AVXOps.swift; runtime AVX2 CPU detection; #if arch(x86_64) dispatch in VarDCTEncoder and ModularEncoder with scalar #else fallback
  • Vulkan GPU Compute (Linux/Windows)VulkanOps (device/queue/buffer management) and VulkanCompute (DCT, colour conversion, quantisation, async pipeline) in Hardware/Vulkan/, all #if canImport(Vulkan) guarded; GPUCompute cross-platform abstraction layer routes to Metal on Apple and Vulkan on Linux/Windows; hasVulkan field added to HardwareCapabilities; Vulkan dispatch path added to VarDCTEncoder; GLSL compute shaders in Shaders.comp; VulkanBufferPool and VulkanAsyncPipeline for double-buffered GPU execution
  • DICOM Awareness (DICOM-Independent)PixelType.int16 for signed 16-bit Hounsfield units; getPixelSigned/setPixelSigned/getPixelFloat/setPixelFloat accessors; PhotometricInterpretation enum (MONOCHROME1/MONOCHROME2/RGB/YCbCr); WindowLevel with built-in CT presets; MedicalImageMetadata passthrough struct; MedicalImageValidator with dimension/bit-depth/channel checks; MedicalImageSeries for CT/MR stacks; convenience initialisers medical12bit, medical16bit, medicalSigned16bit; EncodingOptions.medicalLossless preset; 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 the encode subcommand; ColourPrimaries type alias alongside ColorPrimaries; scripts/check-spelling.sh spelling consistency checker with CI integration; British English style guide in CONTRIBUTING.md
  • J2KSwift API ConsistencyRasterImageEncoder, RasterImageDecoder, and RasterImageCodec shared protocols; JXLEncoder conforms via encode(frame:)/encode(frames:); JXLDecoder conforms via decode(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); refreshed TECHNICAL.md, CONTRIBUTING.md, and CHANGELOG.md; ExamplesTests.swift with 18 tests verifying example logic
  • Performance: Exceeding libjxlPerformanceProfiler with stage-level timing (colour conversion, DCT, IDCT, quantisation, entropy coding, CfL, noise, splines, patches, frame header, bitstream write), ProfilingReport with 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), WorkStealingThreadPool with per-thread deques and work-stealing for multi-core scaling, SharedThreadPool singleton, expanded AccelerateOps (vectorised clamp, abs, sum, normalise, interleaved U8→YCbCr), expanded NEONOps (SIMD quantisation, horizontal reductions), Metal GPU pipeline optimisation (adaptive batch size, occupancy analysis, page-aligned coalesced buffers); 70 tests in Milestone21Tests.swift

Standards Compliance

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)

License

MIT License - See LICENSE file for details

Testing

Run the test suite with:

swift test

Full Test Coverage with libjxl

For 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 djxl can decode JXLSwift-encoded files
  • Quality validation - Verifies PSNR thresholds for lossy compression
  • Format validation - Tests various image sizes and encoding modes

Installing libjxl

macOS (via Homebrew):

brew install jpeg-xl

Ubuntu/Debian:

sudo apt-get install libjxl-tools

Building 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.

Code Coverage

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-html

For detailed information on coverage verification, see Documentation/COVERAGE.md.

Contributing

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

References

Acknowledgments

This is a reference implementation created for educational and research purposes. For production use, consider the official libjxl C++ implementation.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages