image/jpeg_enc.nu — baseline JPEG (JFIF) encode.
Writes the interoperable core of the format: baseline sequential DCT, 8-bit, Huffman-coded with the ITU T.81 Annex K standard tables, Annex K quantisation tables scaled by a libjpeg-style quality factor (1–100). Greyscale images (1/2 channels) encode as one component; colour (3/4) as YCbCr with 4:4:4, 4:2:2 or 4:2:0 subsampling — chroma is box-averaged before encoding. Alpha channels are dropped.
( jpeg_encode im 90 ) → ( Vec u ) 4:2:0 below quality 90, 4:4:4 from 90 up ( jpeg_encode_sub im 85 2 ) → ( Vec u ) subsampling forced: 0 = 4:4:4, 1 = 4:2:2, 2 = 4:2:0
A decode→encode→decode round trip stays within normal JPEG loss for the chosen quality; the output decodes in libjpeg/Pillow and in this package's own jpeg_decode.
: JEnc: JEnc {
( Vec u ) out
i bbuf i bcnt
( Vec i ) qy ( Vec i ) qc // quant tables, zigzag order
( Vec i ) ydc_co ( Vec i ) ydc_si
( Vec i ) yac_co ( Vec i ) yac_si
( Vec i ) cdc_co ( Vec i ) cdc_si
( Vec i ) cac_co ( Vec i ) cac_si
( Vec f ) A ( Vec i ) zz
}
@ jpeg_encode_sub Image im i quality i subsamp → ( Vec u )subsamp: 0 = 4:4:4, 1 = 4:2:2, 2 = 4:2:0 (ignored for greyscale).
@ jpeg_encode Image im i quality → ( Vec u )Quality-driven default: 4:2:0 below 90, 4:4:4 from 90 up (mirrors what mainstream encoders pick at high quality).
image/jpeg.nu — JPEG decode: baseline AND progressive (Huffman).
Supports the JPEGs real encoders emit: 8-bit precision, Huffman coding, 1 component (greyscale) or 3 (YCbCr → RGB), any 4:4:4 / 4:2:2 / 4:2:0 / 4:1:1 subsampling, restart intervals (DRI/RSTn), sequential (SOF0/SOF1) and progressive (SOF2) frames — spectral selection and successive approximation, DC and AC first/refinement scans, EOB runs. Chroma planes use libjpeg's "fancy" (triangular) upsampling for 2x1/2x2 expansion — bit-matching libjpeg's default path — and a separable float IDCT, so output matches a reference decoder (libjpeg/Pillow) to within IDCT rounding — JPEG is lossy and the spec permits IDCT variation, so this is not bit-exact.
Not supported (clean None, never an out-of-bounds read): arithmetic coding, 12-bit, lossless/hierarchical, and 4-component (CMYK/YCCK).
: Jpeg: Jpeg {
( Vec u ) buf i len
i width i height i ncomp
( Vec i ) cid ( Vec i ) chf ( Vec i ) cvf ( Vec i ) ctq ( Vec i ) ctd ( Vec i ) cta ( Vec i ) cpred
i hmax i vmax i restart
( Vec i ) qt
( Vec i ) hmin ( Vec i ) hmaxc ( Vec i ) hptr ( Vec i ) hvals
( Vec f ) idct_a ( Vec i ) zz
i bpos i bbuf i bcnt i marker
b ok
// progressive state: scan parameters, per-component coefficient grids
b prog i ss i se i ah i al i eobrun
i nscomp ( Vec i ) scomp
( Vec ( Vec i ) ) coefs
( Vec i ) cbw ( Vec i ) cbh // padded block-grid dims per comp
}
@ _ivec i n i fill → ( Vec i )@ _idct_basis → ( Vec f )@ _zigzag → ( Vec i )@ _b_i ( Vec i ) v i p → iint-vector read (0 out of range)
@ _b_f ( Vec f ) v i p → f@ jpeg_decode ( Vec u ) buf → ?Imageimage — pure-NURL image codecs. One include for the whole package: the Image raster, PPM (P5/P6), PNG decode + encode, and file load/save with format sniffing. (Baseline JPEG decode is planned as a later milestone.)
$ deps/image/src/image.nu ?? ( image_load photo.png ) { T im → { // … use im (width/height/channels/data) … ( image_save_png out.png im ) ( image_free im ) } F → { ( nurleprintln decode failed ) } }
@ image_decode ( Vec u ) buf → ?ImageDecode by sniffing the magic bytes: PNG signature → png_decode, JPEG SOI (FF D8) → jpeg_decode, P → PPM.
@ image_load s path → ?ImageLoad and decode a file. None on an I/O error or an unrecognised/malformed format.
@ image_save_png s path Image im → b@ image_save_jpeg s path Image im i quality → bWrite a baseline JPEG (quality 1–100; 4:2:0 below 90, 4:4:4 from 90 up).
@ image_save_ppm s path Image im → bimage/core.nu — the Image type, byte helpers, and PPM (P5/P6).
An Image is a tightly-packed, row-major, 8-bit-per-channel raster: channels 1 = grayscale, 3 = RGB, 4 = RGBA. data holds widthheightchannels bytes. Every codec in the package decodes into, and encodes from, this one representation.
: Image: Image {
i width
i height
i channels
( Vec u ) data
}
: ~ s __img_err ``@ image_error → s@ _img_set_err s msg → v@ image_new i w i h i ch → ImageA zero-filled image of the given geometry.
@ image_of i w i h i ch ( Vec u ) data → ImageAdopt an existing byte buffer (moved in; not copied).
@ image_free Image im → v@ image_width Image im → i@ image_height Image im → i@ image_channels Image im → i@ _byte ( Vec u ) buf i pos → iUnsigned byte at pos (0 when out of range).
@ _push_u16be ( Vec u ) out i v → v@ _push_u32be ( Vec u ) out i v → v@ _u32be ( Vec u ) buf i pos → i@ image_get Image im i x i y i c → i@ image_set Image im i x i y i c i v → v@ ppm_decode ( Vec u ) buf → ?Image@ ppm_encode Image im → ( Vec u )Encode as binary PPM: 3/4-channel → P6 (alpha dropped), 1-channel → P5.
image/png.nu — PNG decode + encode over the stdlib DEFLATE codec.
Decode: the full still-image feature matrix — every legal bit depth (1 / 2 / 4 / 8 / 16, deeper samples scaled to the 8-bit raster), every colour type (grey, RGB, palette → RGB, grey+alpha, RGBA), Adam7 interlacing, and tRNS transparency (palette alpha and grey/RGB colour keys, which add an alpha channel to the output). All five scanline filters are reconstructed at any pixel width. Encode: 8-bit, non-interlaced, filter 0, colour type chosen from the image's channel count. Lossless — a decode→encode→decode round-trip reproduces the pixels exactly.
png_decode returns None on malformed input; the codec never reads out of bounds.
@ png_decode ( Vec u ) buf → ?Image@ png_encode Image im → ( Vec u )Encode an image as a non-interlaced, filter-0, 8-bit PNG.
image/ops.nu — raster operations on the Image type.
Everything a downstream package used to hand-roll: resize (nearest / bilinear / box-average), crop, flips, quarter-turn rotations, channel conversion, fill, rectangle / line drawing, and blit with alpha.
Colours are one packed integer, 0xRRGGBBAA (e.g. opaque red 0xFF0000FF), mapped onto whatever channel count the target image has: greyscale targets take the BT.601 luma, images without an alpha channel drop A. Reads from a greyscale image replicate the grey value into R, G and B; a missing alpha channel reads as 255.
Transform functions (image_resize*, image_crop, flips, rotations, image_convert, image_clone) return a NEW image — free both. Drawing functions (image_fill*, image_draw_*, image_blit) mutate in place, clip to the raster, and overwrite (no blending) — except image_blit, which source-over-blends when the source has an alpha channel.
@ image_get_rgba Image im i x i y → iRead pixel (x,y) as packed 0xRRGGBBAA regardless of the image's channel count (grey replicates into R/G/B, missing alpha reads 255). Out of bounds every stored channel reads 0.
@ image_set_rgba Image im i x i y i rgba → vWrite packed 0xRRGGBBAA into pixel (x,y), mapping onto the image's channels (grey targets take the luma; a missing alpha channel drops A). Out-of-bounds writes are ignored.
@ image_clone Image im → Image@ image_convert Image im i ch → ImageConvert to ch channels (1 grey, 2 grey+alpha, 3 RGB, 4 RGBA). Grey ↔ RGB uses BT.601 luma; added alpha is 255; dropped alpha is dropped.
@ image_crop Image im i x i y i w i h → ImageCopy the w×h window whose top-left corner is (x,y). The window is clipped to the source; a fully-outside window yields a 0×0 image.
@ image_flip_h Image im → Image@ image_flip_v Image im → Image@ image_rot90 Image im → ImageClockwise quarter turns.
@ image_rot180 Image im → Image@ image_rot270 Image im → Image@ image_resize_nearest Image im i nw i nh → ImageNearest neighbour: out(x,y) = in( xsw/nw, ysh/nh ). Exact, blocky.
@ image_resize Image im i nw i nh → ImageBilinear, edge-clamped, pixel-centre aligned. The right default for upscaling and mild downscaling; for thumbnails (large shrink) prefer image_resize_area, which averages instead of skipping.
@ image_resize_area Image im i nw i nh → ImageBox average: each output pixel is the mean of its source rectangle. Exact anti-aliased downscale for integer factors (a 2× shrink averages each 2×2 block); the right choice for thumbnails.
@ image_fill Image im i rgba → v@ image_fill_rect Image im i x0 i y0 i x1 i y1 i rgba → vFill the rectangle with corners (x0,y0)–(x1,y1), inclusive.
@ image_draw_rect Image im i x0 i y0 i x1 i y1 i t i rgba → vRectangle outline with corners (x0,y0)–(x1,y1) inclusive, t pixels thick, growing inward.
@ image_draw_line Image im i x0 i y0 i x1 i y1 i rgba → vBresenham line from (x0,y0) to (x1,y1), clipped per pixel.
@ image_blit Image dst Image src i dx i dy → vCopy src onto dst with its top-left corner at (dx,dy), clipped to the destination. Channel counts may differ (pixels are mapped like image_set_rgba). If the source has an alpha channel the copy is source-over blended; otherwise it overwrites.
img — the image package's command-line tool.
img info <file> format-sniffed decode + geometry img convert <in> <out> [-q N] transcode; format from <out>'s extension (.png .jpg .jpeg .ppm .pgm) img resize <in> <out> <W>x<H> [-q N] resize + transcode. 800x600 exact, 800x / x600 keep aspect. Shrinks use box averaging (clean thumbnails), growth uses bilinear.
-q N JPEG quality 1–100 (default 90)
Exit codes: 0 ok · 1 processing error · 2 usage.
@ main → i