Pure-Nim image encoders and decoders, no C bindings.
Deflate via pure pkg/archive library
nimble install image
Note
This is an LLM-accelerated Nim package, a pure-Nim port of the Rust
image crate (v0.25.9), mirroring its
API (DynamicImage, ImageBuffer, ImageReader, ImageFormat). Zero C
bindings (deflate via pure-Nim pkg/archive); imageops processing
functions are out of scope for v1, with one deliberate exception: a
strict width/height crop (cropImage, no resampling) needed by the JPEG
compression request builder.
- Colors:
ColorType,ExtendedColorType,Rgb/Rgba/Luma/LumaApixels, sRGB conversions - Buffers:
ImageBuffer(12 aliases),DynamicImage(12 variants),FlatSamples,SubImageviews - IO:
ImageFormat(15 variants), magic-byte guessing,Limits,ImageReaderhelpers - Errors:
ImageError/ImageResult[T]with decoding, encoding, limits and unsupported kinds
Codec support (o = decode, w = encode):
- BMP: o+w — 1/4/8-bit palette, 16-bit bitfields, 24/32-bit, RLE4/RLE8
- EXR: o+w — scanline NONE/RLE/ZIP/ZIPS, half/float/uint to 32F; PIZ/tiled/multipart unsupported
- Farbfeld: o+w —
Rgba16 - GIF: o+w — animation, disposal, interlace, loop count, LZW
- HDR: o+w — Radiance RGBE, flat + new/old RLE
- ICO: o+w — PNG and BMP payloads, AND-mask transparency
- JPEG: o+w — baseline + progressive (SOF2) DCT decode, 4:4:4/4:2:0, quality 1–100; baseline encoder with optional optimized Huffman tables + fluent
compress().crop().encode()API and strictcropImage(all 12DynamicImagevariants) - PNG: o+w — all color types, 1/2/4/8/16-bit, all 5 filters, Adam7, tRNS
- PNM: o+w — P1–P6 plus PAM; writer emits P5/P6
- QOI: o+w — full op-index/run/diff/luma/rgb/rgba codec
- TGA: o+w — raw/RLE, colormaps, 15-bit, orientation
- TIFF: o+w — strips/tiles, chunky/planar, None/LZW/Deflate/PackBits, YCbCr/CMYK/palette
- WebP: o+w — lossless VP8L + lossy VP8 keyframe decode (bit-exact vs dwebp, fancy upsampling) + literal-only encoder; animation / VP8X alpha unsupported
- AVIF: color math only (BT.601/709/2020 YCbCr, YCgCo-Re); AV1 bitstream unsupported
- HEIF/HEIC: o (decode-only) — ISOBMFF container + uncompressed (
unci, v0/v1, comp/pixel/row/mixed, 8/16-bit) + JPEG-in-HEIF via the JPEG decoder +irot/imir/clap/iden; HEVC/VVC/AVC/JPEG2000 bitstreams, tiled layouts, grids/overlays, sequences unsupported
Not ported (out of scope): imageops processing functions, rayon parallelism.
All codecs return ImageResult[T] — check .ok, then use .value:
import image
let res = decodePng(bytes)
if res.ok:
echo res.value.dimensions()let img = RgbImage(width: 2, height: 2, pixels: px)
let enc = encodeBmpRgb8(img) # 24-bit BI_RGB
let dec = decodeBmp(enc) # ImageResult[DynamicImage]let img = DynamicImage(kind: dkRgb32F, rgb32f: Rgb32FImage(
width: 2, height: 1, pixels: px))
let enc = encodeExr(img) # ZIP scanlines
let dec = decodeExr(enc.value) # Rgb32F backlet enc = encodeFarbfeld(img) # Rgba16Image -> seq[uint8]
let dec = decodeFarbfeld(enc) # ImageResult[Rgba16Image]let enc = encodeGif(@[frame0, frame1], 2, 2, palette, @[10'u16, 20'u16])
let gif = decodeGif(enc).value # frames, delays, loop count
let rgba = gifFrameToRgba(gif, 0) # first frame as RgbaImagelet enc = encodeHdr(img) # Rgb32FImage -> Radiance RGBE
let dec = decodeHdr(enc.value) # ImageResult[Rgb32FImage]let enc = encodeIco(@[img]) # PNG-compressed entries
let dec = decodeIco(enc.value) # largest entry winslet enc = encodeJpeg(img, 90) # quality 1..100, 4:2:0 default
let enc2 = encodeJpeg(img, 95, subsample = false) # 4:4:4
let enc3 = encodeJpeg(img, 75, optimize = true) # smaller optimal Huffman tables
let dec = decodeJpeg(enc.value) # baseline DCT
let req = img.compress(50).crop(300, 300, 20, 20).encode() # fluent: strict crop + encode
let opt = img.compress(90).subsampled(false).optimized().encode()
let c = cropImage(img, 0, 0, 20, 20) # strict, variant-preserving; 8-bit inputs only for JPEGlet img = DynamicImage(kind: dkRgb8, rgb8: rgb)
let enc = encodePng(img) # filter None + deflate
let dec = decodePng(enc.value) # validates CRC, unfilters, deinterlaceslet enc = encodePnmRgb8(img) # P6 binary
let enc2 = encodePnmGray8(gray) # P5 binary
let dec = decodePnm(enc) # P1-P7 -> DynamicImagelet enc = encodeQoi(img) # RgbaImage, auto channels
let dec = decodeQoi(enc) # ImageResult[RgbaImage]let enc = encodeTgaRgb8(img) # uncompressed, top-left origin
let enc2 = encodeTgaRgba8(rgba)
let dec = decodeTga(enc) # handles RLE, colormaps, orientationlet enc = encodeTiff(img) # little-endian, single strip
let dec = decodeTiff(enc.value) # first page -> DynamicImage
let pages = decodeTiffAll(bytes) # multi-page -> seq[DynamicImage]let enc = encodeWebp(img) # lossless VP8L
let dec = decodeWebp(enc.value) # opaque -> Rgb8, alpha -> Rgba8let (y, u, v) = rgbToYuvPixel(255, 0, 0, ymBT709, yrFull)
let (r, g, b) = yuvToRgbPixel(y, u, v, ymBT709, yrFull)
let (yy, cg, co) = ycgcoReForward(255, 0, 0) # lossless round-trip pair
let (r2, g2, b2) = ycgcoReInverse(yy, cg, co)
let img = yuv420ToRgb(yPlane, uPlane, vPlane, w, h, ymBT601, yrFull)let dec = decodeHeif(bytes) # primary still -> DynamicImage
let dec2 = decodeHeic(bytes) # alias (HEIC = HEVC-branded HEIF)
let raw = extractJ2kCodestream(bytes) # raw codestream for external decoderslet (found, fmt) = guessFormat(bytes) # magic-byte sniffing
let f = fromExtension("png") # (found, fmtPng)
let m = fromMimeType("image/jpeg") # (found, fmtJpeg)- Core: colors, buffers,
DynamicImage, format detection, limits, errors - Shared:
archivelibrary inflate/deflate/CRC32, pure-Nim LZW (GIF/TIFF) - BMP (palette, bitfields, RLE4/RLE8)
- EXR scanline subset (NONE/RLE/ZIP/ZIPS, half/float/uint)
- Farbfeld
- GIF (animation, interlace, loop count)
- HDR (flat + new/old RLE)
- ICO (PNG + BMP payloads, AND mask)
- JPEG baseline (DCT, 4:4:4/4:2:2/4:2:0, encoder with quality 1–100, optimized Huffman, fluent compress/crop API)
- PNG (all color types, filters, Adam7, tRNS, CRC)
- PNM (P1–P6 + PAM)
- QOI (full opcode set)
- TGA (raw/RLE, colormaps, orientation)
- TIFF (strips/tiles, 4 compressions, YCbCr/CMYK/palette)
- WebP lossless VP8L (meta-Huffman, LZ77, all transforms; validated vs dwebp)
- AVIF color math (BT.601/709/2020 YCbCr, YCgCo-Re)
- JPEG progressive (SOF2) decode (spectral selection + successive approximation; encoder stays baseline-only)
- WebP lossy VP8 keyframe decode (bit-exact vs dwebp)
- EXR PIZ/PXR24/B44/DWAA + tiled/multipart
- AVIF bitstream (requires an AV1 decoder)
- HEIF/HEIC stills: container + uncompressed + JPEG-in-HEIF (HEVC/VVC/AVC/JPEG2000 bitstreams stay unsupported — no pure-Nim video/wavelet decoders)
- JPEG XL (jxl)
- JPEG 2000 (jp2)
- HEIF grids/overlays/sequences/tiled layouts, EVC brand
- RAW (a family of formats, not one codec; often requires camera-specific metadata and processing)
- SIMD: PNG Up filter, JPEG IDCT/FDCT, YCbCr row batch via nimsimd (runtime auto-dispatch SSE2/AVX2/NEON, bit-identical scalar fallback;
-ffp-contract=off) - SIMD: PNG Average/Paeth/Sub (scalar by design — small rows stay scalar)
-
imageopsprocessing functions (blur, resize, rotate — excluded from v1)
- 🐛 Found a bug? Create a new Issue
- 👋 Wanna help? Fork it!
MIT license | Nim Community.