// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. use num_derive::FromPrimitive; use num_traits::FromPrimitive; use crate::{ bit_reader::BitReader, entropy_coding::decode::Histograms, entropy_coding::decode::SymbolReader, error::{Error, Result}, features::blending::perform_blending, frame::{DecoderState, ReferenceFrame}, headers::extra_channels::ExtraChannelInfo, util::{NewWithCapacity, slice, tracing_wrappers::*}, }; // Context numbers as specified in Section C.4.5, Listing C.2: #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(usize)] pub enum PatchContext { NumRefPatch = 0, ReferenceFrame = 1, PatchSize = 2, PatchReferencePosition = 3, PatchPosition = 4, PatchBlendMode = 5, PatchOffset = 6, PatchCount = 7, PatchAlphaChannel = 8, PatchClamp = 9, } impl PatchContext { const NUM: usize = 10; } /// Blend modes #[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)] #[repr(u8)] pub enum PatchBlendMode { // The new values are the old ones. Useful to skip some channels. None = 0, // The new values (in the crop) replace the old ones: sample = new Replace = 1, // The new values (in the crop) get added to the old ones: sample = old + new Add = 2, // The new values (in the crop) get multiplied by the old ones: // sample = old * new // This blend mode is only supported if BlendColorSpace is kEncoded. The // range of the new value matters for multiplication purposes, and its // nominal range of 0..1 is computed the same way as this is done for the // alpha values in kBlend and kAlphaWeightedAdd. Mul = 3, // The new values (in the crop) replace the old ones if alpha>0: // For first alpha channel: // alpha = old + new * (1 - old) // For other channels if !alpha_associated: // sample = ((1 - new_alpha) * old * old_alpha + new_alpha * new) / alpha // For other channels if alpha_associated: // sample = (1 - new_alpha) * old + new // The alpha formula applies to the alpha used for the division in the other // channels formula, and applies to the alpha channel itself if its // blend_channel value matches itself. // If using kBlendAbove, new is the patch and old is the original image; if // using kBlendBelow, the meaning is inverted. BlendAbove = 4, BlendBelow = 5, // The new values (in the crop) are added to the old ones if alpha>0: // For first alpha channel: sample = sample = old + new * (1 - old) // For other channels: sample = old + alpha * new AlphaWeightedAddAbove = 6, AlphaWeightedAddBelow = 7, } impl PatchBlendMode { pub const NUM_BLEND_MODES: u8 = 8; #[cfg(test)] fn try_from(i: u8) -> Result { match i { 0 => Ok(PatchBlendMode::None), 1 => Ok(PatchBlendMode::Replace), 2 => Ok(PatchBlendMode::Add), 3 => Ok(PatchBlendMode::Mul), 4 => Ok(PatchBlendMode::BlendAbove), 5 => Ok(PatchBlendMode::BlendBelow), 6 => Ok(PatchBlendMode::AlphaWeightedAddAbove), 7 => Ok(PatchBlendMode::AlphaWeightedAddBelow), _ => Err(Error::PatchesInvalidBlendMode( i, PatchBlendMode::NUM_BLEND_MODES, )), } } #[cfg(test)] fn random(rng: &mut R) -> Self { use rand::distr::{Distribution, Uniform}; Self::try_from( Uniform::new_inclusive( PatchBlendMode::None as u8, PatchBlendMode::AlphaWeightedAddBelow as u8, ) .unwrap() .sample(rng), ) .unwrap() } pub fn uses_alpha(self) -> bool { matches!( self, Self::BlendAbove | Self::BlendBelow | Self::AlphaWeightedAddAbove | Self::AlphaWeightedAddBelow ) } pub fn uses_clamp(self) -> bool { self.uses_alpha() || self == Self::Mul } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PatchBlending { pub mode: PatchBlendMode, pub alpha_channel: usize, pub clamp: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PatchReferencePosition { // Not using `ref` like in the spec here, because it is a keyword. reference: usize, x0: usize, y0: usize, xsize: usize, ysize: usize, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PatchPosition { x: usize, y: usize, ref_pos_idx: usize, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] struct PatchTreeNode { left_child: isize, right_child: isize, y_center: usize, start: usize, num: usize, } #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct PatchesDictionary { pub positions: Vec, pub ref_positions: Vec, blendings: Vec, blendings_stride: usize, patch_tree: Vec, // Number of patches for each row. num_patches: Vec, sorted_patches_y0: Vec<(usize, usize)>, sorted_patches_y1: Vec<(usize, usize)>, } impl PatchesDictionary { #[cfg(test)] pub fn random( size: (usize, usize), num_extra_channels: usize, alpha_channel: usize, reference_frames: usize, rng: &mut R, ) -> Self { use rand::distr::{Distribution, Uniform}; let width_dist = Uniform::new_inclusive(0, size.0 - 1).unwrap(); let height_dist = Uniform::new_inclusive(0, size.1 - 1).unwrap(); let num_refs = Uniform::new_inclusive(1, 5).unwrap().sample(rng); let ref_dist = Uniform::new_inclusive(0, num_refs - 1).unwrap(); let ref_frame_dist = Uniform::new_inclusive(0, reference_frames - 1).unwrap(); let num_patches = Uniform::new_inclusive(num_refs, 10).unwrap().sample(rng); let mut result = PatchesDictionary { positions: (0..num_patches) .map(|_| PatchPosition { x: width_dist.sample(rng), y: height_dist.sample(rng), ref_pos_idx: ref_dist.sample(rng), }) .collect(), ref_positions: (0..num_refs) .map(|_| { let mut result = PatchReferencePosition { reference: ref_frame_dist.sample(rng), x0: width_dist.sample(rng), y0: height_dist.sample(rng), xsize: 0, ysize: 0, }; result.xsize = Uniform::new_inclusive(1, size.0 - result.x0) .unwrap() .sample(rng); result.ysize = Uniform::new_inclusive(1, size.1 - result.y0) .unwrap() .sample(rng); result }) .collect(), blendings: (0..num_patches) .map(|_| PatchBlending { mode: PatchBlendMode::random(rng), alpha_channel, clamp: Uniform::new_inclusive(0, 1).unwrap().sample(rng) == 0, }) .collect(), blendings_stride: num_extra_channels + 1, patch_tree: vec![], num_patches: vec![], sorted_patches_y0: vec![], sorted_patches_y1: vec![], }; result.compute_patch_tree().unwrap(); result } fn compute_patch_tree(&mut self) -> Result<()> { #[derive(Debug, Clone, Copy)] struct PatchInterval { idx: usize, y0: usize, y1: usize, } self.patch_tree.clear(); self.num_patches.clear(); self.sorted_patches_y0.clear(); self.sorted_patches_y1.clear(); if self.positions.is_empty() { return Ok(()); } // Create a y-interval for each patch. let mut intervals: Vec = Vec::new_with_capacity(self.positions.len())?; for (i, pos) in self.positions.iter().enumerate() { let ref_pos = self.ref_positions[pos.ref_pos_idx]; if ref_pos.xsize > 0 && ref_pos.ysize > 0 { intervals.push(PatchInterval { idx: i, y0: pos.y, y1: pos.y + self.ref_positions[pos.ref_pos_idx].ysize, }); } } let intervals_len = intervals.len(); let sort_by_y0 = |intervals: &mut Vec, start: usize, end: usize| { intervals[start..end].sort_unstable_by_key(|i| i.y0); }; let sort_by_y1 = |intervals: &mut Vec, start: usize, end: usize| { intervals[start..end].sort_unstable_by_key(|i| i.y1); }; // Count the number of patches for each row. sort_by_y1(&mut intervals, 0, intervals_len); self.num_patches .resize(intervals.last().map_or(0, |iv| iv.y1), 0); //Safe last() for iv in &intervals { for y in iv.y0..iv.y1 { self.num_patches[y] += 1; } } let root = PatchTreeNode { start: 0, num: intervals.len(), ..Default::default() }; self.patch_tree.push(root); let mut next = 0; while next < self.patch_tree.len() { let node = &mut self.patch_tree[next]; // Borrow mutably *before* accessing fields let start = node.start; let end = node.start + node.num; // Choose the y_center for this node to be the median of interval starts. sort_by_y0(&mut intervals, start, end); let middle_idx = start + node.num / 2; node.y_center = intervals[middle_idx].y0; // Divide the intervals in [start, end) into three groups: let mut right_start = middle_idx; while right_start < end && intervals[right_start].y0 == node.y_center { right_start += 1; } sort_by_y1(&mut intervals, start, right_start); let mut left_end = right_start; while left_end > start && intervals[left_end - 1].y1 > node.y_center { left_end -= 1; } // Fill in sorted_patches_y0_ and sorted_patches_y1_ for the current node. node.num = right_start - left_end; node.start = self.sorted_patches_y0.len(); self.sorted_patches_y1 .try_reserve(right_start.saturating_sub(left_end))?; self.sorted_patches_y0 .try_reserve(right_start.saturating_sub(left_end))?; for i in (left_end..right_start).rev() { self.sorted_patches_y1 .push((intervals[i].y1, intervals[i].idx)); } sort_by_y0(&mut intervals, left_end, right_start); for interval in intervals.iter().take(right_start).skip(left_end) { self.sorted_patches_y0.push((interval.y0, interval.idx)); } // Create the left and right nodes (if not empty). // We modify left_child/right_child on the *original* node in patch_tree, // so we have to do the assignment *before* we push the new nodes. self.patch_tree[next].left_child = -1; self.patch_tree[next].right_child = -1; if left_end > start { let mut left = PatchTreeNode::default(); left.start = start; left.num = left_end - left.start; self.patch_tree[next].left_child = self.patch_tree.len() as isize; self.patch_tree.try_reserve(1)?; self.patch_tree.push(left); } if right_start < end { let mut right = PatchTreeNode::default(); right.start = right_start; right.num = end - right.start; self.patch_tree[next].right_child = self.patch_tree.len() as isize; self.patch_tree.try_reserve(1)?; self.patch_tree.push(right); } next += 1; } Ok(()) } #[instrument(level = "debug", skip(br), ret, err)] pub fn read( br: &mut BitReader, xsize: usize, ysize: usize, num_extra_channels: usize, reference_frames: &[Option], ) -> Result { let blendings_stride = num_extra_channels + 1; let patches_histograms = Histograms::decode(PatchContext::NUM, br, true)?; let mut patches_reader = SymbolReader::new(&patches_histograms, br, None)?; let num_ref_patch = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::NumRefPatch as usize, ) as usize; let num_pixels = xsize * ysize; let max_ref_patches = 1024 + num_pixels / 4; let max_patches = max_ref_patches * 4; let max_blending_infos = max_patches * 4; if num_ref_patch > max_ref_patches { return Err(Error::PatchesTooMany( "reference patches".to_string(), num_ref_patch, max_ref_patches, )); } let mut total_patches = 0; let mut next_size = 1; let mut positions: Vec = Vec::new(); let mut blendings = Vec::new(); let mut ref_positions = Vec::new_with_capacity(num_ref_patch)?; for _ in 0..num_ref_patch { let reference = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::ReferenceFrame as usize, ) as usize; if reference >= DecoderState::MAX_STORED_FRAMES { return Err(Error::PatchesRefTooLarge( reference, DecoderState::MAX_STORED_FRAMES, )); } let x0 = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchReferencePosition as usize, ) as usize; let y0 = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchReferencePosition as usize, ) as usize; let ref_pos_xsize = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchSize as usize, ) as usize + 1; let ref_pos_ysize = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchSize as usize, ) as usize + 1; let reference_frame = &reference_frames[reference]; // TODO(firsching): make sure this check is correct in the presence of downsampled extra channels (also in libjxl). match reference_frame { None => return Err(Error::PatchesInvalidReference(reference)), Some(reference) => { if !reference.saved_before_color_transform { return Err(Error::PatchesPostColorTransform()); } if x0 + ref_pos_xsize > reference.frame[0].size().0 { return Err(Error::PatchesInvalidPosition( "x".to_string(), x0, ref_pos_xsize, reference.frame[0].size().0, )); } if y0 + ref_pos_ysize > reference.frame[0].size().1 { return Err(Error::PatchesInvalidPosition( "y".to_string(), y0, ref_pos_ysize, reference.frame[0].size().1, )); } } } let id_count = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchCount as usize, ) as usize + 1; if id_count > max_patches + 1 { return Err(Error::PatchesTooMany( "patches".to_string(), id_count, max_patches, )); } total_patches += id_count; if total_patches > max_patches { return Err(Error::PatchesTooMany( "patches".to_string(), total_patches, max_patches, )); } if next_size < total_patches { next_size *= 2; next_size = std::cmp::min(next_size, max_patches); } if next_size * blendings_stride > max_blending_infos { return Err(Error::PatchesTooMany( "blending_info".to_string(), total_patches, max_patches, )); } positions.try_reserve(next_size.saturating_sub(positions.len()))?; blendings.try_reserve( (next_size * PatchBlendMode::NUM_BLEND_MODES as usize) .saturating_sub(blendings.len()), )?; for i in 0..id_count { let mut pos = PatchPosition { x: 0, y: 0, ref_pos_idx: ref_positions.len(), }; if i == 0 { // Read initial position pos.x = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchPosition as usize, ) as usize; pos.y = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchPosition as usize, ) as usize; } else { // Read offsets and calculate new position let delta_x = patches_reader.read_signed( &patches_histograms, br, PatchContext::PatchOffset as usize, ); if delta_x < 0 && (-delta_x as usize) > positions.last().unwrap().x { return Err(Error::PatchesInvalidDelta( "x".to_string(), positions.last().unwrap().x, delta_x, )); } pos.x = (positions.last().unwrap().x as i32 + delta_x) as usize; let delta_y = patches_reader.read_signed( &patches_histograms, br, PatchContext::PatchOffset as usize, ); if delta_y < 0 && (-delta_y as usize) > positions.last().unwrap().y { return Err(Error::PatchesInvalidDelta( "y".to_string(), positions.last().unwrap().y, delta_y, )); } pos.y = (positions.last().unwrap().y as i32 + delta_y) as usize; } if pos.x + ref_pos_xsize > xsize { return Err(Error::PatchesOutOfBounds( "x".to_string(), pos.x, ref_pos_xsize, xsize, )); } if pos.y + ref_pos_ysize > ysize { return Err(Error::PatchesOutOfBounds( "y".to_string(), pos.y, ref_pos_ysize, ysize, )); } for _ in 0..blendings_stride { let mut alpha_channel = 0; let mut clamp = false; let maybe_blend_mode = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchBlendMode as usize, ) as u8; let blend_mode = match PatchBlendMode::from_u8(maybe_blend_mode) { None => { return Err(Error::PatchesInvalidBlendMode( maybe_blend_mode, PatchBlendMode::NUM_BLEND_MODES, )); } Some(blend_mode) => blend_mode, }; if PatchBlendMode::uses_alpha(blend_mode) && blendings_stride > 2 { alpha_channel = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchAlphaChannel as usize, ) as usize; if alpha_channel >= num_extra_channels { return Err(Error::PatchesInvalidAlphaChannel( alpha_channel, num_extra_channels, )); } } if PatchBlendMode::uses_clamp(blend_mode) { clamp = patches_reader.read_unsigned( &patches_histograms, br, PatchContext::PatchClamp as usize, ) != 0; } blendings.push(PatchBlending { mode: blend_mode, alpha_channel, clamp, }); } positions.push(pos); } ref_positions.push(PatchReferencePosition { reference, x0, y0, xsize: ref_pos_xsize, ysize: ref_pos_ysize, }) } let mut patches_dict = PatchesDictionary { positions, blendings, ref_positions, blendings_stride, num_patches: vec![], sorted_patches_y0: vec![], sorted_patches_y1: vec![], patch_tree: vec![], }; patches_dict.compute_patch_tree()?; Ok(patches_dict) } pub fn set_patches_for_row(&self, y: usize, patches_for_row_result: &mut Vec) { patches_for_row_result.clear(); if self.num_patches.len() <= y || self.num_patches[y] == 0 { return; } let mut tree_idx: isize = 0; loop { if tree_idx == -1 { break; } // Safe access using get() and unwrap_or(). No need for the assert. let node = self.patch_tree.get(tree_idx as usize).unwrap_or_else(|| { // TODO(firsching): Handle panic differently? panic!("Invalid tree_idx: {tree_idx}"); }); if y <= node.y_center { for i in 0..node.num { let p = self.sorted_patches_y0[node.start + i]; if y < p.0 { break; } patches_for_row_result.push(p.1); } tree_idx = if y < node.y_center { node.left_child } else { -1 }; } else { for i in 0..node.num { let p = self.sorted_patches_y1[node.start + i]; if y >= p.0 { break; } patches_for_row_result.push(p.1); } tree_idx = node.right_child; } } // Ensure that the relative order of patches is preserved. patches_for_row_result.sort(); } pub fn add_one_row( &self, row: &mut [&mut [f32]], row_pos: (usize, usize), xsize: usize, extra_channel_info: &[ExtraChannelInfo], reference_frames: &[Option], patches_for_row_result: &mut Vec, ) { // TODO(zond): Allocate a buffer for this when building the stage instead of when executing it. let mut out = row .iter_mut() .map(|s| &mut s[..xsize]) .collect::>(); let num_ec = extra_channel_info.len(); assert!(num_ec + 1 == self.blendings_stride); let dummy_fg = vec![0f32]; let mut fg = vec![dummy_fg.as_slice(); 3 + num_ec]; self.set_patches_for_row(row_pos.1, &mut *patches_for_row_result); for pos_idx in patches_for_row_result.iter() { let pos = &self.positions[*pos_idx]; assert!(row_pos.1 >= pos.y); // assert patch starts at or before current row if pos.x >= row_pos.0 + out[0].len() { // if patch starts before end of current chunk, continue continue; } let ref_pos = &self.ref_positions[pos.ref_pos_idx]; assert!(pos.y + ref_pos.ysize > row_pos.1); // assert patch ends after current row if pos.x + ref_pos.xsize < row_pos.0 { // if patch ends before current chunk, continue continue; } let (ref_x0, out_x0, ref_xsize) = if pos.x < row_pos.0 { // if patch starts before current chunk // crop the first part of the patch and use the first part of the chunk ( ref_pos.x0 + row_pos.0 - pos.x, 0, ref_pos.xsize + pos.x - row_pos.0, ) } else { // otherwise // use the first part of the patch and crop the first part of the chunk (ref_pos.x0, pos.x - row_pos.0, ref_pos.xsize) }; let (ref_x1, out_x1) = if out[0].len() - out_x0 < ref_xsize { // if rest of chunk is smaller than patch // crop the last part of the patch and use the last part of the chunk (ref_x0 + out[0].len() - out_x0, out[0].len()) } else { // otherwise // use the last part of the patch and crop the last part of the chunk (ref_x0 + ref_xsize, out_x0 + ref_xsize) }; let ref_pos_y = ref_pos.y0 + row_pos.1 - pos.y; for (c, fg_ptr) in fg.iter_mut().enumerate().take(3) { *fg_ptr = &(reference_frames[ref_pos.reference].as_ref().unwrap().frame[c] .row(ref_pos_y)[ref_x0..ref_x1]); } for i in 0..num_ec { fg[3 + i] = &(reference_frames[ref_pos.reference].as_ref().unwrap().frame[3 + i] .row(ref_pos_y)[ref_x0..ref_x1]); } let blending_idx = pos_idx * self.blendings_stride; perform_blending( &mut slice!(&mut out, .., out_x0..out_x1), &fg, &self.blendings[blending_idx], &self.blendings[blending_idx + 1..], extra_channel_info, ); } } } #[cfg(test)] mod tests { mod read_patches_tests { use super::super::*; use test_log::test; #[test] fn read_single_patch_dict() -> Result<()> { let mut br = BitReader::new(&[0x12, 0x4a, 0x8c, 0x63, 0x13, 0x01, 0xa6, 0x53, 0x01]); let got_dict = PatchesDictionary::read( &mut br, 1024, 1024, 0, &[Some(ReferenceFrame::blank(1024, 1024, 1, true).unwrap())], )?; let want_dict = PatchesDictionary { positions: vec![PatchPosition { x: 10, y: 20, ref_pos_idx: 0, }], ref_positions: vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 1, ysize: 1, }], blendings: vec![PatchBlending { mode: PatchBlendMode::Add, alpha_channel: 0, clamp: false, }], blendings_stride: 1, num_patches: vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ], patch_tree: vec![PatchTreeNode { left_child: -1, right_child: -1, y_center: 20, start: 0, num: 1, }], sorted_patches_y0: vec![(20, 0)], sorted_patches_y1: vec![(21, 0)], }; assert_eq!(got_dict, want_dict); Ok(()) } #[test] fn read_multi_patch_dict() -> Result<()> { let mut br = BitReader::new(&[ 0x12, 0xc6, 0x26, 0x3f, 0x08, 0x4e, 0xb6, 0x0d, 0xf2, 0xde, 0xb6, 0x6d, ]); let got_dict = PatchesDictionary::read( &mut br, 1024, 1024, 2, &[Some(ReferenceFrame::blank(1024, 1024, 1, true).unwrap())], )?; let want_dict = PatchesDictionary { positions: vec![ PatchPosition { x: 0, y: 0, ref_pos_idx: 0, }, PatchPosition { x: 5, y: 5, ref_pos_idx: 1, }, ], ref_positions: vec![ PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 2, ysize: 1, }, PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 1, ysize: 2, }, ], blendings: vec![ PatchBlending { mode: PatchBlendMode::BlendAbove, alpha_channel: 1, clamp: false, }, PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: true, }, PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: true, }, PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: true, }, PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: true, }, PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: true, }, ], blendings_stride: 3, num_patches: vec![1, 0, 0, 0, 0, 1, 1], patch_tree: vec![ PatchTreeNode { left_child: 1, right_child: -1, y_center: 5, start: 0, num: 1, }, PatchTreeNode { left_child: -1, right_child: -1, y_center: 0, start: 1, num: 1, }, ], sorted_patches_y0: vec![(5, 1), (0, 0)], sorted_patches_y1: vec![(7, 1), (1, 0)], }; assert_eq!(got_dict, want_dict); Ok(()) } #[test] fn read_large_patch_dict() -> Result<()> { let mut br = BitReader::new(&[ 0x12, 0x4e, 0x50, 0x76, 0xeb, 0x41, 0x0d, 0x7e, 0xe5, 0x8e, 0xd2, 0x5d, 0x01, ]); let got_dict = PatchesDictionary::read( &mut br, 1024, 1024, 1, &[Some(ReferenceFrame::blank(1024, 1024, 1, true).unwrap())], )?; let want_dict = PatchesDictionary { positions: vec![PatchPosition { x: 2, y: 3, ref_pos_idx: 0, }], ref_positions: vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 300, ysize: 200, }], blendings: vec![ PatchBlending { mode: PatchBlendMode::AlphaWeightedAddBelow, alpha_channel: 0, clamp: false, }, PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: false, }, ], blendings_stride: 2, num_patches: vec![ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ], patch_tree: vec![PatchTreeNode { left_child: -1, right_child: -1, y_center: 3, start: 0, num: 1, }], sorted_patches_y0: vec![(3, 0)], sorted_patches_y1: vec![(203, 0)], }; assert_eq!(got_dict, want_dict); Ok(()) } #[test] fn read_clamped_patch_dict() -> Result<()> { let mut br = BitReader::new(&[0x12, 0xc6, 0x26, 0x1f, 0x70, 0xce, 0x06]); let got_dict = PatchesDictionary::read( &mut br, 1024, 1024, 0, &[Some(ReferenceFrame::blank(1024, 1024, 1, true).unwrap())], )?; let want_dict = PatchesDictionary { positions: vec![PatchPosition { x: 4, y: 4, ref_pos_idx: 0, }], ref_positions: vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 1, ysize: 1, }], blendings: vec![PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: true, }], blendings_stride: 1, num_patches: vec![0, 0, 0, 0, 1], patch_tree: vec![PatchTreeNode { left_child: -1, right_child: -1, y_center: 4, start: 0, num: 1, }], sorted_patches_y0: vec![(4, 0)], sorted_patches_y1: vec![(5, 0)], }; assert_eq!(got_dict, want_dict); Ok(()) } #[test] fn read_dup_patch_dict() -> Result<()> { let mut br = BitReader::new(&[0x12, 0x0a, 0x8d, 0x88, 0x03, 0x31, 0xd7, 0x35]); let got_dict = PatchesDictionary::read( &mut br, 1024, 1024, 0, &[Some(ReferenceFrame::blank(1024, 1024, 1, true).unwrap())], )?; let want_dict = PatchesDictionary { positions: vec![ PatchPosition { x: 0, y: 0, ref_pos_idx: 0, }, PatchPosition { x: 5, y: 5, ref_pos_idx: 0, }, ], ref_positions: vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 1, ysize: 1, }], blendings: vec![ PatchBlending { mode: PatchBlendMode::Add, alpha_channel: 0, clamp: false, }, PatchBlending { mode: PatchBlendMode::Add, alpha_channel: 0, clamp: false, }, ], blendings_stride: 1, num_patches: vec![1, 0, 0, 0, 0, 1], patch_tree: vec![ PatchTreeNode { left_child: 1, right_child: -1, y_center: 5, start: 0, num: 1, }, PatchTreeNode { left_child: -1, right_child: -1, y_center: 0, start: 1, num: 1, }, ], sorted_patches_y0: vec![(5, 1), (0, 0)], sorted_patches_y1: vec![(6, 1), (1, 0)], }; assert_eq!(got_dict, want_dict); Ok(()) } } mod set_patches_for_row_tests { use super::super::*; use test_log::test; // Helper to create a PatchesDictionary for tests fn create_dictionary( positions: Vec, ref_positions: Vec, ) -> PatchesDictionary { // Using default/empty blendings for these tests as they don't affect get_patches_for_row let mut dict = PatchesDictionary { positions, ref_positions, ..Default::default() }; dict.compute_patch_tree().unwrap(); dict } #[test] fn test_no_patches() { let dict = create_dictionary(vec![], vec![]); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(0, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(10, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } #[test] fn test_single_patch_hit() { let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 5, }]; let positions = vec![PatchPosition { x: 0, y: 10, ref_pos_idx: 0, }]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; // Patch covers rows 10, 11, 12, 13, 14 dict.set_patches_for_row(10, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); // First row of patch dict.set_patches_for_row(12, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); // Middle row of patch dict.set_patches_for_row(14, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); // Last row of patch } #[test] fn test_single_patch_miss() { let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 5, }]; // Covers y=10 to y=14 let positions = vec![PatchPosition { x: 0, y: 10, ref_pos_idx: 0, }]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(9, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); // Row before patch dict.set_patches_for_row(15, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); // Row after patch } #[test] fn test_single_patch_height_one() { let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 1, }]; // Covers y=5 only let positions = vec![PatchPosition { x: 0, y: 5, ref_pos_idx: 0, }]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(4, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(5, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); dict.set_patches_for_row(6, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } #[test] fn test_multiple_patches_non_overlapping() { let ref_positions = vec![ PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 3, }, // Patch 0: rows 5,6,7 PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 2, }, // Patch 1: rows 10,11 ]; let positions = vec![ PatchPosition { x: 0, y: 5, ref_pos_idx: 0, }, PatchPosition { x: 0, y: 10, ref_pos_idx: 1, }, ]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(4, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(5, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); dict.set_patches_for_row(7, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); dict.set_patches_for_row(8, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); // Between patches dict.set_patches_for_row(9, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); // Between patches dict.set_patches_for_row(10, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(11, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(12, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } #[test] fn test_multiple_patches_overlapping() { let ref_positions = vec![ PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 5, }, // Patch 0: rows 10-14 PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 4, }, // Patch 1: rows 12-15 ]; let positions = vec![ PatchPosition { x: 0, y: 10, ref_pos_idx: 0, }, // idx 0 PatchPosition { x: 0, y: 12, ref_pos_idx: 1, }, // idx 1 ]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(10, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); // Only patch 0 dict.set_patches_for_row(11, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); // Only patch 0 dict.set_patches_for_row(12, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 1]); // Both patches (sorted indices) dict.set_patches_for_row(13, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 1]); // Both patches dict.set_patches_for_row(14, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 1]); // Patch 0 ends, Patch 1 continues dict.set_patches_for_row(15, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); // Only patch 1 dict.set_patches_for_row(16, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } #[test] fn test_multiple_patches_adjacent() { let ref_positions = vec![ PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 2, }, // Patch 0: rows 5,6 PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 3, }, // Patch 1: rows 7,8,9 ]; let positions = vec![ PatchPosition { x: 0, y: 5, ref_pos_idx: 0, }, PatchPosition { x: 0, y: 7, ref_pos_idx: 1, }, ]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(4, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(5, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); dict.set_patches_for_row(6, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0]); dict.set_patches_for_row(7, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); // Patch 0 ends, Patch 1 starts dict.set_patches_for_row(8, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(9, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(10, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } #[test] fn test_multiple_patches_same_start_different_heights() { let ref_positions = vec![ PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 2, }, // Patch 0: rows 3,4 PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 10, ysize: 4, }, // Patch 1: rows 3,4,5,6 ]; let positions = vec![ PatchPosition { x: 0, y: 3, ref_pos_idx: 0, }, // idx 0 PatchPosition { x: 0, y: 3, ref_pos_idx: 1, }, // idx 1 ]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(2, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(3, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 1]); // Both cover dict.set_patches_for_row(4, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 1]); // Both cover dict.set_patches_for_row(5, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); // Only patch 1 (longer) dict.set_patches_for_row(6, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); // Only patch 1 dict.set_patches_for_row(7, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } #[test] fn test_patches_out_of_order_definition() { // Define patches in a non-sorted order of their y positions // get_patches_for_row should still return sorted indices if multiple apply. let ref_positions = vec![ PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 5, ysize: 3, }, // Patch 0 (idx 0): rows 10,11,12 PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 5, ysize: 3, }, // Patch 1 (idx 1): rows 5,6,7 PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 5, ysize: 3, }, // Patch 2 (idx 2): rows 10,11,12 (overlaps with 0) ]; let positions = vec![ PatchPosition { x: 0, y: 10, ref_pos_idx: 0, }, // Patch 0 PatchPosition { x: 0, y: 5, ref_pos_idx: 1, }, // Patch 1 PatchPosition { x: 0, y: 10, ref_pos_idx: 2, }, // Patch 2 ]; let dict = create_dictionary(positions, ref_positions); let mut patches_for_row_result = vec![]; dict.set_patches_for_row(4, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(5, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(6, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(7, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![1]); dict.set_patches_for_row(8, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(9, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); dict.set_patches_for_row(10, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 2]); // Patches 0 and 2, indices sorted dict.set_patches_for_row(11, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 2]); dict.set_patches_for_row(12, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![0, 2]); dict.set_patches_for_row(13, &mut patches_for_row_result); assert_eq!(patches_for_row_result, vec![] as Vec); } } mod add_one_row_tests { use super::super::*; use crate::{ headers::{bit_depth::BitDepth, extra_channels::ExtraChannel}, image::Image, util::test::assert_all_almost_abs_eq, }; const MAX_ABS_DELTA: f32 = 1e-6; // Adjusted for typical f32 comparisons fn create_reference_frame( width: usize, height: usize, channel_values: &[f32], ) -> Result> { let mut frame_channels = Vec::new(); for v in channel_values.iter() { let img = Image::new_with_value((width, height), *v)?; frame_channels.push(img); } Ok(Some(ReferenceFrame { frame: frame_channels, saved_before_color_transform: true, })) } fn create_reference_frame_single_row( rows: &[[f32; N]], ) -> Result> { let mut frame_channels = Vec::new(); for v in rows.iter() { let mut img = Image::new((N, 1))?; img.row_mut(0).copy_from_slice(v); frame_channels.push(img); } Ok(Some(ReferenceFrame { frame: frame_channels, saved_before_color_transform: true, })) } #[test] fn test_add_one_row_simple_replace() -> Result<()> { let xsize = 10; let ref_frames = vec![create_reference_frame(xsize, 1, &[1.0; 3])?]; let extra_channel_info: Vec = Vec::new(); let ref_positions = vec![PatchReferencePosition { reference: 0, // Points to main_ref_frame x0: 2, y0: 0, xsize: 3, ysize: 1, }]; let positions = vec![PatchPosition { x: 2, y: 0, ref_pos_idx: 0, }]; let blendings = vec![PatchBlending { mode: PatchBlendMode::Replace, alpha_channel: 0, clamp: false, // Clamping set to false }]; let mut patches_dict = PatchesDictionary { positions, ref_positions, blendings, blendings_stride: 1 + extra_channel_info.len(), patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; patches_dict.compute_patch_tree()?; let mut r_data: Vec = vec![0.0; xsize]; let mut g_data: Vec = vec![0.0; xsize]; let mut b_data: Vec = vec![0.0; xsize]; let mut row_slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data]; let expected_r = vec![0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]; patches_dict.add_one_row( &mut row_slices, (0, 0), xsize, &extra_channel_info, &ref_frames, // Pass the Vec &mut vec![], ); assert_all_almost_abs_eq(&r_data, &expected_r, MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &expected_r, MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &expected_r, MAX_ABS_DELTA); Ok(()) } #[test] fn test_add_one_row_simple_add() -> Result<()> { let xsize = 10; let y_coord = 0; let ref_frames = vec![create_reference_frame(xsize, 1, &[0.2; 3])?]; let extra_channel_info: Vec = Vec::new(); let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 2, y0: 0, xsize: 3, ysize: 1, }]; let positions = vec![PatchPosition { x: 2, y: 0, ref_pos_idx: 0, }]; let blendings = vec![PatchBlending { mode: PatchBlendMode::Add, alpha_channel: 0, clamp: false, // Clamping set to false }]; let mut patches_dict = PatchesDictionary { positions, ref_positions, blendings, blendings_stride: 1 + extra_channel_info.len(), patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; patches_dict.compute_patch_tree()?; let mut r_data: Vec = vec![0.5; xsize]; let mut g_data: Vec = vec![0.5; xsize]; let mut b_data: Vec = vec![0.5; xsize]; let mut row_slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data]; let mut expected_r: Vec = vec![0.5; xsize]; for r in expected_r.iter_mut().take(5).skip(2) { *r = 0.5 + 0.2 } patches_dict.add_one_row( &mut row_slices, (0, y_coord), xsize, &extra_channel_info, &ref_frames, &mut vec![], ); assert_all_almost_abs_eq(&r_data, &expected_r, MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &expected_r, MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &expected_r, MAX_ABS_DELTA); Ok(()) } #[test] fn test_add_one_row_overlapping_replace() -> Result<()> { let xsize = 10; let y_coord = 0; let main_ref_frame1 = create_reference_frame(xsize, 1, &[1.0; 3])?; let main_ref_frame2 = create_reference_frame(xsize, 1, &[2.0; 3])?; let ref_frames = vec![main_ref_frame1, main_ref_frame2]; let extra_channel_info: Vec = Vec::new(); let ref_positions = vec![ PatchReferencePosition { reference: 0, // Points to main_ref_frame1 x0: 0, y0: 0, xsize: 4, ysize: 1, }, PatchReferencePosition { reference: 1, // Points to main_ref_frame2 x0: 0, y0: 0, xsize: 3, ysize: 1, }, ]; let positions = vec![ PatchPosition { x: 2, y: 0, ref_pos_idx: 0, }, // P1: canvas [2..6] with 1.0 PatchPosition { x: 4, y: 0, ref_pos_idx: 1, }, // P2: canvas [4..7] with 2.0 ]; let blendings = vec![ PatchBlending { mode: PatchBlendMode::Replace, alpha_channel: 0, clamp: false, // Clamping set to false }, // For P1 PatchBlending { mode: PatchBlendMode::Replace, alpha_channel: 0, clamp: false, // Clamping set to false }, // For P2 ]; let mut patches_dict = PatchesDictionary { positions, ref_positions, blendings, blendings_stride: 1 + extra_channel_info.len(), patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; patches_dict.compute_patch_tree()?; let mut r_data: Vec = vec![0.0; xsize]; let mut g_data: Vec = vec![0.0; xsize]; let mut b_data: Vec = vec![0.0; xsize]; let mut row_slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data]; let expected_r: Vec = vec![0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]; patches_dict.add_one_row( &mut row_slices, (0, y_coord), xsize, &extra_channel_info, &ref_frames, &mut vec![], ); assert_all_almost_abs_eq(&r_data, &expected_r, MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &expected_r, MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &expected_r, MAX_ABS_DELTA); Ok(()) } #[test] fn test_add_one_row_blend_above_ec_alpha_non_associated() -> Result<()> { let xsize = 1; let y_coord = 0; let initial_color_val = 0.1; let initial_ec0_alpha = 0.4; let ref_color_val = 0.8; let ref_ec0_alpha_val = 0.5; let ec_info = vec![ExtraChannelInfo::new( true, ExtraChannel::Alpha, BitDepth::f32(), 0, "AlphaEC".to_string(), false, // alpha_associated = false None, None, )]; let main_ref_frame = create_reference_frame( xsize, 1, &[ ref_color_val, ref_color_val, ref_color_val, ref_ec0_alpha_val, ], )?; let ref_frames = vec![main_ref_frame]; let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 1, ysize: 1, }]; let positions = vec![PatchPosition { x: 0, y: 0, ref_pos_idx: 0, }]; let blendings = vec![ PatchBlending { mode: PatchBlendMode::BlendAbove, alpha_channel: 0, // Alpha for color is EC0 clamp: false, // Clamping set to false }, // Color PatchBlending { mode: PatchBlendMode::BlendAbove, alpha_channel: 0, // Alpha for EC0 is EC0 itself clamp: false, // Clamping set to false }, // EC0 ]; let mut patches_dict = PatchesDictionary { positions, ref_positions, blendings, blendings_stride: 1 + ec_info.len(), // Color + 1 EC patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; patches_dict.compute_patch_tree()?; let mut r_data = vec![initial_color_val; xsize]; let mut g_data = vec![initial_color_val; xsize]; let mut b_data = vec![initial_color_val; xsize]; let mut ec0_data = vec![initial_ec0_alpha; xsize]; let mut row_slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data, &mut ec0_data]; // Calculations based on C++ logic for non-associated alpha: // OutputAlpha = OldAlpha + PatchAlpha * (1 - OldAlpha) // OutputColor = (OldColor * OldAlpha * (1 - PatchAlpha) + PatchColor * PatchAlpha) / OutputAlpha // (If OutputAlpha is very small, OutputColor is 0) let canvas_alpha_val = initial_ec0_alpha; // old_alpha let patch_alpha_val = ref_ec0_alpha_val; // ref_alpha let expected_ec0 = canvas_alpha_val + patch_alpha_val * (1.0 - canvas_alpha_val); let canvas_color_val = initial_color_val; // old_color let patch_color_val = ref_color_val; // ref_color (straight) let expected_color = if expected_ec0.abs() < 1e-5 { // Threshold similar to kSmallAlpha 0.0 } else { (canvas_color_val * canvas_alpha_val * (1.0 - patch_alpha_val) + patch_color_val * patch_alpha_val) / expected_ec0 }; patches_dict.add_one_row( &mut row_slices, (0, y_coord), xsize, &ec_info, &ref_frames, &mut vec![], ); assert_all_almost_abs_eq(&r_data, &vec![expected_color], MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &vec![expected_color], MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &vec![expected_color], MAX_ABS_DELTA); assert_all_almost_abs_eq(&ec0_data, &vec![expected_ec0], MAX_ABS_DELTA); Ok(()) } #[test] fn test_add_one_row_blend_above_ec_alpha_associated() -> Result<()> { let xsize = 1; let y_coord = 0; let initial_color_val = 0.1; // Canvas color, assumed associated let initial_ec0_alpha = 0.4; // Canvas alpha let ref_color_val = 0.8; // Patch color, straight (non-associated) let ref_ec0_alpha_val = 0.5; // Patch alpha let ec_info = vec![ExtraChannelInfo::new( true, ExtraChannel::Alpha, BitDepth::f32(), 0, "AlphaEC".to_string(), true, // alpha_associated = true None, None, )]; let main_ref_frame = create_reference_frame( xsize, 1, &[ ref_color_val, ref_color_val, ref_color_val, ref_ec0_alpha_val, ], )?; let ref_frames = vec![main_ref_frame]; let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 1, ysize: 1, }]; let positions = vec![PatchPosition { x: 0, y: 0, ref_pos_idx: 0, }]; let blendings = vec![ PatchBlending { mode: PatchBlendMode::BlendAbove, alpha_channel: 0, clamp: false, // Clamping set to false }, // Color PatchBlending { mode: PatchBlendMode::BlendAbove, alpha_channel: 0, clamp: false, // Clamping set to false }, // EC0 ]; let mut patches_dict = PatchesDictionary { positions, ref_positions, blendings, blendings_stride: 1 + ec_info.len(), patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; patches_dict.compute_patch_tree()?; let mut r_data = vec![initial_color_val; xsize]; let mut g_data = vec![initial_color_val; xsize]; let mut b_data = vec![initial_color_val; xsize]; let mut ec0_data = vec![initial_ec0_alpha; xsize]; let mut row_slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data, &mut ec0_data]; let expected_ec0 = ref_ec0_alpha_val + initial_ec0_alpha * (1.0 - ref_ec0_alpha_val); let expected_color = ref_color_val + initial_color_val * (1.0 - ref_ec0_alpha_val); patches_dict.add_one_row( &mut row_slices, (0, y_coord), xsize, &ec_info, &ref_frames, &mut vec![], ); assert_all_almost_abs_eq(&ec0_data, &vec![expected_ec0], MAX_ABS_DELTA); assert_all_almost_abs_eq(&r_data, &vec![expected_color], MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &vec![expected_color], MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &vec![expected_color], MAX_ABS_DELTA); Ok(()) } #[test] fn test_add_one_row_mul_blend() -> Result<()> { let xsize = 2; let y_coord = 0; let initial_vals = [0.5, 2.0]; let ref_vals = [0.8, 0.7]; let main_ref_frame = create_reference_frame_single_row(&[ref_vals; 3])?; let dummy_ref_frame1 = create_reference_frame_single_row(&[[0.0; 2]; 3])?; let dummy_ref_frame2 = create_reference_frame_single_row(&[[0.0; 2]; 3])?; let ref_frames = vec![main_ref_frame, dummy_ref_frame1, dummy_ref_frame2]; let extra_channel_info: Vec = Vec::new(); let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize, ysize: 1, }]; let positions = vec![PatchPosition { x: 0, y: 0, ref_pos_idx: 0, }]; // Test Mul (always without clamp as per new instruction) let blendings = vec![PatchBlending { mode: PatchBlendMode::Mul, alpha_channel: 0, clamp: false, // Clamping set to false }]; let mut dict = PatchesDictionary { positions: positions.clone(), ref_positions: ref_positions.clone(), blendings, blendings_stride: 1, // Only color channels patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; dict.compute_patch_tree()?; let mut r_data = initial_vals; let mut g_data = initial_vals; let mut b_data = initial_vals; let mut slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data]; dict.add_one_row( &mut slices, (0, y_coord), xsize, &extra_channel_info, &ref_frames, &mut vec![], ); let expected_vals = [0.5 * 0.8, 2.0 * 0.7]; // [0.4, 1.4] assert_all_almost_abs_eq(&r_data, &expected_vals, MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &expected_vals, MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &expected_vals, MAX_ABS_DELTA); Ok(()) } #[test] fn test_add_one_row_none_blend() -> Result<()> { let xsize = 5; let y_coord = 0; let main_ref_frame = create_reference_frame(xsize, 1, &[100.0; 3])?; let dummy_ref_frame1 = create_reference_frame(xsize, 1, &[0.0; 3])?; let dummy_ref_frame2 = create_reference_frame(xsize, 1, &[0.0; 3])?; let ref_frames = vec![main_ref_frame, dummy_ref_frame1, dummy_ref_frame2]; let extra_channel_info: Vec = Vec::new(); let ref_positions = vec![PatchReferencePosition { reference: 0, x0: 0, y0: 0, xsize: 3, ysize: 1, }]; let positions = vec![PatchPosition { x: 1, y: 0, ref_pos_idx: 0, }]; let blendings = vec![PatchBlending { mode: PatchBlendMode::None, alpha_channel: 0, clamp: false, // Clamping set to false }]; let mut patches_dict = PatchesDictionary { positions, ref_positions, blendings, blendings_stride: 1, // Only color channels patch_tree: Vec::new(), num_patches: Vec::new(), sorted_patches_y0: Vec::new(), sorted_patches_y1: Vec::new(), }; patches_dict.compute_patch_tree()?; let initial_data: Vec = (0..xsize).map(|i| i as f32 * 0.1 + 0.05).collect(); let mut r_data = initial_data.clone(); let mut g_data = initial_data.clone(); let mut b_data = initial_data.clone(); let mut row_slices: Vec<&mut [f32]> = vec![&mut r_data, &mut g_data, &mut b_data]; patches_dict.add_one_row( &mut row_slices, (0, y_coord), xsize, &extra_channel_info, &ref_frames, &mut vec![], ); assert_all_almost_abs_eq(&r_data, &initial_data, MAX_ABS_DELTA); assert_all_almost_abs_eq(&g_data, &initial_data, MAX_ABS_DELTA); assert_all_almost_abs_eq(&b_data, &initial_data, MAX_ABS_DELTA); Ok(()) } } }