A quick guide to get you up and running with JPEG-LS compression using JLSwift.
Add JLSwift to your Package.swift:
dependencies: [
.package(url: "https://github.com/Raster-Lab/JLSwift.git", from: "0.1.0")
]Then add the library to your target:
.target(
name: "YourTarget",
dependencies: ["JPEGLS"]
)git clone https://github.com/Raster-Lab/JLSwift.git
cd JLSwift
swift buildimport JPEGLS
// 1. Prepare your image data as a 2D array
let pixels: [[Int]] = [
[100, 110, 120, 130],
[105, 115, 125, 135],
[110, 120, 130, 140],
[115, 125, 135, 145]
]
// 2. Create image data structure
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// 3. Create the encoder and encode (lossless by default)
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print("Encoded \(pixels.count * pixels[0].count) pixels")
print("Output size: \(jpegLSData.count) bytes")import JPEGLS
// Load your image data (example: 512×512 8-bit greyscale)
let width = 512
let height = 512
let pixels: [[Int]] = loadYourImageData() // Your image loading code
// Create greyscale image data
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// Encode lossless using the high-level encoder
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print("✓ Encoded \(width * height) pixels")
print("✓ Output: \(jpegLSData.count) bytes")import JPEGLS
// Separate colour channels (each is a 2D array)
let redPixels: [[Int]] = loadRedChannel()
let greenPixels: [[Int]] = loadGreenChannel()
let bluePixels: [[Int]] = loadBlueChannel()
// Create RGB image data
let imageData = try MultiComponentImageData.rgb(
redPixels: redPixels,
greenPixels: greenPixels,
bluePixels: bluePixels,
bitsPerSample: 8
)
// Encode lossless, sample-interleaved (recommended for RGB)
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(
near: 0,
interleaveMode: .sample
)
let jpegLSData = try encoder.encode(imageData, configuration: config)
print("✓ Encoded \(jpegLSData.count) bytes across 3 components")For lossy compression with controlled error bounds:
// Encode near-lossless (NEAR=3 means max error of ±3)
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(near: 3)
let jpegLSData = try encoder.encode(imageData, configuration: config)
// Rest of encoding is the same...import JPEGLS
// Load JPEG-LS file
let jpegLSData = try Data(contentsOf: URL(fileURLWithPath: "image.jls"))
// Decode using the high-level decoder
let decoder = JPEGLSDecoder()
let imageData = try decoder.decode(jpegLSData)
// Inspect frame header
print("Image: \(imageData.frameHeader.width)×\(imageData.frameHeader.height)")
print("Bits per sample: \(imageData.frameHeader.bitsPerSample)")
print("Components: \(imageData.frameHeader.componentCount)")
// Access pixel data
for (index, component) in imageData.components.enumerated() {
print("Component \(index + 1): \(component.pixels.count) rows × \(component.pixels.first?.count ?? 0) cols")
}The jpegls command-line tool provides easy access to JPEG-LS operations.
# Human-readable output
jpegls info image.jls
# JSON output for programmatic use
jpegls info image.jls --json
# Quiet mode (single line)
jpegls info image.jls --quiet# Basic verification
jpegls verify image.jls
# Verbose output with details
jpegls verify image.jls --verbose
# Quiet mode (exit code 0 = success)
jpegls verify image.jls --quiet
echo $? # Check exit codeEncode raw pixel data or PGM/PPM images to JPEG-LS, and decode them back:
# Encode raw pixel data to JPEG-LS
jpegls encode input.raw output.jls --width 512 --height 512 --bits-per-sample 8
# Encode a PGM or PPM image (dimensions detected automatically)
jpegls encode input.pgm output.jls
jpegls encode input.ppm output.jls
# Near-lossless encoding
jpegls encode input.pgm output.jls --near 3
# Decode JPEG-LS to PGM/PPM
jpegls decode input.jls output.pgm
jpegls decode input.jls output.ppm
# Compare two JPEG-LS or PGM/PPM files
jpegls compare original.jls decoded.jlsProcess multiple files at once:
# Get info for all JPEG-LS files
jpegls batch info "images/*.jls"
# Verify all files with progress
jpegls batch verify "*.jls" --verbose
# Custom parallelism
jpegls batch info "*.jls" --parallelism 4JLSwift uses 2D arrays to represent image data:
// Grayscale: Single 2D array
let grayscale: [[Int]] = [
[100, 110, 120], // Row 0
[105, 115, 125], // Row 1
[110, 120, 130] // Row 2
]
// RGB: Three separate 2D arrays (one per color channel)
let red: [[Int]] = [[255, 200, 150], ...]
let green: [[Int]] = [[240, 190, 140], ...]
let blue: [[Int]] = [[230, 180, 130], ...]- Frame Header: Describes the entire image (dimensions, bits per sample, component count)
- Encoder Configuration: Specifies how the image is encoded (interleaving, NEAR parameter)
// Frame header (created automatically from image data)
let frameHeader = imageData.frameHeader
print("Width: \(frameHeader.width), Height: \(frameHeader.height)")
// Encoder configuration
let config = try JPEGLSEncoder.Configuration(
near: 0, // 0 = lossless
interleaveMode: .none // .none, .line, or .sample
)Three modes for multi-component images:
- None (
.none): Encode each component separately - Line-interleaved (
.line): Interleave by scan lines - Sample-interleaved (
.sample): Interleave pixel by pixel (best for RGB)
// Sample-interleaved (recommended for RGB)
let config = try JPEGLSEncoder.Configuration(near: 0, interleaveMode: .sample)
// Line-interleaved
let config = try JPEGLSEncoder.Configuration(near: 0, interleaveMode: .line)- Lossless (
NEAR=0): Perfect reconstruction, no quality loss - Near-Lossless (
NEAR>0): Controlled lossy compression with maximum error of ±NEAR
// Lossless (NEAR=0, the default)
let losslessData = try JPEGLSEncoder().encode(imageData)
// Near-lossless (NEAR=3 means max error of ±3)
let config = try JPEGLSEncoder.Configuration(near: 3)
let nearLosslessData = try JPEGLSEncoder().encode(imageData, configuration: config)For large frames, parallelise a single image across cores with restart markers (ITU-T.87 DRI/RSTm). There is no tiling API — the codec decodes each scan into one flat pixel plane; restart intervals are the parallelism and error-resilience mechanism:
import JPEGLS
// Large frames: parallelise a single image across cores with restart markers
let config = try JPEGLSEncoder.Configuration(restartInterval: 256)
let encoded = try JPEGLSEncoder().encode(imageData, configuration: config)
// Decoding splits at the RST markers automatically and decodes intervals concurrently.Restart intervals are currently supported for lossless (NEAR = 0), non-interleaved scans. Buffer pooling and SIMD acceleration are handled internally by the codec — no setup is required.
Handle JPEG-LS errors gracefully:
import JPEGLS
do {
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print("✓ Success: Encoded \(jpegLSData.count) bytes")
} catch let error as JPEGLSError {
switch error {
case .invalidDimensions(let width, let height):
print("✗ Invalid dimensions: \(width)×\(height)")
case .invalidBitsPerSample(let bits):
print("✗ Invalid bits per sample: \(bits) (must be 2-16)")
case .invalidComponentCount(let count):
print("✗ Invalid component count: \(count) (must be 1-4)")
default:
print("✗ Error: \(error)")
}
} catch {
print("✗ Unexpected error: \(error)")
}- README.md: Detailed feature documentation
- USAGE_EXAMPLES.md: Comprehensive real-world usage examples
- MILESTONES.md: Development roadmap and progress
- PERFORMANCE_TUNING.md: Performance optimisation guide
- TROUBLESHOOTING.md: Common issues and solutions
Generate full API documentation using DocC:
swift package generate-documentationCheck the test suite for comprehensive examples:
# View encoding examples
open Tests/JPEGLSTests/JPEGLSMultiComponentEncoderTests.swift
# View decoding examples
open Tests/JPEGLSTests/JPEGLSMultiComponentDecoderTests.swift
# View parser examples
open Tests/JPEGLSTests/CharLSConformanceTests.swiftRun performance benchmarks to measure encoding/decoding speed:
swift test --filter JPEGLSPerformanceBenchmarks- Issues: GitHub Issues
- Contributing: See Copilot Instructions
- USAGE_EXAMPLES.md - Comprehensive real-world usage examples
- SWIFTUI_EXAMPLES.md - SwiftUI integration guide with image loading examples
- APPKIT_EXAMPLES.md - AppKit integration guide for macOS applications
- PERFORMANCE_TUNING.md - Performance optimisation and benchmarking
- TROUBLESHOOTING.md - Common issues and solutions
- Swift 6.2 or later
- Platforms: Linux, macOS 12+, iOS 15+
- Optimised for: Apple Silicon (M1/M2/M3) with ARM64 NEON
- Compatible with: x86-64 with SSE/AVX
Happy coding! 🚀