home ~ projects ~ socials

Convert An Image To AVIF In Rust

Conversion Code

This is the basic code I'm using to convert JPGs and PNGs into AVIF image files for use on my website.

---
# Crates for Cargo.toml 
[dependencies]
anyhow = "1.0.97"
image = "0.25.6"
ravif = "0.11.11"
rgb = "0.8.50"
---

use ravif::*;
use image::ImageReader;
use std::path::PathBuf;
use rgb::FromSlice;

fn main() -> anyhow::Result<()> {
   make_avif(
      &PathBuf::from(
         "ravif-tests/input-avif-test-jpg-without-alpha.jpg"
      ),
      &PathBuf::from(
         "ravif-tests/output-avif-test-from-jpg-without-alpha.avif"
      )
   )?;
   make_avif(
      &PathBuf::from(
         "ravif-tests/input-avif-test-png-with-alpha.png"
      ),
      &PathBuf::from(
         "ravif-tests/output-avif-test-from-png-with-alpha.avif"
      )
   )?;
   Ok(())
}

fn make_avif(input_path: &PathBuf, output_path: &PathBuf) -> anyhow::Result<()> {
   let image_data = ImageReader::open(input_path)?.decode()?;
   match image_data.color() {
      image::ColorType::Rgb8 => convert_rgb8(&image_data, output_path),
      image::ColorType::Rgba8 => convert_rgba8(&image_data, output_path),
      _ => {
         println!("Hit Currently Unhandled ColorType - See Notes");
         Ok(())
      }
   }
}

fn convert_rgb8(image_data: &image::DynamicImage, output_path: &PathBuf) -> anyhow::Result<()> {
   println!("Making: {}", output_path.display());
   let img = Img::new(
      image_data.as_bytes().as_rgb(),
      image_data.width() as usize, 
      image_data.height() as usize
   );
   let res = Encoder::new()
      .with_quality(70.)
      .with_speed(4)
      .encode_rgb(img)?;
   std::fs::write(output_path, res.avif_file)?;
   Ok(())
}

fn convert_rgba8(image_data: &image::DynamicImage, output_path: &PathBuf) -> anyhow::Result<()> {
   println!("Making: {}", output_path.display());
   let img = Img::new(
      image_data.as_bytes().as_rgba(),
      image_data.width() as usize, 
      image_data.height() as usize
   );
   let res = Encoder::new()
      .with_quality(70.)
      .with_speed(4)
      .encode_rgba(img)?;
   std::fs::write(output_path, res.avif_file)?;
   Ok(())
}
Output:
Making: ravif-tests/output-avif-test-from-jpg-without-alpha.avif
Making: ravif-tests/output-avif-test-from-png-with-alpha.avif

Notes

  • AVIF was part of the now completed Baseline 2024. It currently shows as having 94.53% support across browsers according to Can I Use. That's good enough for me. I'm going all-in with it for my site. This example is the starting point for my conversion process.
  • The primary crate used in this example is ravif
  • Images are not resized.

    Adding resizing can be done with the resize, resize_exact, and resize_to_fill methods from DynamicImage. It involves doing a little math since you have to calculate targets for both width and height based on the source image (which can be found with .width() and .height()).

  • There are ten ColorType's listed in the docs: L8, La8, Rgb8, Rgba8, L16, La16, Rgb16, Rgba16, Rgb32F, and Rgba32F.

    The JPGs and PNGs I use on my site use only Rga8 (no alpha channel) and Rgba8 (with an alpha channel). Those are the only two this example covers. The same patter they employ can be used for other ColorTypes if you need them.

  • Generating AVIF images takes significant time. Even modestly sized source files can take a few seconds on my M1 mac.
  • The .with_quality(70.) and .with_speed(4) calls use the default values from the documentation. They can be adjusted to tweak the performance and output quality of the process.
  • I'm using anyhow for error handling. It's not required, but it makes things easier to work with.
-- end of line --

References