avif-decode 1.0.2

Convert AVIF images to PNG or decompressed pixel data
Documentation
use rgb::ComponentMap;
use std::path::Path;
use std::path::PathBuf;
use avif_decode::{Decoder, Image, Result};

fn main() {
    let (input_path, output_path) = match parse_args() {
        Ok(p) => p,
        Err(e) => {
            eprintln!("{e}\nusage: avif_decode input.avif output.png");
            std::process::exit(1);
        }
    };

    if let Err(e) = run(&input_path, &output_path) {
        eprintln!("error ({}): {}", input_path.display(), e);
        std::process::exit(1);
    }
}

fn parse_args() -> Result<(PathBuf, PathBuf), &'static str> {
    let mut input_path = None;
    let mut output_path = None;
    let mut force = false;
    let mut args = std::env::args_os().skip(1);
    while let Some(arg) = args.next() {
        match arg {
            a if a == "-v" || a == "--version" => {
                return Err(env!("CARGO_PKG_VERSION"));
            },
            a if a == "-h" || a == "--help" => {
                return Err("");
            },
            a if a == "-f" => {
                force = true;
            },
            a if a == "-o" => {
                if output_path.is_some() {
                    return Err("Output specified more than once");
                }
                output_path = Some(args.next().ok_or("-o needs a value")?);
            },
            path if input_path.is_none() => {
                input_path = Some(path);
            },
            path if output_path.is_none() => {
                output_path = Some(path);
            },
            _ => return Err("Too many arguments. Only input and output path expected"),
        }
    }
    let input_path = PathBuf::from(input_path.ok_or("Missing input path")?);
    let output_path = args.next().map_or_else(|| input_path.with_extension("png"), PathBuf::from);

    if !force && output_path.exists() {
        return Err(if output_path.extension().unwrap_or("".as_ref()) == "png" {
            "output file already exists. Use -f to overwrite."
        } else {
            "output file must be .png. Multiple AVIF input files are not supported."
        });
    }
    Ok((input_path, output_path))
}

fn run(input_path: &Path, output_path: &Path) -> Result<(), Box<dyn std::error::Error>> {

    let data = std::fs::read(input_path).map_err(|e| format!("Unable to read '{}', because: {}", input_path.display(), e))?;

    let d = Decoder::from_avif(&data)?;
    let encoded = match d.to_image()? {
        Image::Rgb8(img) => {
            let (buf, width, height) = img.into_contiguous_buf();
            lodepng::encode_memory(&buf, width, height, lodepng::ColorType::RGB, 8)
        },
        Image::Rgb16(img) => {
            let (mut buf, width, height) = img.into_contiguous_buf();
            for px in &mut buf {
                *px = px.map(|c| u16::from_ne_bytes(c.to_be_bytes()));
            }
            lodepng::encode_memory(&buf, width, height, lodepng::ColorType::RGB, 16)
        },
        Image::Rgba8(img) => {
            let (buf, width, height) = img.into_contiguous_buf();
            lodepng::encode_memory(&buf, width, height, lodepng::ColorType::RGBA, 8)
        },
        Image::Rgba16(img) => {
            let (mut buf, width, height) = img.into_contiguous_buf();
            for px in &mut buf {
                *px = px.map(|c| u16::from_ne_bytes(c.to_be_bytes()));
            }
            lodepng::encode_memory(&buf, width, height, lodepng::ColorType::RGBA, 16)
        },
        Image::Gray8(img) => {
            let (buf, width, height) = img.into_contiguous_buf();
            lodepng::encode_memory(&buf, width, height, lodepng::ColorType::GREY, 8)
        },
        Image::Gray16(img) => {
            let (mut buf, width, height) = img.into_contiguous_buf();
            for px in &mut buf {
                *px = px.map(|c| u16::from_ne_bytes(c.to_be_bytes()));
            }
            lodepng::encode_memory(&buf, width, height, lodepng::ColorType::GREY, 16)
        },
    }?;
    std::fs::write(output_path, encoded).map_err(|e| format!("Unable to write '{}', because: {}", output_path.display(), e))?;
    Ok(())
}