image (Rust)
| Language | Rust |
|---|---|
| License | MIT or Apache-2.0 |
| Repository | github.com/image-rs/image |
| Documentation | docs.rs/image |
| Depends on | zune-jpeg (JPEG decoding), pure Rust |
| Read / write | yes / yes; decodes baseline and progressive, encodes baseline (L8 and RGB8); reads and writes Exif and ICC bytes |
image gives one API for many formats: ImageReader::open(path)?.decode()? returns a DynamicImage whatever the format, and save picks the encoder from the file extension. JPEG decoding uses zune-jpeg; encoding uses the crate’s own baseline encoder with a quality setting.
Since 0.25 the decoder reports the Exif orientation and raw Exif and ICC data, and DynamicImage::apply_orientation rotates the pixels. The encoder writes only baseline JPEG and accepts grayscale or RGB, not RGBA. For progressive or mozjpeg-quality output use the turbojpeg or mozjpeg crates.
Installation#
cargo add image
# for Exif tag parsing
cargo add kamadak-exif
To compile only JPEG support: image = { version = "0.25", default-features = false, features = ["jpeg"] }.
Basic usage: decode, resize and encode a JPEG#
use image::codecs::jpeg::JpegEncoder;
use image::imageops::FilterType;
use image::ImageReader;
use std::fs::File;
use std::io::BufWriter;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let img = ImageReader::open("in.jpg")?.decode()?;
println!("{} x {}, {:?}", img.width(), img.height(), img.color());
let small = img.resize(1200, 1200, FilterType::Lanczos3); // fit inside, keep aspect ratio
let out = BufWriter::new(File::create("out.jpg")?);
let encoder = JpegEncoder::new_with_quality(out, 85);
small.write_with_encoder(encoder)?;
Ok(())
}
img.save("out.jpg") also works and uses quality 75.
How to apply the Exif orientation#
use image::{DynamicImage, ImageDecoder, ImageReader};
fn open_upright(path: &str) -> Result<DynamicImage, image::ImageError> {
let mut decoder = ImageReader::open(path)?.into_decoder()?;
let orientation = decoder.orientation()?;
let mut img = DynamicImage::from_decoder(decoder)?;
img.apply_orientation(orientation);
Ok(img)
}
How to get JPEG width and height without decoding#
let (w, h) = image::image_dimensions("in.jpg")?;
println!("{w} x {h}");
How to read Exif tags#
The decoder returns the raw Exif block; parse it with kamadak-exif:
use exif::{In, Tag};
use image::{ImageDecoder, ImageReader};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut decoder = ImageReader::open("photo.jpg")?.into_decoder()?;
if let Some(raw) = decoder.exif_metadata()? {
let exif = exif::Reader::new().read_raw(raw)?;
if let Some(model) = exif.get_field(Tag::Model, In::PRIMARY) {
println!("Camera: {}", model.display_value());
}
for field in exif.fields() {
println!("{}: {}", field.tag, field.display_value().with_unit(&exif));
}
}
Ok(())
}
How to convert PNG with transparency to JPEG#
The JPEG encoder rejects RGBA. Convert to RGB (alpha is dropped) or composite on a background first:
let png = image::open("logo.png")?;
let mut bg = image::RgbImage::from_pixel(png.width(), png.height(), image::Rgb([255, 255, 255]));
for (x, y, p) in png.to_rgba8().enumerate_pixels() {
let a = p[3] as u32;
let blend = |c: u8, b: u8| ((c as u32 * a + b as u32 * (255 - a)) / 255) as u8;
let o = bg.get_pixel_mut(x, y);
*o = image::Rgb([blend(p[0], o[0]), blend(p[1], o[1]), blend(p[2], o[2])]);
}
bg.save("logo.jpg")?;
Tips#
- Build with
--release: image decoding in debug builds is 10-50 times slower. - Copy metadata into the output with
encoder.set_exif_metadata(bytes)andencoder.set_icc_profile(bytes)(methods of theImageEncodertrait) beforewrite_with_encoder. ImageReader::limitssets maximum dimensions and memory for untrusted input.