Image codecs, in pure MoonBit

Seven image formats, read and written.

PNG, JPEG, GIF, WebP, BMP, TGA and QOI, with the pixel arithmetic between them: layouts, transparency, rotation, cropping, scaling. One package, no C bindings.

Read the guide API reference

A lens test plate: a Siemens star, a colour strip, a sixteen-step grey wedge and four rulings, printed on warm paper.
Test plate 071200 × 800

The plate, read in this browser

decoded and written by the library, not by a server

format — size — layout — bytes —

Written back

FormatBytesRound trip
reading the plate…

The row above the rule is the file on disk: a PNG written by a drawing script at 1200 × 800. Every row below it is this library decoding that file, writing the pixels in another format, and reading them back. Exact means the samples came back identical; JPEG quantises, and GIF has two hundred and fifty-six colours to spend.

Drop any image on this panel — PNG, JPEG, GIF, WebP, BMP, TGA or QOI — and it is read the same way.

What it does

one package, seven codecs, the arithmetic between them

Seven formats, one call

decode looks at the first bytes and picks the codec; encode takes a picture and a format. The same picture goes out as PNG, QOI, BMP, TGA, JPEG or GIF, or as lossless WebP, without the call site changing.

Pixels you can count

Nine layouts: grey or colour, eight or sixteen bits, with or without alpha, and 32-bit floats. convert moves between them, pixel gives back four numbers, pixels is the bytes themselves.

Geometry

Mirror on either axis, rotate by quarter turns, crop to a rectangle, and scale with nearest, bilinear or Lanczos resampling. Each returns a new picture; the original is untouched.

Animation

GIF, APNG and animated WebP carry frames with delays, disposal and blending. All three arrive as one shape, and compose applies the rules in order to give the frames a viewer shows.

Refuses rather than guesses

A file this library will not read raises Refused with the offset, what it found and what it expected. Limits puts a ceiling on the allocation before a hostile header can ask for it.

Metadata, kept

A PNG keeps its gamma, chromaticities, rendering intent, ICC profile and text chunks. A JPEG's EXIF orientation can be read on its own, or applied while decoding.

7formats, read and written
9pixel layouts
3resampling filters

A smaller file, in one function

the whole of the call site, for a picture that has to travel

fn small_copy(file : Bytes) -> Bytes raise @moonimage.Refused {
  let image = @moonimage.decode(file)      // the format is in the bytes
  let small = image.scale(320, 200)          // bilinear, the default
  @moonimage.encode(small, Format::Webp)   // lossless, no options
}

The guide starts from an empty project and ends with all of this, each step with the output it really printed.