use crate::Result;
use crate::error::Error;
use almost_enough::Stop;
use imgref::{ImgRef, ImgVec};
use rgb::{RGB8, RGBA8, Rgb, Rgba};
use rgb::{RGB16, RGBA16};
use whereat::at;
#[derive(Debug, Clone)]
pub struct GainMapConfig {
pub av1_data: Vec<u8>,
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub metadata: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct EncodedImage {
pub avif_file: Vec<u8>,
pub color_byte_size: usize,
pub alpha_byte_size: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncodeBitDepth {
Eight,
Ten,
#[default]
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncodeColorModel {
#[default]
YCbCr,
Rgb,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncodeAlphaMode {
#[default]
UnassociatedClean,
UnassociatedDirty,
Premultiplied,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncodePixelRange {
#[default]
Full,
Limited,
}
#[derive(Debug, Clone, Copy)]
pub struct MasteringDisplayConfig {
pub primaries: [(u16, u16); 3],
pub white_point: (u16, u16),
pub max_luminance: u32,
pub min_luminance: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Av1Backend {
#[default]
Zenravif,
Svtav1,
}
#[derive(Debug, Clone)]
pub struct EncoderConfig {
pub(crate) backend: Av1Backend,
pub(crate) quality: f32,
pub(crate) speed: u8,
pub(crate) alpha_quality: Option<f32>,
pub(crate) bit_depth: EncodeBitDepth,
pub(crate) color_model: EncodeColorModel,
pub(crate) alpha_color_mode: EncodeAlphaMode,
pub(crate) threads: Option<usize>,
pub(crate) exif: Option<Vec<u8>>,
pub(crate) xmp: Option<Vec<u8>>,
pub(crate) icc_profile: Option<Vec<u8>>,
pub(crate) rotation: Option<u8>,
pub(crate) mirror: Option<u8>,
pub(crate) content_light_level: Option<(u16, u16)>,
pub(crate) mastering_display: Option<MasteringDisplayConfig>,
pub(crate) color_primaries: Option<u8>,
pub(crate) transfer_characteristics: Option<u8>,
pub(crate) matrix_coefficients: Option<u8>,
pub(crate) pixel_range: Option<EncodePixelRange>,
pub(crate) gain_map: Option<GainMapConfig>,
#[cfg(feature = "encode-imazen")]
pub(crate) enable_qm: bool,
#[cfg(feature = "encode-imazen")]
pub(crate) enable_vaq: bool,
#[cfg(feature = "encode-imazen")]
pub(crate) vaq_strength: f64,
#[cfg(feature = "encode-imazen")]
pub(crate) tune_still_image: bool,
#[cfg(feature = "encode-imazen")]
pub(crate) lossless: bool,
}
impl Default for EncoderConfig {
fn default() -> Self {
Self {
backend: Av1Backend::default(),
quality: 75.0,
speed: 4,
alpha_quality: None,
bit_depth: EncodeBitDepth::default(),
color_model: EncodeColorModel::default(),
alpha_color_mode: EncodeAlphaMode::default(),
threads: None,
exif: None,
xmp: None,
icc_profile: None,
rotation: None,
mirror: None,
content_light_level: None,
mastering_display: None,
color_primaries: None,
transfer_characteristics: None,
matrix_coefficients: None,
pixel_range: None,
gain_map: None,
#[cfg(feature = "encode-imazen")]
enable_qm: true,
#[cfg(feature = "encode-imazen")]
enable_vaq: false,
#[cfg(feature = "encode-imazen")]
vaq_strength: 1.0,
#[cfg(feature = "encode-imazen")]
tune_still_image: false,
#[cfg(feature = "encode-imazen")]
lossless: false,
}
}
}
impl EncoderConfig {
pub fn new() -> Self {
Self::default()
}
pub fn backend(mut self, backend: Av1Backend) -> Self {
self.backend = backend;
self
}
pub fn quality(mut self, quality: f32) -> Self {
self.quality = quality;
self
}
pub fn speed(mut self, speed: u8) -> Self {
self.speed = speed;
self
}
pub fn alpha_quality(mut self, quality: f32) -> Self {
self.alpha_quality = Some(quality);
self
}
pub fn bit_depth(mut self, depth: EncodeBitDepth) -> Self {
self.bit_depth = depth;
self
}
pub fn color_model(mut self, model: EncodeColorModel) -> Self {
self.color_model = model;
self
}
pub fn alpha_color_mode(mut self, mode: EncodeAlphaMode) -> Self {
self.alpha_color_mode = mode;
self
}
pub fn threads(mut self, threads: Option<usize>) -> Self {
self.threads = threads;
self
}
pub fn exif(mut self, exif_data: Vec<u8>) -> Self {
self.exif = Some(exif_data);
self
}
pub fn xmp(mut self, xmp_data: Vec<u8>) -> Self {
self.xmp = Some(xmp_data);
self
}
pub fn icc_profile(mut self, profile: Vec<u8>) -> Self {
self.icc_profile = Some(profile);
self
}
pub fn rotation(mut self, angle: u8) -> Self {
self.rotation = Some(angle);
self
}
pub fn mirror(mut self, axis: u8) -> Self {
self.mirror = Some(axis);
self
}
pub fn content_light_level(mut self, max_cll: u16, max_fall: u16) -> Self {
self.content_light_level = Some((max_cll, max_fall));
self
}
pub fn mastering_display(mut self, md: MasteringDisplayConfig) -> Self {
self.mastering_display = Some(md);
self
}
pub fn color_primaries(mut self, cp: u8) -> Self {
self.color_primaries = Some(cp);
self
}
pub fn transfer_characteristics(mut self, tc: u8) -> Self {
self.transfer_characteristics = Some(tc);
self
}
pub fn matrix_coefficients(mut self, mc: u8) -> Self {
self.matrix_coefficients = Some(mc);
self
}
pub fn pixel_range(mut self, range: EncodePixelRange) -> Self {
self.pixel_range = Some(range);
self
}
pub fn with_gain_map(
mut self,
av1_data: Vec<u8>,
width: u32,
height: u32,
bit_depth: u8,
metadata: Vec<u8>,
) -> Self {
self.gain_map = Some(GainMapConfig {
av1_data,
width,
height,
bit_depth,
metadata,
});
self
}
#[cfg(feature = "encode-imazen")]
pub fn with_qm(mut self, enable: bool) -> Self {
self.enable_qm = enable;
self
}
#[cfg(feature = "encode-imazen")]
pub fn with_vaq(mut self, enable: bool, strength: f64) -> Self {
self.enable_vaq = enable;
self.vaq_strength = strength;
self
}
#[cfg(feature = "encode-imazen")]
pub fn with_still_image_tuning(mut self, enable: bool) -> Self {
self.tune_still_image = enable;
self
}
#[cfg(feature = "encode-imazen")]
pub fn with_lossless(mut self, lossless: bool) -> Self {
self.lossless = lossless;
self
}
#[cfg(feature = "encode-imazen")]
pub fn still_image_preset(self) -> Self {
self.with_qm(true)
.with_vaq(true, 0.5)
.with_still_image_tuning(true)
}
}
fn cicp_to_color_primaries(cp: u8) -> ravif::ColorPrimaries {
match cp {
1 => ravif::ColorPrimaries::BT709,
4 => ravif::ColorPrimaries::BT470M,
5 => ravif::ColorPrimaries::BT470BG,
6 => ravif::ColorPrimaries::BT601,
7 => ravif::ColorPrimaries::SMPTE240,
8 => ravif::ColorPrimaries::GenericFilm,
9 => ravif::ColorPrimaries::BT2020,
10 => ravif::ColorPrimaries::XYZ,
11 => ravif::ColorPrimaries::SMPTE431,
12 => ravif::ColorPrimaries::SMPTE432,
22 => ravif::ColorPrimaries::EBU3213,
_ => ravif::ColorPrimaries::Unspecified,
}
}
fn cicp_to_transfer_characteristics(tc: u8) -> ravif::TransferCharacteristics {
match tc {
1 => ravif::TransferCharacteristics::BT709,
4 => ravif::TransferCharacteristics::BT470M,
5 => ravif::TransferCharacteristics::BT470BG,
6 => ravif::TransferCharacteristics::BT601,
7 => ravif::TransferCharacteristics::SMPTE240,
8 => ravif::TransferCharacteristics::Linear,
9 => ravif::TransferCharacteristics::Log100,
10 => ravif::TransferCharacteristics::Log100Sqrt10,
11 => ravif::TransferCharacteristics::IEC61966,
12 => ravif::TransferCharacteristics::BT1361,
13 => ravif::TransferCharacteristics::SRGB,
14 => ravif::TransferCharacteristics::BT2020_10Bit,
15 => ravif::TransferCharacteristics::BT2020_12Bit,
16 => ravif::TransferCharacteristics::SMPTE2084,
18 => ravif::TransferCharacteristics::HLG,
_ => ravif::TransferCharacteristics::Unspecified,
}
}
fn resolve_bit_depth(configured: EncodeBitDepth, input_is_16bit: bool) -> ravif::BitDepth {
match configured {
EncodeBitDepth::Eight => ravif::BitDepth::Eight,
EncodeBitDepth::Ten => ravif::BitDepth::Ten,
EncodeBitDepth::Auto => {
if input_is_16bit {
ravif::BitDepth::Ten
} else {
ravif::BitDepth::Eight
}
}
}
}
fn build_ravif_encoder(
config: &EncoderConfig,
stop: almost_enough::StopToken,
input_is_16bit: bool,
) -> ravif::Encoder<'_> {
let mut enc = ravif::Encoder::new()
.with_quality(config.quality)
.with_speed(config.speed)
.with_bit_depth(resolve_bit_depth(config.bit_depth, input_is_16bit))
.with_internal_color_model(match config.color_model {
EncodeColorModel::YCbCr => ravif::ColorModel::YCbCr,
EncodeColorModel::Rgb => ravif::ColorModel::RGB,
})
.with_alpha_color_mode(match config.alpha_color_mode {
EncodeAlphaMode::UnassociatedClean => ravif::AlphaColorMode::UnassociatedClean,
EncodeAlphaMode::UnassociatedDirty => ravif::AlphaColorMode::UnassociatedDirty,
EncodeAlphaMode::Premultiplied => ravif::AlphaColorMode::Premultiplied,
})
.with_num_threads(config.threads);
if let Some(aq) = config.alpha_quality {
enc = enc.with_alpha_quality(aq);
}
if let Some(ref exif_data) = config.exif {
enc = enc.with_exif(exif_data.as_slice());
}
if let Some(ref xmp_data) = config.xmp {
enc = enc.with_xmp(xmp_data.clone());
}
if let Some(ref icc) = config.icc_profile {
enc = enc.with_icc_profile(icc.clone());
}
if let Some(angle) = config.rotation {
enc = enc.with_rotation(angle);
}
if let Some(axis) = config.mirror {
enc = enc.with_mirror(axis);
}
if let Some((max_cll, max_fall)) = config.content_light_level {
enc = enc.with_content_light(ravif::ContentLight {
max_content_light_level: max_cll,
max_frame_average_light_level: max_fall,
});
}
if let Some(md) = config.mastering_display {
enc = enc.with_mastering_display(ravif::MasteringDisplay {
primaries: [
ravif::ChromaticityPoint {
x: md.primaries[0].0,
y: md.primaries[0].1,
},
ravif::ChromaticityPoint {
x: md.primaries[1].0,
y: md.primaries[1].1,
},
ravif::ChromaticityPoint {
x: md.primaries[2].0,
y: md.primaries[2].1,
},
],
white_point: ravif::ChromaticityPoint {
x: md.white_point.0,
y: md.white_point.1,
},
max_luminance: md.max_luminance,
min_luminance: md.min_luminance,
});
}
if let Some(cp) = config.color_primaries {
enc = enc.with_color_primaries(cicp_to_color_primaries(cp));
}
if let Some(tc) = config.transfer_characteristics {
enc = enc.with_transfer_characteristics(cicp_to_transfer_characteristics(tc));
}
if let Some(pr) = config.pixel_range {
enc = enc.with_pixel_range(match pr {
EncodePixelRange::Full => ravif::PixelRange::Full,
EncodePixelRange::Limited => ravif::PixelRange::Limited,
});
}
if let Some(ref gm) = config.gain_map {
enc = enc.with_gain_map(ravif::GainMapData {
av1_data: gm.av1_data.clone(),
width: gm.width,
height: gm.height,
bit_depth: gm.bit_depth,
metadata: gm.metadata.clone(),
});
}
#[cfg(feature = "encode-imazen")]
{
let qm = config.enable_qm && !config.lossless;
enc = enc
.with_qm(qm)
.with_vaq(config.enable_vaq, config.vaq_strength)
.with_still_image_tuning(config.tune_still_image)
.with_lossless(config.lossless);
}
enc = enc.with_stop(stop);
enc
}
pub fn encode_rgb8(
img: ImgRef<'_, Rgb<u8>>,
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedImage> {
stop.check().map_err(|e| at!(Error::from(e)))?;
#[cfg(feature = "encode-svtav1")]
if config.backend == Av1Backend::Svtav1 {
return encode_rgb8_svtav1(img, config);
}
let enc = build_ravif_encoder(config, stop, false);
let result = enc
.encode_rgb(img)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedImage {
avif_file: result.avif_file,
color_byte_size: result.color_byte_size,
alpha_byte_size: result.alpha_byte_size,
})
}
#[cfg(feature = "encode-svtav1")]
fn encode_rgb8_svtav1(img: ImgRef<'_, Rgb<u8>>, config: &EncoderConfig) -> Result<EncodedImage> {
let w = img.width();
let h = img.height();
let mut y_plane = vec![0u8; w * h];
for (i, px) in img.pixels().enumerate() {
let y = (0.2126 * px.r as f64 + 0.7152 * px.g as f64 + 0.0722 * px.b as f64)
.round()
.clamp(0.0, 255.0) as u8;
y_plane[i] = y;
}
let mut enc = svtav1::avif::AvifEncoder::new()
.with_quality(config.quality)
.with_speed(config.speed);
match config.bit_depth {
EncodeBitDepth::Ten => {
enc = enc.with_bit_depth(10);
}
EncodeBitDepth::Eight => {
enc = enc.with_bit_depth(8);
}
_ => {}
}
if let (Some(cp), Some(tc), Some(mc)) = (
config.color_primaries,
config.transfer_characteristics,
config.matrix_coefficients,
) {
let full_range = config.pixel_range == Some(EncodePixelRange::Full);
enc = enc.with_color_space(cp, tc, mc, full_range);
}
let av1_data = enc
.encode_to_av1_obu(&y_plane, w as u32, h as u32, w as u32)
.map_err(|e| at!(Error::Encode(format!("svtav1: {e}"))))?;
let color_byte_size = av1_data.len();
Ok(EncodedImage {
avif_file: av1_data,
color_byte_size,
alpha_byte_size: 0,
})
}
pub fn encode_rgba8(
img: ImgRef<'_, Rgba<u8>>,
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedImage> {
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, false);
let result = enc
.encode_rgba(img)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedImage {
avif_file: result.avif_file,
color_byte_size: result.color_byte_size,
alpha_byte_size: result.alpha_byte_size,
})
}
pub fn encode_rgb16(
img: ImgRef<'_, Rgb<u16>>,
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedImage> {
use crate::convert::scale_from_u16;
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, true);
let width = img.width();
let height = img.height();
let pixels: Vec<[u16; 3]> = img
.pixels()
.map(|p| {
[
scale_from_u16(p.r, 10),
scale_from_u16(p.g, 10),
scale_from_u16(p.b, 10),
]
})
.collect();
let pixel_range = match config.pixel_range {
Some(EncodePixelRange::Limited) => ravif::PixelRange::Limited,
_ => ravif::PixelRange::Full,
};
let result = enc
.encode_raw_planes_10_bit(
width,
height,
pixels,
None::<std::iter::Empty<u16>>,
pixel_range,
ravif::MatrixCoefficients::Identity,
)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedImage {
avif_file: result.avif_file,
color_byte_size: result.color_byte_size,
alpha_byte_size: result.alpha_byte_size,
})
}
pub fn encode_rgba16(
img: ImgRef<'_, Rgba<u16>>,
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedImage> {
use crate::convert::scale_from_u16;
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, true);
let width = img.width();
let height = img.height();
let pixels: Vec<[u16; 3]> = img
.pixels()
.map(|p| {
[
scale_from_u16(p.r, 10),
scale_from_u16(p.g, 10),
scale_from_u16(p.b, 10),
]
})
.collect();
let alpha: Vec<u16> = img.pixels().map(|p| scale_from_u16(p.a, 10)).collect();
let pixel_range = match config.pixel_range {
Some(EncodePixelRange::Limited) => ravif::PixelRange::Limited,
_ => ravif::PixelRange::Full,
};
let result = enc
.encode_raw_planes_10_bit(
width,
height,
pixels,
Some(alpha),
pixel_range,
ravif::MatrixCoefficients::Identity,
)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedImage {
avif_file: result.avif_file,
color_byte_size: result.color_byte_size,
alpha_byte_size: result.alpha_byte_size,
})
}
#[derive(Clone)]
pub struct AnimationFrame {
pub pixels: ImgVec<RGB8>,
pub duration_ms: u32,
}
#[derive(Clone)]
pub struct AnimationFrameRgba {
pub pixels: ImgVec<RGBA8>,
pub duration_ms: u32,
}
#[non_exhaustive]
#[derive(Clone)]
pub struct EncodedAnimation {
pub avif_file: Vec<u8>,
pub frame_count: usize,
pub total_duration_ms: u64,
}
pub fn encode_animation_rgb8(
frames: &[AnimationFrame],
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedAnimation> {
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, false);
let ravif_frames: Vec<ravif::AnimFrame<'_>> = frames
.iter()
.map(|f| ravif::AnimFrame {
rgb: f.pixels.as_ref(),
duration_ms: f.duration_ms,
})
.collect();
let result = enc
.encode_animation_rgb(&ravif_frames)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedAnimation {
avif_file: result.avif_file,
frame_count: result.frame_count,
total_duration_ms: result.total_duration_ms,
})
}
pub fn encode_animation_rgba8(
frames: &[AnimationFrameRgba],
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedAnimation> {
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, false);
let ravif_frames: Vec<ravif::AnimFrameRgba<'_>> = frames
.iter()
.map(|f| ravif::AnimFrameRgba {
rgba: f.pixels.as_ref(),
duration_ms: f.duration_ms,
})
.collect();
let result = enc
.encode_animation_rgba(&ravif_frames)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedAnimation {
avif_file: result.avif_file,
frame_count: result.frame_count,
total_duration_ms: result.total_duration_ms,
})
}
#[derive(Clone)]
pub struct AnimationFrame16 {
pub pixels: ImgVec<RGB16>,
pub duration_ms: u32,
}
#[derive(Clone)]
pub struct AnimationFrameRgba16 {
pub pixels: ImgVec<RGBA16>,
pub duration_ms: u32,
}
pub fn encode_animation_rgb16(
frames: &[AnimationFrame16],
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedAnimation> {
use crate::convert::scale_from_u16;
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, true);
let scaled_frames: Vec<ImgVec<RGB16>> = frames
.iter()
.map(|f| {
let scaled: Vec<RGB16> = f
.pixels
.buf()
.iter()
.map(|p| RGB16 {
r: scale_from_u16(p.r, 10),
g: scale_from_u16(p.g, 10),
b: scale_from_u16(p.b, 10),
})
.collect();
ImgVec::new(scaled, f.pixels.width(), f.pixels.height())
})
.collect();
let ravif_frames: Vec<ravif::AnimFrame16<'_>> = scaled_frames
.iter()
.zip(frames.iter())
.map(|(scaled, orig)| ravif::AnimFrame16 {
rgb: scaled.as_ref(),
duration_ms: orig.duration_ms,
})
.collect();
let result = enc
.encode_animation_rgb16(&ravif_frames)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedAnimation {
avif_file: result.avif_file,
frame_count: result.frame_count,
total_duration_ms: result.total_duration_ms,
})
}
pub fn encode_animation_rgba16(
frames: &[AnimationFrameRgba16],
config: &EncoderConfig,
stop: almost_enough::StopToken,
) -> Result<EncodedAnimation> {
use crate::convert::scale_from_u16;
stop.check().map_err(|e| at!(Error::from(e)))?;
let enc = build_ravif_encoder(config, stop, true);
let scaled_frames: Vec<ImgVec<RGBA16>> = frames
.iter()
.map(|f| {
let scaled: Vec<RGBA16> = f
.pixels
.buf()
.iter()
.map(|p| RGBA16 {
r: scale_from_u16(p.r, 10),
g: scale_from_u16(p.g, 10),
b: scale_from_u16(p.b, 10),
a: scale_from_u16(p.a, 10),
})
.collect();
ImgVec::new(scaled, f.pixels.width(), f.pixels.height())
})
.collect();
let ravif_frames: Vec<ravif::AnimFrameRgba16<'_>> = scaled_frames
.iter()
.zip(frames.iter())
.map(|(scaled, orig)| ravif::AnimFrameRgba16 {
rgba: scaled.as_ref(),
duration_ms: orig.duration_ms,
})
.collect();
let result = enc
.encode_animation_rgba16(&ravif_frames)
.map_err(|e: ravif::Error| at!(Error::Encode(e.to_string())))?;
Ok(EncodedAnimation {
avif_file: result.avif_file,
frame_count: result.frame_count,
total_duration_ms: result.total_duration_ms,
})
}