Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/jxl/src/features/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 74 kB image not shown  

Quelle  patches.rs   Sprache: Rust

 

// 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<PatchBlendMode> {
        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<R: rand::Rng>(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<PatchPosition>,
    pub ref_positions: Vec<PatchReferencePosition>,
    blendings: Vec<PatchBlending>,
    blendings_stride: usize,
    patch_tree: Vec<PatchTreeNode>,
    // Number of patches for each row.
    num_patches: Vec<usize>,
    sorted_patches_y0: Vec<(usize, usize)>,
    sorted_patches_y1: Vec<(usize, usize)>,
}

impl PatchesDictionary {
    pub fn new(num_extra_channels: usize) -> Self {
        Self {
            blendings_stride: num_extra_channels + 1,
            ..Default::default()
        }
    }

    #[cfg(test)]
    pub fn random<R: rand::Rng>(
        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<PatchInterval> = 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<PatchInterval>, start: usize, end: usize| {
            intervals[start..end].sort_unstable_by_key(|i| i.y0);
        };
        let sort_by_y1 = |intervals: &mut Vec<PatchInterval>, 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<ReferenceFrame>],
    ) -> Result<PatchesDictionary> {
        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<PatchPosition> = 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)
    }

    #[inline(always)]
    pub fn set_patches_for_row(&self, y: usize, patches_for_row_result: &mut Vec<usize>) {
        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();
    }

    #[inline(always)]
    pub fn add_one_row(
        &self,
        row: &mut [&mut [f32]],
        row_pos: (usize, usize),
        xsize: usize,
        extra_channel_info: &[ExtraChannelInfo],
        reference_frames: &[Option<ReferenceFrame>],
        patches_for_row_result: &mut Vec<usize>,
    ) {
        // 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::<Vec<&mut [f32]>>();
        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<PatchPosition>,
            ref_positions: Vec<PatchReferencePosition>,
        ) -> 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<usize>);
            dict.set_patches_for_row(10, &mut patches_for_row_result);
            assert_eq!(patches_for_row_result, vec![] as Vec<usize>);
        }

        #[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<usize>); // Row before patch
            dict.set_patches_for_row(15, &mut patches_for_row_result);
            assert_eq!(patches_for_row_result, vec![] as Vec<usize>); // 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<usize>);
            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<usize>);
        }

        #[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<usize>);
            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<usize>); // Between patches
            dict.set_patches_for_row(9, &mut patches_for_row_result);
            assert_eq!(patches_for_row_result, vec![] as Vec<usize>); // 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<usize>);
        }

        #[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<usize>);
        }

        #[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<usize>);
            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<usize>);
        }

        #[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<usize>);
            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<usize>);
        }

        #[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<usize>);
            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<usize>);
            dict.set_patches_for_row(9, &mut patches_for_row_result);
            assert_eq!(patches_for_row_result, vec![] as Vec<usize>);
            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<usize>);
        }
    }

    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<Option<ReferenceFrame>> {
            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<const N: usize>(
            rows: &[[f32; N]],
        ) -> Result<Option<ReferenceFrame>> {
            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<ExtraChannelInfo> = 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<f32> = vec![0.0; xsize];
            let mut g_data: Vec<f32> = vec![0.0; xsize];
            let mut b_data: Vec<f32> = 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<ReferenceFrame>
                &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<ExtraChannelInfo> = 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<f32> = vec![0.5; xsize];
            let mut g_data: Vec<f32> = vec![0.5; xsize];
            let mut b_data: Vec<f32> = 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<f32> = 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<ExtraChannelInfo> = 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<f32> = vec![0.0; xsize];
            let mut g_data: Vec<f32> = vec![0.0; xsize];
            let mut b_data: Vec<f32> = 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<f32> = 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, &e='color:red'>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, &e='color:red'>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<ExtraChannelInfo> = 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<ExtraChannelInfo> = 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<f32> = (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(())
        }
    }
}

Messung V0.5 in Prozent
C=92 H=97 G=94

¤ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet am  2026-10-11) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.