wml2 is a callback-based image I/O library for Rust.
- decodes and encodes images in memory
- works with the built-in RGBA
ImageBufferor custom callbacks - supports still images and selected animation / multi-page formats
- includes native file helpers on non-WASM targets
See wml2-test/examples.
$ cargo run -p wml2-test --example to_bmp --release -- <inputfile> <output_dir>$ cargo run -p wml2-test --example metadata --release -- <inputfile>$ cargo run -p wml2-test --example converter -- <inputfiles...> -o <output_dir> [-f gif|png|jpeg|bmp|tiff|webp] [-q <quality>] [-z <0-9>] [-c <none|lzw|lzw_msb|lzw_lsb|jpeg|lossy|lossless>] [--exif copy] [--split]| format | enc | dec | notes |
|---|---|---|---|
| BMP | O | O | encoder writes uncompressed BMP |
| JPEG | O | O | baseline encoder; baseline and Huffman progressive decoder |
| GIF | O | O | palette/LZW encoder, animation supported |
| PNG | O | O | PNG/APNG; encoder writes RGBA truecolor |
| TIFF | O | O | encode: none/LZW/JPEG(new); decode: none/LZW/PackBits/JPEG(new)/Adobe Deflate/CCITT Huffman RLE/CCITT Group 3/4 Fax |
| WEBP | O | O | pure Rust still/animated decoder and still/animated encoder; lossless/lossy output |
| MAG | x | O | Japanese legacy image format |
| MAKI | x | O | Japanese legacy image format, disabled by noretoro |
| PI | x | O | Japanese legacy image format, disabled by noretoro |
| PIC | x | O | Japanese legacy image format, disabled by noretoro |
| VSP/DAT | x | O | Japanese legacy image format/container, disabled by noretoro |
| PCD | x | O | Photo CD base4 decode, disabled by noretoro |
- default: legacy decoders are enabled
noretoro: disablesMAKI,PI,PIC,VSP/DAT, andPCD
[dependencies] wml2 = "0.0.18"[dependencies] wml2 = { version = "0.0.18", features = ["noretoro"] }draw::image_to() encodes an ImageBuffer directly into a Vec<u8>.
draw::convert() chooses the encoder from the output extension: .gif, .png, .apng, .jpg, .jpeg, .bmp, .tif, .tiff, .webp.
Supported option keys in EncodeOptions::options / draw::convert(..., options):
- JPEG:
quality - TIFF:
compression = none|lzw|lzw_msb|lzw_lsb|jpeg - TIFF with
compression=jpeg:quality - WebP:
optimize(0..=9) - WebP lossy:
quality - PNG/JPEG/TIFF/WebP:
exif- raw EXIF bytes via
DataMap::Raw - TIFF-style EXIF via
DataMap::Exif DataMap::Ascii("copy".to_string())to preserve decoded EXIF duringconvert()
- raw EXIF bytes via
wml2-test/examples/converter options:
-q: JPEG quality, WebP lossy quality, or TIFF JPEG quality-z: WebP optimize level-c: TIFF compression or WebPlossy|lossless--exif copy: copy source EXIF to PNG/JPEG/TIFF/WebP output--split: split animation / multi-frame output for GIF/PNG/TIFF/WebP
Encode supports:
- no compression
- LZW
- JPEG (new-style TIFF JPEG, RGB only)
Decode supports:
- no compression
- LZW
- PackBits
- JPEG (new-style TIFF JPEG)
- Adobe Deflate
- CCITT Huffman RLE
- CCITT Group 3 Fax
- CCITT Group 4 Fax
For simple in-memory decoding, use draw::image_load(). For native file I/O, use draw::image_from_file().
use std::error::Error; use wml2::draw::{PickCallback, image_from_file}; fn main() -> Result<(), Box<dyn Error>> { let mut image = image_from_file("foo.webp".to_string())?; println!("{}x{}", image.width, image.height); if let Some(metadata) = image.metadata()? { if let Some(format) = metadata.get("Format") { println!("format: {:?}", format); } } Ok(()) }Use draw::image_loader() or draw::image_reader() when you want decoders to write into a custom DrawCallback.
ImageBuffer implements DrawCallback and receives:
init: initialize the canvasdraw: write RGBA pixels into a rectanglenext: receiveNextOptionsfor animation or multi-image transitionsterminate: finalize decodingverbose: decoder-specific debug outputset_metadata: decoded metadata
Use draw::image_to() for ImageBuffer, or draw::image_encoder() / draw::image_writer() when you want to encode a custom PickCallback.
use std::collections::HashMap; use std::error::Error; use wml2::draw::{ImageBuffer, image_to}; use wml2::metadata::DataMap; use wml2::util::ImageFormat; fn main() -> Result<(), Box<dyn Error>> { let mut image = ImageBuffer::from_buffer(1, 1, vec![255, 0, 0, 255]); let mut options = HashMap::new(); options.insert("quality".to_string(), DataMap::UInt(90)); let jpeg = image_to(&mut image, ImageFormat::Jpeg, Some(options))?; assert!(jpeg.starts_with(&[0xff, 0xd8])); Ok(()) }ImageBuffer implements PickCallback and provides:
encode_startencode_pickencode_endmetadata
Metadata is stored as HashMap<String, DataMap>.
use std::error::Error; use wml2::draw::{PickCallback, image_from_file}; use wml2::metadata::DataMap; fn main() -> Result<(), Box<dyn Error>> { let mut image = image_from_file("foo.jpg".to_string())?; let metadata = image.metadata()?.unwrap_or_default(); if let Some(DataMap::ICCProfile(profile)) = metadata.get("ICC Profile") { println!("ICC profile: {} bytes", profile.len()); } Ok(()) }- integration tests use generic names such as
sample.mki,sample.pi,sample.pic,sample.dat - original sample filenames are intentionally not referenced in public test code
- optional external sample paths can be configured in
wml2/tests/test_samples.txt wml2/tests/test_samples.txtis ignored by git; usewml2/tests/test_samples.example.txtas a template
0x01: basic header0x02: Huffman table0x04: extracted Huffman table0x08: quantization table0x10: EXIF0x20: ICC profile header0x40: ICC profile detail0x60: ICC profile all
0.0.1: baseline JPEG0.0.2: BMP OS/2 and Windows RGB/RLE4/RLE8/bit fields, baseline JPEG0.0.3: GIF decoder0.0.4: error message updates0.0.5: reader and error propagation changes0.0.6: RST marker read bug fix0.0.7: PNG decoder0.0.8: pipelined JPEG, BMP saver, animation GIF work0.0.9: PNG saver0.0.10: progressive JPEG fix0.0.11: TIFF Group 3/4 Fax and multipage TIFF decode improvements0.0.12: encode option changes0.0.13: MAG decoder0.0.14: MAKI/PI/PIC/VSP(DAT)/PCD decoders andnoretoro0.0.15: baseline JPEG encoder0.0.16: pure Rust WebP decoder and APNG encoder0.0.17: pure Rust WebP encoder and animated WebP encode0.0.18: GIF encoder, TIFF encoder, EXIF writer
MIT License (C) 2022-2026
MITH@mmk https://mith-mmk.github.io/