Skip to content
nimbasePublic

About

Encoding and decoding images in Nim. Supporting PNG, JPEG, TIFF, WebP, GIF, OpenEXR, BMP and more!

Topics

Resources

Stars

4 stars

Watchers

2 watching

Forks

Latest commit

 

History

44 Commits

Folders and files

Repository files navigation

Pure-Nim image encoders and decoders, no C bindings.
Deflate via pure pkg/archive library

nimble install image

API reference
Github Actions Github Actions

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.

Features

  • Colors: ColorType, ExtendedColorType, Rgb/Rgba/Luma/LumaA pixels, sRGB conversions
  • Buffers: ImageBuffer (12 aliases), DynamicImage (12 variants), FlatSamples, SubImage views
  • IO: ImageFormat (15 variants), magic-byte guessing, Limits, ImageReader helpers
  • 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 strict cropImage (all 12 DynamicImage variants)
  • 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.

Examples

All codecs return ImageResult[T] — check .ok, then use .value:

import image

let res = decodePng(bytes)
if res.ok:
  echo res.value.dimensions()

BMP

let img = RgbImage(width: 2, height: 2, pixels: px)
let enc = encodeBmpRgb8(img)      # 24-bit BI_RGB
let dec = decodeBmp(enc)          # ImageResult[DynamicImage]

EXR

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 back

Farbfeld

let enc = encodeFarbfeld(img)     # Rgba16Image -> seq[uint8]
let dec = decodeFarbfeld(enc)     # ImageResult[Rgba16Image]

GIF

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 RgbaImage

HDR

let enc = encodeHdr(img)          # Rgb32FImage -> Radiance RGBE
let dec = decodeHdr(enc.value)    # ImageResult[Rgb32FImage]

ICO

let enc = encodeIco(@[img])       # PNG-compressed entries
let dec = decodeIco(enc.value)    # largest entry wins

JPEG

let 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 JPEG

PNG

let img = DynamicImage(kind: dkRgb8, rgb8: rgb)
let enc = encodePng(img)          # filter None + deflate
let dec = decodePng(enc.value)    # validates CRC, unfilters, deinterlaces

PNM

let enc = encodePnmRgb8(img)      # P6 binary
let enc2 = encodePnmGray8(gray)   # P5 binary
let dec = decodePnm(enc)          # P1-P7 -> DynamicImage

QOI

let enc = encodeQoi(img)          # RgbaImage, auto channels
let dec = decodeQoi(enc)          # ImageResult[RgbaImage]

TGA

let enc = encodeTgaRgb8(img)      # uncompressed, top-left origin
let enc2 = encodeTgaRgba8(rgba)
let dec = decodeTga(enc)          # handles RLE, colormaps, orientation

TIFF

let enc = encodeTiff(img)         # little-endian, single strip
let dec = decodeTiff(enc.value)   # first page -> DynamicImage
let pages = decodeTiffAll(bytes)  # multi-page -> seq[DynamicImage]

WebP

let enc = encodeWebp(img)         # lossless VP8L
let dec = decodeWebp(enc.value)   # opaque -> Rgb8, alpha -> Rgba8

AVIF

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

HEIF/HEIC

let dec = decodeHeif(bytes)   # primary still -> DynamicImage
let dec2 = decodeHeic(bytes)  # alias (HEIC = HEVC-branded HEIF)
let raw = extractJ2kCodestream(bytes)  # raw codestream for external decoders

Format detection

let (found, fmt) = guessFormat(bytes)  # magic-byte sniffing
let f = fromExtension("png")           # (found, fmtPng)
let m = fromMimeType("image/jpeg")     # (found, fmtJpeg)

Roadmap

  • Core: colors, buffers, DynamicImage, format detection, limits, errors
  • Shared: archive library 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)
  • imageops processing functions (blur, resize, rotate — excluded from v1)

❤ Contributions & Support

🎩 License

MIT license | Nim Community.

About

Encoding and decoding images in Nim. Supporting PNG, JPEG, TIFF, WebP, GIF, OpenEXR, BMP and more!

Topics

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages