All formats

image (Rust)

LanguageRust
LicenseMIT or Apache-2.0
Repositorygithub.com/image-rs/image
Documentationdocs.rs/image
Depends onzune-jpeg (JPEG decoding), pure Rust
Read / writeyes / 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#

Shell
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#

Rust
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#

Rust
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#

Rust
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:

Rust
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:

Rust
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#