#![allow(clippy::excessive_precision)]
#![deny(unreachable_pub)]
use std::mem::size_of;
mod aq;
mod cabac;
mod color;
mod cost;
mod dct;
mod deblock;
mod error;
mod fmt;
mod hevc;
mod hevc_transform;
mod intra;
mod isobmff;
mod math;
mod metadata;
mod pool;
mod sao;
mod ycgco;
mod yuv;
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
mod avx;
#[cfg(all(target_arch = "aarch64", feature = "neon"))]
mod neon;
pub use color::{Cicp, ColorMetadata, MatrixCoefficients, Primaries, TransferFunction};
pub use error::EncodeError;
pub use fmt::{BitDepth, ChromaFormat};
pub use metadata::{ContentLightLevel, Metadata, Orientation};
pub use yuv::Yuv;
const MIN_DIM: u32 = 1;
const MAX_DIM: u32 = 16_384;
const TILE_SIZE: u32 = 512;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum ParallelismStrategy {
Single,
Wpp,
TilesWpp,
Grid,
#[default]
GridWpp,
}
#[derive(Clone, Copy, Debug)]
pub struct VarianceBoost {
pub octile: u8,
pub strength: f32,
pub boost_only: bool,
}
impl Default for VarianceBoost {
fn default() -> Self {
Self {
octile: 6,
strength: 1.0,
boost_only: false,
}
}
}
impl ParallelismStrategy {
fn uses_grid(self) -> bool {
matches!(self, Self::Grid | Self::GridWpp)
}
fn grid_cell_wpp(self) -> bool {
matches!(self, Self::GridWpp)
}
fn single_wpp(self) -> bool {
matches!(self, Self::Wpp | Self::TilesWpp | Self::GridWpp)
}
fn single_tiles(self) -> bool {
matches!(self, Self::TilesWpp)
}
}
#[derive(Clone, Debug)]
pub struct EncodeConfig {
pub quality: u8,
pub lossless: bool,
pub chroma: ChromaFormat,
pub color: ColorMetadata,
pub metadata: Metadata,
pub threads: usize,
pub parallelism: ParallelismStrategy,
pub sao: bool,
pub variance_boost: VarianceBoost,
}
impl Default for EncodeConfig {
fn default() -> Self {
EncodeConfig {
quality: 90,
lossless: false,
chroma: ChromaFormat::Yuv420,
color: ColorMetadata::default(), metadata: Metadata::default(),
threads: 0, parallelism: ParallelismStrategy::GridWpp,
sao: true,
variance_boost: VarianceBoost::default(),
}
}
}
impl EncodeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_quality(mut self, quality: u8) -> Self {
self.quality = quality;
self
}
pub fn with_parallelism(mut self, parallelism: ParallelismStrategy) -> Self {
self.parallelism = parallelism;
self
}
pub fn with_lossless(mut self, lossless: bool) -> Self {
self.lossless = lossless;
self
}
pub fn with_sao(mut self, sao: bool) -> Self {
self.sao = sao;
self
}
pub fn with_variance_boost(mut self, octile: u8, strength: f32, boost_only: bool) -> Self {
self.variance_boost = VarianceBoost {
octile,
strength,
boost_only,
};
self
}
pub fn with_chroma(mut self, chroma: ChromaFormat) -> Self {
self.chroma = chroma;
self
}
pub fn with_color(mut self, color: ColorMetadata) -> Self {
self.color = color;
self
}
pub fn with_icc_profile(mut self, icc: Vec<u8>) -> Self {
self.color.icc = Some(icc);
self
}
pub fn with_cicp(mut self, enc: Cicp) -> Self {
self.color.cicp = Some(enc);
self
}
pub fn with_metadata(mut self, metadata: Metadata) -> Self {
self.metadata = metadata;
self
}
pub fn with_orientation(mut self, o: Orientation) -> Self {
self.metadata.orientation = o;
self
}
pub fn with_content_light_level(mut self, cll: ContentLightLevel) -> Self {
self.metadata.content_light_level = Some(cll);
self
}
pub fn with_exif(mut self, exif: Vec<u8>) -> Self {
self.metadata.exif = Some(exif);
self
}
pub fn with_threads(mut self, threads: usize) -> Self {
self.threads = threads;
self
}
fn validate(&self) -> Result<(), EncodeError> {
validate_quality(self.quality)?;
if !(1..=8).contains(&self.variance_boost.octile)
|| !self.variance_boost.strength.is_finite()
|| !(0.0..=4.0).contains(&self.variance_boost.strength)
{
return Err(EncodeError::InvalidInput);
}
Ok(())
}
}
pub fn encode_rgb(
rgb: &[u8],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u8(rgb, width, height, 3)?;
let wide: Vec<u16> = rgb.iter().map(|&b| b as u16).collect();
encode_rgb_wide(&wide, width, height, BitDepth::Eight, cfg)
}
pub fn encode_rgba(
rgba: &[u8],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u8(rgba, width, height, 4)?;
let wide: Vec<u16> = rgba
.as_chunks::<4>()
.0
.iter()
.flat_map(|px| [px[0] as u16, px[1] as u16, px[2] as u16])
.collect();
encode_rgb_wide(&wide, width, height, BitDepth::Eight, cfg)
}
pub fn encode_rgba_with_alpha(
rgba: &[u8],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u8(rgba, width, height, 4)?;
let wide: Vec<u16> = rgba.iter().map(|&b| b as u16).collect();
encode_rgba_with_alpha_wide(&wide, width, height, BitDepth::Eight, cfg)
}
pub fn encode_rgb10(
rgb: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(rgb, width, height, 3)?;
encode_rgb_wide(rgb, width, height, BitDepth::Ten, cfg)
}
pub fn encode_rgba10(
rgba: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(rgba, width, height, 4)?;
let rgb: Vec<u16> = rgba
.as_chunks::<4>()
.0
.iter()
.flat_map(|px| [px[0], px[1], px[2]])
.collect();
encode_rgb_wide(&rgb, width, height, BitDepth::Ten, cfg)
}
pub fn encode_rgba10_with_alpha(
rgba: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(rgba, width, height, 4)?;
encode_rgba_with_alpha_wide(rgba, width, height, BitDepth::Ten, cfg)
}
pub fn encode_rgb12(
rgb: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(rgb, width, height, 3)?;
encode_rgb_wide(rgb, width, height, BitDepth::Twelve, cfg)
}
pub fn encode_rgba12(
rgba: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(rgba, width, height, 4)?;
let rgb: Vec<u16> = rgba
.as_chunks::<4>()
.0
.iter()
.flat_map(|px| [px[0], px[1], px[2]])
.collect();
encode_rgb_wide(&rgb, width, height, BitDepth::Twelve, cfg)
}
pub fn encode_rgba12_with_alpha(
rgba: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(rgba, width, height, 4)?;
encode_rgba_with_alpha_wide(rgba, width, height, BitDepth::Twelve, cfg)
}
pub fn encode_gray(
gray: &[u8],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u8(gray, width, height, 1)?;
let wide: Vec<u16> = gray.iter().map(|&b| b as u16).collect();
encode_gray_wide(&wide, width, height, BitDepth::Eight, cfg)
}
pub fn encode_gray_alpha(
ya: &[u8],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u8(ya, width, height, 2)?;
let wide: Vec<u16> = ya
.as_chunks::<2>()
.0
.iter()
.map(|px| px[0] as u16)
.collect();
encode_gray_wide(&wide, width, height, BitDepth::Eight, cfg)
}
pub fn encode_gray_alpha_with_alpha(
ya: &[u8],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u8(ya, width, height, 2)?;
let wide: Vec<u16> = ya.iter().map(|&b| b as u16).collect();
encode_gray_alpha_wide(&wide, width, height, BitDepth::Eight, cfg)
}
pub fn encode_gray10(
gray: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(gray, width, height, 1)?;
encode_gray_wide(gray, width, height, BitDepth::Ten, cfg)
}
pub fn encode_gray_alpha10(
ya: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(ya, width, height, 2)?;
let luma: Vec<u16> = ya.as_chunks::<2>().0.iter().map(|px| px[0]).collect();
encode_gray_wide(&luma, width, height, BitDepth::Ten, cfg)
}
pub fn encode_gray_alpha10_with_alpha(
ya: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(ya, width, height, 2)?;
encode_gray_alpha_wide(ya, width, height, BitDepth::Ten, cfg)
}
pub fn encode_gray12(
gray: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(gray, width, height, 1)?;
encode_gray_wide(gray, width, height, BitDepth::Twelve, cfg)
}
pub fn encode_gray_alpha12(
ya: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(ya, width, height, 2)?;
let luma: Vec<u16> = ya.as_chunks::<2>().0.iter().map(|px| px[0]).collect();
encode_gray_wide(&luma, width, height, BitDepth::Twelve, cfg)
}
pub fn encode_gray_alpha12_with_alpha(
ya: &[u16],
width: u32,
height: u32,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
cfg.validate()?;
validate_buf_u16(ya, width, height, 2)?;
encode_gray_alpha_wide(ya, width, height, BitDepth::Twelve, cfg)
}
pub fn encode_yuv(yuv: &Yuv, cfg: &EncodeConfig) -> Result<Vec<u8>, EncodeError> {
yuv.validate()?;
cfg.validate()?;
if needs_tiling(yuv.display_w, yuv.display_h) && cfg.parallelism.uses_grid() {
return encode_yuv_tiled(yuv, cfg);
}
encode_yuv_raw(yuv, cfg)
}
fn needs_tiling(width: u32, height: u32) -> bool {
width > TILE_SIZE || height > TILE_SIZE
}
fn encode_rgb_wide(
rgb: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
let mut local_cfg = cfg.clone();
if local_cfg.lossless {
local_cfg.chroma = ChromaFormat::Yuv444;
force_identity_matrix(&mut local_cfg.color);
}
let cfg = &local_cfg;
if needs_tiling(width, height) && cfg.parallelism.uses_grid() {
return encode_rgb_tiled(rgb, width, height, bit_depth, cfg);
}
let (enc_w, enc_h) = encoded_dims(width, height, cfg.chroma);
let mut yuv = if enc_w != width || enc_h != height {
let padded = pad_buf::<3>(rgb, width, height, enc_w, enc_h);
if cfg.lossless {
ycgco::rgb_to_gbr(&padded, enc_w, enc_h, cfg.chroma, bit_depth)
} else {
yuv::rgb_to_yuv(&padded, enc_w, enc_h, cfg.chroma, bit_depth)
}
} else if cfg.lossless {
ycgco::rgb_to_gbr(rgb, width, height, cfg.chroma, bit_depth)
} else {
yuv::rgb_to_yuv(rgb, width, height, cfg.chroma, bit_depth)
};
yuv = yuv.with_display(width, height);
encode_yuv_raw(&yuv, cfg)
}
fn force_identity_matrix(color: &mut ColorMetadata) {
let base = color.cicp.unwrap_or_else(Cicp::srgb);
color.cicp = Some(Cicp {
primaries: base.primaries,
transfer: base.transfer,
matrix: MatrixCoefficients::Identity,
full_range: true,
});
}
fn encode_rgba_with_alpha_wide(
rgba: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
if needs_tiling(width, height) {
return encode_rgba_alpha_tiled(rgba, width, height, bit_depth, cfg);
}
let (enc_w, enc_h) = encoded_dims(width, height, cfg.chroma);
let (w, h) = (width as usize, height as usize);
let (nw, nh) = (enc_w as usize, enc_h as usize);
let mut color_buf = vec![0u16; nw * nh * 3];
let mut alpha_plane = vec![0u16; nw * nh];
for (dst_row_idx, (col_row, alp_row)) in color_buf
.chunks_exact_mut(nw * 3)
.zip(alpha_plane.chunks_exact_mut(nw))
.enumerate()
{
let sr = dst_row_idx.min(h - 1);
let src_row = &rgba[sr * w * 4..(sr * w + w) * 4];
for (dst_col_idx, (col_px, alp_px)) in col_row
.as_chunks_mut::<3>()
.0
.iter_mut()
.zip(alp_row.iter_mut())
.enumerate()
{
let sc = dst_col_idx.min(w - 1);
let src = &src_row[sc * 4..sc * 4 + 4];
col_px.copy_from_slice(&src[..3]);
*alp_px = src[3];
}
}
let color_yuv = yuv::rgb_to_yuv(&color_buf, enc_w, enc_h, cfg.chroma, bit_depth);
let color_stream = hevc::encode_intra(
&color_yuv,
enc_w,
enc_h,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.sao,
cfg.variance_boost,
)?;
let alpha_yuv = build_mono_yuv(alpha_plane, enc_w, enc_h, width, height, bit_depth);
let alpha_stream = hevc::encode_intra(
&alpha_yuv,
enc_w,
enc_h,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.sao,
cfg.variance_boost,
)?;
isobmff::wrap_hevc_image_with_alpha(
&color_stream,
&alpha_stream,
width,
height,
isobmff::ImageMeta {
bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_gray_wide(
gray: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
validate_buf_u16(gray, width, height, 1)?;
if needs_tiling(width, height) {
return encode_gray_tiled(gray, width, height, bit_depth, cfg);
}
let (enc_w, enc_h) = encoded_dims(width, height, ChromaFormat::Monochrome);
let luma = if enc_w != width || enc_h != height {
pad_buf::<1>(gray, width, height, enc_w, enc_h)
} else {
gray.to_vec()
};
let yuv = build_mono_yuv(luma, enc_w, enc_h, width, height, bit_depth);
encode_yuv_raw(&yuv, cfg)
}
fn encode_gray_alpha_wide(
ya: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
validate_buf_u16(ya, width, height, 2)?;
if needs_tiling(width, height) {
return encode_gray_alpha_tiled(ya, width, height, bit_depth, cfg);
}
let (enc_w, enc_h) = encoded_dims(width, height, ChromaFormat::Monochrome);
let (w, h) = (width as usize, height as usize);
let (nw, nh) = (enc_w as usize, enc_h as usize);
let mut luma_plane = vec![0u16; nw * nh];
let mut alpha_plane = vec![0u16; nw * nh];
for (dst_row_idx, (luma_row, alp_row)) in luma_plane
.chunks_exact_mut(nw)
.zip(alpha_plane.chunks_exact_mut(nw))
.enumerate()
{
let sr = dst_row_idx.min(h - 1);
let src_row = &ya[sr * w * 2..(sr * w + w) * 2];
for (dst_col_idx, (luma_px, alp_px)) in
luma_row.iter_mut().zip(alp_row.iter_mut()).enumerate()
{
let sc = dst_col_idx.min(w - 1);
*luma_px = src_row[sc * 2];
*alp_px = src_row[sc * 2 + 1];
}
}
let color_yuv = build_mono_yuv(luma_plane, enc_w, enc_h, width, height, bit_depth);
let color_stream = hevc::encode_intra(
&color_yuv,
enc_w,
enc_h,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.sao,
cfg.variance_boost,
)?;
let alpha_yuv = build_mono_yuv(alpha_plane, enc_w, enc_h, width, height, bit_depth);
let alpha_stream = hevc::encode_intra(
&alpha_yuv,
enc_w,
enc_h,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.sao,
cfg.variance_boost,
)?;
isobmff::wrap_hevc_image_with_alpha(
&color_stream,
&alpha_stream,
width,
height,
isobmff::ImageMeta {
bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
pub fn encode_yuv_with_alpha(
yuv: &Yuv,
alpha: &[u16],
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
yuv.validate()?;
cfg.validate()?;
if alpha.len() != yuv.y.len() {
return Err(EncodeError::InvalidInput);
}
if needs_tiling(yuv.display_w, yuv.display_h) {
return encode_yuv_alpha_tiled(yuv, alpha, cfg);
}
let color_stream = hevc::encode_intra(
yuv,
yuv.width,
yuv.height,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.sao,
cfg.variance_boost,
)?;
let alpha_yuv = build_mono_yuv(
alpha.to_vec(),
yuv.width,
yuv.height,
yuv.display_w,
yuv.display_h,
yuv.bit_depth,
);
let alpha_stream = hevc::encode_intra(
&alpha_yuv,
yuv.width,
yuv.height,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.sao,
cfg.variance_boost,
)?;
isobmff::wrap_hevc_image_with_alpha(
&color_stream,
&alpha_stream,
yuv.display_w,
yuv.display_h,
isobmff::ImageMeta {
bit_depth: yuv.bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_yuv_raw(yuv: &Yuv, cfg: &EncodeConfig) -> Result<Vec<u8>, EncodeError> {
let nalu_stream = hevc::encode_intra_opts(
yuv,
yuv.width,
yuv.height,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cfg.parallelism.single_wpp(),
cfg.parallelism.single_tiles(),
resolve_threads(cfg.threads),
cfg.sao,
cfg.variance_boost,
)?;
isobmff::wrap_hevc_image(
&nalu_stream,
yuv.display_w,
yuv.display_h,
isobmff::ImageMeta {
bit_depth: yuv.bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn build_mono_yuv(
y: Vec<u16>,
coded_w: u32,
coded_h: u32,
display_w: u32,
display_h: u32,
bit_depth: BitDepth,
) -> Yuv {
Yuv {
y,
cb: Vec::new(),
cr: Vec::new(),
width: coded_w,
height: coded_h,
display_w,
display_h,
chroma: ChromaFormat::Monochrome,
bit_depth,
}
}
fn resolve_threads(preferred: usize) -> usize {
if preferred != 0 {
preferred
} else {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
}
}
fn parallel_try_map<T, E, F>(n: usize, threads: usize, f: F) -> Result<Vec<T>, E>
where
T: Send,
E: Send,
F: Fn(usize, usize) -> Result<T, E> + Sync,
{
if n == 0 {
return Ok(Vec::new());
}
let total_threads = resolve_threads(threads);
let nthreads = total_threads.clamp(1, n);
if nthreads == 1 {
return (0..n).map(|index| f(index, total_threads)).collect();
}
let threads_per_item = total_threads / nthreads;
let extra_threads = total_threads % nthreads;
let mut slots: Vec<Option<Result<T, E>>> = (0..n).map(|_| None).collect();
let f = &f;
let pool = pool::ThreadPool::new(nthreads.saturating_sub(1));
pool.scoped(|scope| {
for (i, slot) in slots.iter_mut().enumerate() {
let item_threads = threads_per_item + usize::from(i < extra_threads);
scope.spawn(move || {
*slot = Some(f(i, item_threads));
});
}
});
let mut out = Vec::with_capacity(n);
for slot in slots {
out.push(slot.expect("scoped tasks fill every slot")?);
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn encode_cell(
yuv: &Yuv,
w: u32,
h: u32,
quality: u8,
lossless: bool,
cicp: Option<Cicp>,
cell_wpp: bool,
threads: usize,
sao: bool,
variance_boost: VarianceBoost,
) -> Result<hevc::NaluStream, EncodeError> {
if cell_wpp {
let wpp_threads = threads.min(h.div_ceil(64) as usize).max(1);
hevc::encode_intra_opts(
yuv,
w,
h,
quality,
lossless,
cicp,
true,
false,
wpp_threads,
sao,
variance_boost,
)
} else {
hevc::encode_intra(yuv, w, h, quality, lossless, cicp, sao, variance_boost)
}
}
fn encode_rgb_tiled(
rgb: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
encode_cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
let mut cfg = encode_cfg.clone();
let cell_wpp = cfg.parallelism.grid_cell_wpp();
let cols = width.div_ceil(TILE_SIZE);
let rows = height.div_ceil(TILE_SIZE);
let (enc_tw, enc_th) = encoded_dims(TILE_SIZE, TILE_SIZE, cfg.chroma);
if cfg.lossless {
cfg.chroma = ChromaFormat::Yuv444;
force_identity_matrix(&mut cfg.color);
}
let n = (cols * rows) as usize;
let tile_streams = parallel_try_map(n, cfg.threads, |idx, cell_threads| {
let row = idx as u32 / cols;
let col = idx as u32 % cols;
let tile = extract_rgb_tile(rgb, width, height, col, row, TILE_SIZE, 3);
let yuv = if cfg.lossless {
ycgco::rgb_to_gbr(&tile, enc_tw, enc_th, cfg.chroma, bit_depth)
} else {
yuv::rgb_to_yuv(&tile, enc_tw, enc_th, cfg.chroma, bit_depth)
};
encode_cell(
&yuv,
enc_tw,
enc_th,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)
})?;
isobmff::wrap_hevc_grid(
&tile_streams,
isobmff::GridDims {
cols,
rows,
tile_w: enc_tw,
tile_h: enc_th,
full_w: width,
full_h: height,
},
isobmff::ImageMeta {
bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_gray_tiled(
gray: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
validate_buf_u16(gray, width, height, 1)?;
let cols = width.div_ceil(TILE_SIZE);
let rows = height.div_ceil(TILE_SIZE);
let cell_wpp = cfg.parallelism.grid_cell_wpp();
let n = (cols * rows) as usize;
let tile_streams = parallel_try_map(n, cfg.threads, |idx, cell_threads| {
let row = idx as u32 / cols;
let col = idx as u32 % cols;
let luma = extract_plane_tile(
gray,
width as usize,
height as usize,
(col * TILE_SIZE) as usize,
(row * TILE_SIZE) as usize,
TILE_SIZE as usize,
TILE_SIZE as usize,
);
let yuv = build_mono_yuv(luma, TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE, bit_depth);
encode_cell(
&yuv,
TILE_SIZE,
TILE_SIZE,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)
})?;
isobmff::wrap_hevc_grid(
&tile_streams,
isobmff::GridDims {
cols,
rows,
tile_w: TILE_SIZE,
tile_h: TILE_SIZE,
full_w: width,
full_h: height,
},
isobmff::ImageMeta {
bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_yuv_alpha_tiled(
yuv: &Yuv,
alpha: &[u16],
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
let cell_wpp = cfg.parallelism.grid_cell_wpp();
let cols = yuv.display_w.div_ceil(TILE_SIZE);
let rows = yuv.display_h.div_ceil(TILE_SIZE);
let (enc_tw, enc_th) = encoded_dims(TILE_SIZE, TILE_SIZE, yuv.chroma);
let sw = yuv.chroma.sub_w() as u32;
let sh = yuv.chroma.sub_h() as u32;
let c_tw = (enc_tw / sw) as usize;
let c_th = (enc_th / sh) as usize;
let c_src_w = (yuv.width / sw) as usize;
let c_src_h = (yuv.height / sh) as usize;
let n = (cols * rows) as usize;
let pairs = parallel_try_map(n, cfg.threads, |idx, cell_threads| {
let row = idx as u32 / cols;
let col = idx as u32 % cols;
let x0 = (col * TILE_SIZE) as usize;
let y0 = (row * TILE_SIZE) as usize;
let y_tile = extract_plane_tile(
&yuv.y,
yuv.width as usize,
yuv.height as usize,
x0,
y0,
enc_tw as usize,
enc_th as usize,
);
let (cb_tile, cr_tile) = if yuv.chroma.is_monochrome() {
(Vec::new(), Vec::new())
} else {
let cx0 = x0 / sw as usize;
let cy0 = y0 / sh as usize;
(
extract_plane_tile(&yuv.cb, c_src_w, c_src_h, cx0, cy0, c_tw, c_th),
extract_plane_tile(&yuv.cr, c_src_w, c_src_h, cx0, cy0, c_tw, c_th),
)
};
let tile_yuv = Yuv {
y: y_tile,
cb: cb_tile,
cr: cr_tile,
width: enc_tw,
height: enc_th,
display_w: enc_tw,
display_h: enc_th,
chroma: yuv.chroma,
bit_depth: yuv.bit_depth,
};
let color = encode_cell(
&tile_yuv,
enc_tw,
enc_th,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)?;
let alpha_tile = extract_plane_tile(
alpha,
yuv.width as usize,
yuv.height as usize,
x0,
y0,
enc_tw as usize,
enc_th as usize,
);
let alpha_yuv = build_mono_yuv(alpha_tile, enc_tw, enc_th, enc_tw, enc_th, yuv.bit_depth);
let alpha = encode_cell(
&alpha_yuv,
enc_tw,
enc_th,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)?;
Ok::<_, EncodeError>((color, alpha))
})?;
let mut color_streams = Vec::with_capacity(n);
let mut alpha_streams = Vec::with_capacity(n);
for (color, alpha) in pairs {
color_streams.push(color);
alpha_streams.push(alpha);
}
isobmff::wrap_hevc_grid_with_alpha(
&color_streams,
&alpha_streams,
isobmff::GridDims {
cols,
rows,
tile_w: enc_tw,
tile_h: enc_th,
full_w: yuv.display_w,
full_h: yuv.display_h,
},
isobmff::ImageMeta {
bit_depth: yuv.bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_yuv_tiled(yuv: &Yuv, cfg: &EncodeConfig) -> Result<Vec<u8>, EncodeError> {
let cell_wpp = cfg.parallelism.grid_cell_wpp();
let cols = yuv.display_w.div_ceil(TILE_SIZE);
let rows = yuv.display_h.div_ceil(TILE_SIZE);
let (enc_tw, enc_th) = encoded_dims(TILE_SIZE, TILE_SIZE, yuv.chroma);
let sw = yuv.chroma.sub_w() as u32;
let sh = yuv.chroma.sub_h() as u32;
let c_tw = (enc_tw / sw) as usize;
let c_th = (enc_th / sh) as usize;
let c_src_w = (yuv.width / sw) as usize;
let c_src_h = (yuv.height / sh) as usize;
let n = (cols * rows) as usize;
let tile_streams = parallel_try_map(n, cfg.threads, |idx, cell_threads| {
let row = idx as u32 / cols;
let col = idx as u32 % cols;
let x0 = (col * TILE_SIZE) as usize;
let y0 = (row * TILE_SIZE) as usize;
let y_tile = extract_plane_tile(
&yuv.y,
yuv.width as usize,
yuv.height as usize,
x0,
y0,
enc_tw as usize,
enc_th as usize,
);
let (cb_tile, cr_tile) = if yuv.chroma.is_monochrome() {
(Vec::new(), Vec::new())
} else {
let cx0 = x0 / sw as usize;
let cy0 = y0 / sh as usize;
(
extract_plane_tile(&yuv.cb, c_src_w, c_src_h, cx0, cy0, c_tw, c_th),
extract_plane_tile(&yuv.cr, c_src_w, c_src_h, cx0, cy0, c_tw, c_th),
)
};
let tile_yuv = Yuv {
y: y_tile,
cb: cb_tile,
cr: cr_tile,
width: enc_tw,
height: enc_th,
display_w: enc_tw,
display_h: enc_th,
chroma: yuv.chroma,
bit_depth: yuv.bit_depth,
};
encode_cell(
&tile_yuv,
enc_tw,
enc_th,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)
})?;
isobmff::wrap_hevc_grid(
&tile_streams,
isobmff::GridDims {
cols,
rows,
tile_w: enc_tw,
tile_h: enc_th,
full_w: yuv.display_w,
full_h: yuv.display_h,
},
isobmff::ImageMeta {
bit_depth: yuv.bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_rgba_alpha_tiled(
rgba: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
let cell_wpp = cfg.parallelism.grid_cell_wpp();
let cols = width.div_ceil(TILE_SIZE);
let rows = height.div_ceil(TILE_SIZE);
let (enc_tw, enc_th) = encoded_dims(TILE_SIZE, TILE_SIZE, cfg.chroma);
let ts2 = (TILE_SIZE * TILE_SIZE) as usize;
let n = (cols * rows) as usize;
let pairs = parallel_try_map(n, cfg.threads, |idx, cell_threads| {
let row = idx as u32 / cols;
let col = idx as u32 % cols;
let tile = extract_rgb_tile(rgba, width, height, col, row, TILE_SIZE, 4);
let mut color_buf = vec![0u16; ts2 * 3];
let mut alpha_plane = vec![0u16; ts2];
for ((px, colors), alpha) in tile
.as_chunks::<4>()
.0
.iter()
.zip(color_buf.as_chunks_mut::<3>().0.iter_mut())
.zip(alpha_plane.iter_mut())
{
colors[0] = px[0];
colors[1] = px[1];
colors[2] = px[2];
*alpha = px[3];
}
let color_yuv = yuv::rgb_to_yuv(&color_buf, enc_tw, enc_th, cfg.chroma, bit_depth);
let color = encode_cell(
&color_yuv,
enc_tw,
enc_th,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)?;
let alpha_yuv = build_mono_yuv(
alpha_plane,
TILE_SIZE,
TILE_SIZE,
TILE_SIZE,
TILE_SIZE,
bit_depth,
);
let alpha = encode_cell(
&alpha_yuv,
TILE_SIZE,
TILE_SIZE,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)?;
Ok::<_, EncodeError>((color, alpha))
})?;
let mut color_streams = Vec::with_capacity(n);
let mut alpha_streams = Vec::with_capacity(n);
for (color, alpha) in pairs {
color_streams.push(color);
alpha_streams.push(alpha);
}
isobmff::wrap_hevc_grid_with_alpha(
&color_streams,
&alpha_streams,
isobmff::GridDims {
cols,
rows,
tile_w: enc_tw,
tile_h: enc_th,
full_w: width,
full_h: height,
},
isobmff::ImageMeta {
bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn encode_gray_alpha_tiled(
ya: &[u16],
width: u32,
height: u32,
bit_depth: BitDepth,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(width, height)?;
validate_buf_u16(ya, width, height, 2)?;
let cols = width.div_ceil(TILE_SIZE);
let rows = height.div_ceil(TILE_SIZE);
let cell_wpp = cfg.parallelism.grid_cell_wpp();
let ts2 = (TILE_SIZE * TILE_SIZE) as usize;
let n = (cols * rows) as usize;
let pairs = parallel_try_map(n, cfg.threads, |idx, cell_threads| {
let row = idx as u32 / cols;
let col = idx as u32 % cols;
let tile = extract_rgb_tile(ya, width, height, col, row, TILE_SIZE, 2);
let mut luma_plane = vec![0u16; ts2];
let mut alpha_plane = vec![0u16; ts2];
for ((px, luma), alpha) in tile
.as_chunks::<2>()
.0
.iter()
.zip(luma_plane.iter_mut())
.zip(alpha_plane.iter_mut())
{
*luma = px[0];
*alpha = px[1];
}
let luma_yuv = build_mono_yuv(
luma_plane, TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE, bit_depth,
);
let luma = encode_cell(
&luma_yuv,
TILE_SIZE,
TILE_SIZE,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)?;
let alpha_yuv = build_mono_yuv(
alpha_plane,
TILE_SIZE,
TILE_SIZE,
TILE_SIZE,
TILE_SIZE,
bit_depth,
);
let alpha = encode_cell(
&alpha_yuv,
TILE_SIZE,
TILE_SIZE,
cfg.quality,
cfg.lossless,
cfg.color.cicp,
cell_wpp,
cell_threads,
cfg.sao,
cfg.variance_boost,
)?;
Ok::<_, EncodeError>((luma, alpha))
})?;
let mut luma_streams = Vec::with_capacity(n);
let mut alpha_streams = Vec::with_capacity(n);
for (luma, alpha) in pairs {
luma_streams.push(luma);
alpha_streams.push(alpha);
}
isobmff::wrap_hevc_grid_with_alpha(
&luma_streams,
&alpha_streams,
isobmff::GridDims {
cols,
rows,
tile_w: TILE_SIZE,
tile_h: TILE_SIZE,
full_w: width,
full_h: height,
},
isobmff::ImageMeta {
bit_depth,
color_meta: &cfg.color,
metadata: &cfg.metadata,
},
)
}
fn extract_rgb_tile(
src: &[u16],
src_w: u32,
src_h: u32,
col: u32,
row: u32,
tile_size: u32,
channels: usize,
) -> Vec<u16> {
let (sw, sh) = (src_w as usize, src_h as usize);
let ts = tile_size as usize;
let x0 = (col * tile_size) as usize;
let y0 = (row * tile_size) as usize;
let mut tile = vec![0u16; ts * ts * channels];
for ty in 0..ts {
let sy = (y0 + ty).min(sh - 1);
let src_row = &src[sy * sw * channels..(sy * sw + sw) * channels];
let dst_row = &mut tile[ty * ts * channels..(ty * ts + ts) * channels];
for tx in 0..ts {
let sx = (x0 + tx).min(sw - 1);
dst_row[tx * channels..(tx + 1) * channels]
.copy_from_slice(&src_row[sx * channels..(sx + 1) * channels]);
}
}
tile
}
fn extract_plane_tile(
plane: &[u16],
src_w: usize,
src_h: usize,
x0: usize,
y0: usize,
tile_w: usize,
tile_h: usize,
) -> Vec<u16> {
let mut tile = vec![0u16; tile_w * tile_h];
for ty in 0..tile_h {
let sy = (y0 + ty).min(src_h - 1);
let src_row = &plane[sy * src_w..(sy + 1) * src_w];
let dst_row = &mut tile[ty * tile_w..(ty + 1) * tile_w];
for tx in 0..tile_w {
dst_row[tx] = src_row[(x0 + tx).min(src_w - 1)];
}
}
tile
}
pub(crate) fn validate_dims(width: u32, height: u32) -> Result<(), EncodeError> {
if width < MIN_DIM || height < MIN_DIM || width > MAX_DIM || height > MAX_DIM {
return Err(EncodeError::InvalidDimensions { width, height });
}
Ok(())
}
fn validate_quality(quality: u8) -> Result<(), EncodeError> {
if quality == 0 || quality > 100 {
return Err(EncodeError::InvalidInput);
}
Ok(())
}
fn validate_buf_u8(buf: &[u8], w: u32, h: u32, ch: usize) -> Result<(), EncodeError> {
let needed = checked_buffer_size::<u8>(w as usize, h as usize, ch)?;
if buf.len() != needed {
return Err(EncodeError::InvalidInput);
}
Ok(())
}
fn validate_buf_u16(buf: &[u16], w: u32, h: u32, ch: usize) -> Result<(), EncodeError> {
let needed = checked_buffer_size::<u16>(w as usize, h as usize, ch)?;
if buf.len() != needed {
return Err(EncodeError::InvalidInput);
}
Ok(())
}
pub(crate) fn checked_buffer_size<T>(
width: usize,
height: usize,
channels: usize,
) -> Result<usize, EncodeError> {
let pixel_size = size_of::<T>();
width
.checked_mul(height)
.and_then(|v| v.checked_mul(channels))
.and_then(|v| {
v.checked_mul(pixel_size)
.and_then(|b| isize::try_from(b).ok())?;
Some(v)
})
.ok_or(EncodeError::InvalidInput)
}
fn encoded_dims(width: u32, height: u32, chroma: ChromaFormat) -> (u32, u32) {
let sw = chroma.sub_w() as u32;
let sh = chroma.sub_h() as u32;
(width.div_ceil(sw) * sw, height.div_ceil(sh) * sh)
}
fn pad_buf<const N: usize>(src: &[u16], w: u32, h: u32, nw: u32, nh: u32) -> Vec<u16> {
let (w, h, nw, nh) = (w as usize, h as usize, nw as usize, nh as usize);
let src_stride = w * N;
let dst_stride = nw * N;
let mut out = vec![0u16; nw * nh * N];
for (dst_row_idx, dst_row) in out.chunks_exact_mut(dst_stride).enumerate() {
let sr = dst_row_idx.min(h - 1);
let src_row = &src[sr * src_stride..(sr + 1) * src_stride];
let (real, pad) = dst_row.split_at_mut(src_stride);
real.copy_from_slice(src_row);
if !pad.is_empty() {
let last_px = &src_row[src_row.len() - N..];
for px in pad.as_chunks_mut::<N>().0.iter_mut() {
px.copy_from_slice(last_px);
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grid_thread_budgets_use_capacity_without_oversubscription() {
let four_cells = parallel_try_map::<usize, (), _>(4, 10, |_, budget| Ok(budget)).unwrap();
assert_eq!(four_cells, [3, 3, 2, 2]);
assert_eq!(four_cells.iter().sum::<usize>(), 10);
let many_cells = parallel_try_map::<usize, (), _>(12, 8, |_, budget| Ok(budget)).unwrap();
assert_eq!(many_cells, [1; 12]);
let one_cell = parallel_try_map::<usize, (), _>(1, 8, |_, budget| Ok(budget)).unwrap();
assert_eq!(one_cell, [8]);
}
fn cfg() -> EncodeConfig {
EncodeConfig::new()
}
#[test]
fn rejects_zero_dims() {
assert!(validate_dims(0, 1).is_err());
assert!(validate_dims(1, 0).is_err());
}
#[test]
fn rejects_oversized_dims() {
assert!(validate_dims(MAX_DIM + 1, 1).is_err());
assert!(validate_dims(1, MAX_DIM + 1).is_err());
}
#[test]
fn rejects_quality_bounds() {
assert!(cfg().with_quality(0).validate().is_err());
assert!(cfg().with_quality(101).validate().is_err());
assert!(cfg().with_quality(1).validate().is_ok());
assert!(cfg().with_quality(100).validate().is_ok());
}
#[test]
fn validates_variance_boost_options() {
assert!(cfg().with_variance_boost(6, 2.0, false).validate().is_ok());
assert!(cfg().with_variance_boost(0, 2.0, false).validate().is_err());
assert!(cfg().with_variance_boost(9, 2.0, false).validate().is_err());
assert!(
cfg()
.with_variance_boost(6, -1.0, false)
.validate()
.is_err()
);
assert!(
cfg()
.with_variance_boost(6, f32::NAN, false)
.validate()
.is_err()
);
}
#[test]
fn rejects_wrong_buffer_size() {
assert!(encode_rgb(&vec![0u8; 46], 4, 4, &cfg()).is_err());
assert!(encode_rgb(&vec![0u8; 49], 4, 4, &cfg()).is_err());
assert!(encode_rgb(&vec![0u8; 48], 4, 4, &cfg()).is_ok());
}
#[test]
fn encode_rgb8_produces_heic() {
let out = encode_rgb(&vec![100u8; 16 * 16 * 3], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_rgb8_without_sao_produces_heic() {
let pixels: Vec<u8> = (0..128 * 128 * 3).map(|i| (i & 255) as u8).collect();
let out = encode_rgb(&pixels, 128, 128, &cfg().with_quality(30).with_sao(false)).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_rgba8_strips_alpha() {
let out = encode_rgba(&vec![100u8; 16 * 16 * 4], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_rgba8_with_alpha_produces_heic() {
let out = encode_rgba_with_alpha(&vec![200u8; 16 * 16 * 4], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_rgb10_produces_heic() {
let out = encode_rgb10(&vec![512u16; 16 * 16 * 3], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_rgba10_strips_alpha() {
let out = encode_rgba10(&vec![512u16; 16 * 16 * 4], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_rgba10_with_alpha_produces_heic() {
let out = encode_rgba10_with_alpha(&vec![512u16; 16 * 16 * 4], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_rgb12_produces_heic() {
let out = encode_rgb12(&vec![2048u16; 16 * 16 * 3], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_rgba12_with_alpha_produces_heic() {
let out = encode_rgba12_with_alpha(&vec![2048u16; 16 * 16 * 4], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_gray8_produces_heic() {
let out = encode_gray(&vec![128u8; 16 * 16], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_gray_alpha8_strips_alpha() {
let out = encode_gray_alpha(&vec![200u8; 16 * 16 * 2], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_gray_alpha8_with_alpha_produces_heic() {
let out = encode_gray_alpha_with_alpha(&vec![180u8; 16 * 16 * 2], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_gray10_produces_heic() {
let out = encode_gray10(&vec![512u16; 16 * 16], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_gray_alpha10_with_alpha_produces_heic() {
let out =
encode_gray_alpha10_with_alpha(&vec![512u16; 16 * 16 * 2], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_gray12_produces_heic() {
let out = encode_gray12(&vec![2048u16; 16 * 16], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_gray_alpha12_with_alpha_produces_heic() {
let out =
encode_gray_alpha12_with_alpha(&vec![2048u16; 16 * 16 * 2], 16, 16, &cfg()).unwrap();
assert!(out.len() > 100);
}
#[test]
fn encode_yuv_roundtrips() {
let rgb = vec![128u16; 16 * 16 * 3];
let yuv = yuv::rgb_to_yuv(&rgb, 16, 16, ChromaFormat::Yuv420, BitDepth::Eight);
let out = encode_yuv(&yuv, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_yuv_rejects_invalid_planes() {
let bad = Yuv::from_planes(
vec![0u16; 15], vec![0u16; 4],
vec![0u16; 4],
4,
4,
ChromaFormat::Yuv420,
BitDepth::Eight,
);
assert!(bad.is_err());
}
#[test]
fn odd_dimensions_reported_in_ispe() {
let rgb = vec![100u8; 281 * 181 * 3];
let out = encode_rgb(&rgb, 281, 181, &cfg().with_chroma(ChromaFormat::Yuv420)).unwrap();
let ispe = out
.array_windows::<4>()
.position(|w| w == b"ispe")
.expect("ispe");
let wpos = ispe + 4 + 4;
let w = u32::from_be_bytes(out[wpos..wpos + 4].try_into().unwrap());
let h = u32::from_be_bytes(out[wpos + 4..wpos + 8].try_into().unwrap());
assert_eq!((w, h), (281, 181));
}
#[test]
fn encode_1x1_rgb8() {
assert!(encode_rgb(&[255, 0, 0], 1, 1, &cfg()).is_ok());
}
#[test]
fn encode_1x1_gray8() {
assert!(encode_gray(&[128], 1, 1, &cfg()).is_ok());
}
#[test]
fn encode_1x1_rgba8_with_alpha() {
assert!(encode_rgba_with_alpha(&[255, 0, 0, 255], 1, 1, &cfg()).is_ok());
}
#[test]
fn tiled_rgb8_produces_grid_heic() {
let px: Vec<u8> = (0u32..1024 * 768 * 3).map(|i| (i % 256) as u8).collect();
let out = encode_rgb(&px, 1024, 768, &cfg().with_quality(30)).unwrap();
assert!(out.len() > 1000);
assert_eq!(&out[4..8], b"ftyp");
assert!(
out.array_windows::<4>().any(|w| w == b"grid"),
"expected grid item"
);
}
#[test]
fn tiled_rgb10_produces_grid_heic() {
let px = vec![512u16; 1024 * 768 * 3];
let out = encode_rgb10(&px, 1024, 768, &cfg()).unwrap();
assert!(out.array_windows::<4>().any(|w| w == b"grid"));
}
#[test]
fn tiled_gray8_produces_grid_heic() {
let px: Vec<u8> = (0u32..1024 * 768).map(|i| (i % 256) as u8).collect();
let out = encode_gray(&px, 1024, 768, &cfg()).unwrap();
assert!(out.array_windows::<4>().any(|w| w == b"grid"));
}
#[test]
fn tiled_yuv_produces_grid_heic() {
let rgb = vec![200u16; 1024 * 768 * 3];
let yuv = yuv::rgb_to_yuv(&rgb, 1024, 768, ChromaFormat::Yuv420, BitDepth::Eight);
let out = encode_yuv(&yuv, &cfg()).unwrap();
assert!(out.array_windows::<4>().any(|w| w == b"grid"));
}
#[test]
fn tiled_grid_has_correct_ispe() {
let px: Vec<u8> = vec![128u8; 1024 * 768 * 3];
let out = encode_rgb(&px, 1024, 768, &cfg()).unwrap();
let mut found = false;
let mut i = 0;
while i + 16 <= out.len() {
if &out[i..i + 4] == b"ispe" {
let w = u32::from_be_bytes(out[i + 8..i + 12].try_into().unwrap());
let h = u32::from_be_bytes(out[i + 12..i + 16].try_into().unwrap());
if w == 1024 && h == 768 {
found = true;
break;
}
}
i += 1;
}
assert!(found, "no ispe 1024×768 found in tiled output");
}
#[test]
fn tiled_rgba8_with_alpha_produces_grid_heic() {
let px: Vec<u8> = (0u32..1024 * 768 * 4).map(|i| (i % 256) as u8).collect();
let out = encode_rgba_with_alpha(&px, 1024, 768, &cfg()).unwrap();
assert!(
out.array_windows::<4>().any(|w| w == b"grid"),
"expected grid item"
);
assert!(
out.array_windows::<4>().any(|w| w == b"auxl"),
"expected auxl reference"
);
assert!(
out.array_windows::<4>().any(|w| w == b"auxC"),
"expected auxC property"
);
}
#[test]
fn tiled_rgba10_with_alpha_produces_grid_heic() {
let px = vec![512u16; 1024 * 768 * 4];
let out = encode_rgba10_with_alpha(&px, 1024, 768, &cfg()).unwrap();
assert!(out.array_windows::<4>().any(|w| w == b"grid"));
assert!(out.array_windows::<4>().any(|w| w == b"auxl"));
}
#[test]
fn tiled_gray_alpha8_with_alpha_produces_grid_heic() {
let px: Vec<u8> = (0u32..1024 * 768 * 2).map(|i| (i % 256) as u8).collect();
let out = encode_gray_alpha_with_alpha(&px, 1024, 768, &cfg()).unwrap();
assert!(out.array_windows::<4>().any(|w| w == b"grid"));
assert!(out.array_windows::<4>().any(|w| w == b"auxl"));
}
#[test]
fn tiled_alpha_grid_has_correct_ispe() {
let px: Vec<u8> = vec![200u8; 1024 * 768 * 4];
let out = encode_rgba_with_alpha(&px, 1024, 768, &cfg()).unwrap();
let mut found = false;
let mut i = 0;
while i + 16 <= out.len() {
if &out[i..i + 4] == b"ispe" {
let w = u32::from_be_bytes(out[i + 8..i + 12].try_into().unwrap());
let h = u32::from_be_bytes(out[i + 12..i + 16].try_into().unwrap());
if w == 1024 && h == 768 {
found = true;
break;
}
}
i += 1;
}
assert!(found, "no ispe 1024×768 in tiled alpha output");
}
#[test]
fn tiled_alpha_has_two_grid_items() {
let px: Vec<u8> = vec![128u8; 1024 * 768 * 4];
let out = encode_rgba_with_alpha(&px, 1024, 768, &cfg()).unwrap();
let count = out.array_windows::<4>().filter(|w| *w == b"grid").count();
assert_eq!(
count, 2,
"expected 2 grid items (color + alpha), got {count}"
);
}
#[test]
fn buffer_size_correct() {
assert_eq!(checked_buffer_size::<u16>(4, 4, 3).unwrap(), 48);
assert_eq!(checked_buffer_size::<u8>(1, 1, 1).unwrap(), 1);
}
#[test]
fn buffer_size_overflow() {
assert!(checked_buffer_size::<u16>(usize::MAX, usize::MAX, 3).is_err());
}
#[test]
fn pad_replicates_column() {
let src = vec![10u16, 20, 30];
assert_eq!(pad_buf::<3>(&src, 1, 1, 2, 1), vec![10, 20, 30, 10, 20, 30]);
}
#[test]
fn pad_replicates_row() {
let src = vec![10u16, 20, 30];
assert_eq!(pad_buf::<3>(&src, 1, 1, 1, 2), vec![10, 20, 30, 10, 20, 30]);
}
#[test]
fn pad_noop_when_aligned() {
let src: Vec<u16> = (0..12).collect();
assert_eq!(pad_buf::<3>(&src, 2, 2, 2, 2), src);
}
#[test]
fn encode_yuv_with_alpha_roundtrips() {
let rgb = vec![128u16; 16 * 16 * 3];
let yuv = yuv::rgb_to_yuv(&rgb, 16, 16, ChromaFormat::Yuv420, BitDepth::Eight);
let alpha = vec![200u16; 16 * 16];
let out = encode_yuv_with_alpha(&yuv, &alpha, &cfg()).unwrap();
assert!(out.len() > 100);
assert_eq!(&out[4..8], b"ftyp");
assert!(
out.array_windows::<4>().any(|w| w == b"auxl"),
"expected an auxl reference for the alpha aux image"
);
}
#[test]
fn encode_yuv_with_alpha_444_roundtrips() {
let rgb = vec![64u16; 16 * 16 * 3];
let yuv = yuv::rgb_to_yuv(&rgb, 16, 16, ChromaFormat::Yuv444, BitDepth::Eight);
let alpha = vec![255u16; 16 * 16];
let out = encode_yuv_with_alpha(&yuv, &alpha, &cfg()).unwrap();
assert_eq!(&out[4..8], b"ftyp");
}
#[test]
fn encode_yuv_with_alpha_rejects_bad_alpha_len() {
let rgb = vec![128u16; 16 * 16 * 3];
let yuv = yuv::rgb_to_yuv(&rgb, 16, 16, ChromaFormat::Yuv420, BitDepth::Eight);
let short_alpha = vec![0u16; 16 * 16 - 1];
assert!(encode_yuv_with_alpha(&yuv, &short_alpha, &cfg()).is_err());
}
}