Skip to main content

ravif/
lib.rs

1//! ```rust
2//! use ravif::*;
3//! # fn doit(pixels: &[RGBA8], width: usize, height: usize) -> Result<(), Error> {
4//! let res = Encoder::new()
5//!     .with_quality(70.)
6//!     .with_speed(4)
7//!     .encode_rgba(Img::new(pixels, width, height))?;
8//! std::fs::write("hello.avif", res.avif_file);
9//! # Ok(()) }
10
11mod av1encoder;
12
13mod error;
14pub use av1encoder::ColorModel;
15pub use error::Error;
16
17#[doc(hidden)]
18#[deprecated = "Renamed to `ColorModel`"]
19pub type ColorSpace = ColorModel;
20
21pub use av1encoder::{AlphaColorMode, BitDepth, EncodedImage, Encoder};
22#[doc(inline)]
23pub use rav1e::prelude::{MatrixCoefficients, PixelRange};
24
25mod dirtyalpha;
26
27#[doc(no_inline)]
28pub use imgref::Img;
29#[doc(no_inline)]
30pub use rgb::{RGB8, RGBA8};
31
32#[cfg(not(feature = "threading"))]
33mod rayoff {
34    pub fn current_num_threads() -> usize {
35        std::thread::available_parallelism().map(|v| v.get()).unwrap_or(1)
36    }
37
38    pub fn join<A, B>(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) {
39        (a(), b())
40    }
41}
42
43#[test]
44fn encode8_with_alpha() {
45    let img = imgref::ImgVec::new((0..200).flat_map(|y| (0..256).map(move |x| {
46        RGBA8::new(x as u8, y as u8, 255, (x + y) as u8)
47    })).collect(), 256, 200);
48
49    let enc = Encoder::new()
50        .with_quality(22.0)
51        .with_bit_depth(BitDepth::Eight)
52        .with_speed(1)
53        .with_alpha_quality(22.0)
54        .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty)
55        .with_num_threads(Some(2));
56    let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgba(img.as_ref()).unwrap();
57    assert!(color_byte_size > 50 && color_byte_size < 1000);
58    assert!(alpha_byte_size > 50 && alpha_byte_size < 1000); // the image must have alpha
59
60    let parsed = avif_parse::read_avif(&mut avif_file.as_slice()).unwrap();
61    assert!(parsed.alpha_item.is_some());
62    assert!(parsed.primary_item.len() > 100);
63    assert!(parsed.primary_item.len() < 1000);
64
65    let md = parsed.primary_item_metadata().unwrap();
66    assert_eq!(md.max_frame_width.get(), 256);
67    assert_eq!(md.max_frame_height.get(), 200);
68    assert_eq!(md.bit_depth, 8);
69}
70
71#[test]
72fn encode8_opaque() {
73    let img = imgref::ImgVec::new((0..101).flat_map(|y| (0..129).map(move |x| {
74        RGBA8::new(255, 100 + x as u8, y as u8, 255)
75    })).collect(), 129, 101);
76
77    let enc = Encoder::new()
78        .with_quality(33.0)
79        .with_speed(10)
80        .with_alpha_quality(33.0)
81        .with_bit_depth(BitDepth::Auto)
82        .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty)
83        .with_num_threads(Some(1));
84    let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgba(img.as_ref()).unwrap();
85    assert_eq!(0, alpha_byte_size); // the image must not have alpha
86    let tmp_path = format!("/tmp/ravif-encode-test-failure-{color_byte_size}.avif");
87    if color_byte_size <= 150 || color_byte_size >= 500 {
88        std::fs::write(&tmp_path, &avif_file).expect(&tmp_path);
89    }
90    assert!(color_byte_size > 150 && color_byte_size < 500, "size = {color_byte_size}; expected ~= 215; see {tmp_path}");
91
92    let parsed1 = avif_parse::read_avif(&mut avif_file.as_slice()).unwrap();
93    assert_eq!(None, parsed1.alpha_item);
94
95    let md = parsed1.primary_item_metadata().unwrap();
96    assert_eq!(md.max_frame_width.get(), 129);
97    assert_eq!(md.max_frame_height.get(), 101);
98    assert!(md.still_picture);
99    assert_eq!(md.bit_depth, 10);
100
101    let img = img.map_buf(|b| b.into_iter().map(|px| px.rgb()).collect::<Vec<_>>());
102
103    let enc = Encoder::new()
104        .with_quality(33.0)
105        .with_speed(10)
106        .with_bit_depth(BitDepth::Ten)
107        .with_alpha_quality(33.0)
108        .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty)
109        .with_num_threads(Some(1));
110
111    let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgb(img.as_ref()).unwrap();
112    assert_eq!(0, alpha_byte_size); // the image must not have alpha
113    assert!(color_byte_size > 50 && color_byte_size < 1000);
114
115    let parsed2 = avif_parse::read_avif(&mut avif_file.as_slice()).unwrap();
116
117    assert_eq!(parsed1.alpha_item, parsed2.alpha_item);
118    assert_eq!(parsed1.primary_item, parsed2.primary_item); // both are the same pixels
119}
120
121#[test]
122fn encode8_cleans_alpha() {
123    let img = imgref::ImgVec::new((0..200).flat_map(|y| (0..256).map(move |x| {
124        RGBA8::new((((x/ 5 + y ) & 0xF) << 4) as u8, (7 * x + y / 2) as u8, ((x * y) & 0x3) as u8, ((x + y) as u8 & 0x7F).saturating_sub(100))
125    })).collect(), 256, 200);
126
127    let enc = Encoder::new()
128        .with_quality(66.0)
129        .with_speed(6)
130        .with_alpha_quality(88.0)
131        .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty)
132        .with_num_threads(Some(1));
133
134    let dirty = enc
135        .encode_rgba(img.as_ref())
136        .unwrap();
137
138    let clean = enc
139        .with_alpha_color_mode(AlphaColorMode::UnassociatedClean)
140        .encode_rgba(img.as_ref())
141        .unwrap();
142
143    assert_eq!(clean.alpha_byte_size, dirty.alpha_byte_size); // same alpha on both
144    assert!(clean.alpha_byte_size > 200 && clean.alpha_byte_size < 1000);
145    assert!(clean.color_byte_size > 2000 && clean.color_byte_size < 6000);
146    assert!(clean.color_byte_size < dirty.color_byte_size / 2); // significant reduction in color data
147}