Skip to content

Commit

Permalink
Default to 10 bit encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
hannes-vernooij committed Dec 13, 2024
1 parent 4e0430f commit 8d28a1d
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 12 deletions.
47 changes: 36 additions & 11 deletions ravif/src/av1encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ use rgb::{Rgb, Rgba};
/// For [`Encoder::with_internal_color_model`]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ColorModel {
/// Standard color space for photographic content. Usually the best choice.
/// Standard color model for photographic content. Usually the best choice.
/// This library always uses full-resolution color (4:4:4).
/// This library will automatically choose between BT.601 or BT.709.
YCbCr,
/// RGB channels are encoded without colorspace transformation.
/// RGB channels are encoded without color space transformation.
/// Usually results in larger file sizes, and is less compatible than `YCbCr`.
/// Use only if the content really makes use of RGB, e.g. anaglyph images or RGB subpixel anti-aliasing.
RGB,
Expand All @@ -38,10 +38,14 @@ pub enum AlphaColorMode {
Premultiplied,
}

/// The 8-bit mode only exists as a historical curiosity caused by lack of interoperability with old Safari versions.
/// There's no other reason to use it. 8 bits internally isn't precise enough for a complex codec like AV1, and 10 bits always compresses much better (even if the input and output are 8-bit sRGB).
/// The workaround for Safari is no longer needed, and the 8-bit encoding is planned to be deleted in a few months when usage of the oldest Safari versions becomes negligible.
/// https://github.com/kornelski/cavif-rs/pull/94#discussion_r1883073823
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
pub enum BitDepth {
#[default]
Eight,
#[default]
Ten,
}

Expand Down Expand Up @@ -85,7 +89,7 @@ pub struct Encoder {
/// [`AlphaColorMode`]
alpha_color_mode: AlphaColorMode,
/// 8 or 10
depth: Option<u8>,
depth: BitDepth,
}

/// Builder methods
Expand All @@ -97,7 +101,7 @@ impl Encoder {
quantizer: quality_to_quantizer(80.),
alpha_quantizer: quality_to_quantizer(80.),
speed: 5,
depth: None,
depth: BitDepth::default(),
premultiplied_alpha: false,
color_model: ColorModel::YCbCr,
threads: None,
Expand All @@ -115,12 +119,17 @@ impl Encoder {
self
}

/// Depth 8 or 10. `None` picks automatically.
#[doc(hidden)]
#[deprecated(note = "Renamed to with_bit_depth")]
pub fn with_depth(self, depth: Option<u8>) -> Self {
self.with_bit_depth(depth.map(|d| if d >= 10 { BitDepth::Ten } else { BitDepth::Eight }).unwrap_or(BitDepth::Ten))
}

/// Depth 8 or 10-bit, default is 10-bit, even when 8 bit input data is provided.
#[inline(always)]
#[track_caller]
#[must_use]
pub fn with_depth(mut self, depth: Option<u8>) -> Self {
assert!(depth.map_or(true, |d| d == 8 || d == 10));
pub fn with_bit_depth(mut self, depth: BitDepth) -> Self {
self.depth = depth;
self
}
Expand Down Expand Up @@ -206,7 +215,7 @@ impl Encoder {
pub fn encode_rgba<P: Pixel + Default>(&self, in_buffer: Img<&[Rgba<P>]>) -> Result<EncodedImage, Error> {
let new_alpha = self.convert_alpha(in_buffer);
let buffer = new_alpha.as_ref().map(|b: &Img<Vec<Rgba<P>>>| b.as_ref()).unwrap_or(in_buffer);
let use_alpha = buffer.pixels().any(|px| Into::<u32>::into(px.a) != 255);
let use_alpha = buffer.pixels().any(|px| px.a != P::cast_from(255));
if !use_alpha {
return self.encode_rgb_internal(buffer.width(), buffer.height(), buffer.pixels().map(|px| px.rgb()));
}
Expand Down Expand Up @@ -279,11 +288,22 @@ impl Encoder {
ColorModel::RGB => MatrixCoefficients::Identity,
};

let is_eight_bit = std::mem::size_of::<P>() == 1;
let input_bit_depth = if is_eight_bit { BitDepth::Eight } else { self.depth };

// First convert from 8 bit rgb to 8 bit ycbcr
let planes = pixels.map(|px| match self.color_model {
ColorModel::YCbCr => rgb_to_ycbcr(px, self.depth, BT601),
ColorModel::YCbCr => rgb_to_ycbcr(px, input_bit_depth, BT601),
ColorModel::RGB => [px.g, px.b, px.r],
});
self.encode_raw_planes(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients, self.depth)

// Then convert from bit depth when needed.
if self.depth != BitDepth::Eight && is_eight_bit {
let planes_u16 = planes.map(|px| [to_ten(px[0]), to_ten(px[1]), to_ten(px[2])]);
self.encode_raw_planes(width, height, planes_u16, None::<[_; 0]>, PixelRange::Full, matrix_coefficients, self.depth)
} else {
self.encode_raw_planes(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients, self.depth)
}
}

/// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`,
Expand Down Expand Up @@ -375,6 +395,11 @@ impl Encoder {
// const REC709: [f32; 3] = [0.2126, 0.7152, 0.0722];
const BT601: [f32; 3] = [0.2990, 0.5870, 0.1140];

#[inline(always)]
fn to_ten<P: Pixel + Default>(x: P) -> u16 {
(u16::cast_from(x) << 2) | (u16::cast_from(x) >> 6)
}

#[inline(always)]
fn rgb_to_ycbcr<P: Pixel + Default>(px: Rgb<P>, bit_depth: BitDepth, matrix: [f32; 3]) -> [P; 3] {
let depth = bit_depth.to_usize();
Expand Down
2 changes: 1 addition & 1 deletion ravif/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod dirtyalpha;
#[doc(no_inline)]
pub use imgref::Img;
#[doc(no_inline)]
pub use rgb::{RGB8, RGBA8};
pub use rgb::{RGB16, RGB8, RGBA16, RGBA8};

#[cfg(not(feature = "threading"))]
mod rayoff {
Expand Down

0 comments on commit 8d28a1d

Please sign in to comment.