Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  blending.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.

#![allow(clippy::needless_range_loop)]

use crate::headers::extra_channels::{ExtraChannel, ExtraChannelInfo};

use super::patches::{PatchBlendMode, PatchBlending};

#[inline]
fn maybe_clamp(v: f32, clamp: bool) -> f32 {
    if clamp { v.clamp(0.01.0) } else { v }
}

pub fn perform_blending<T: AsRef<[f32]>, V: AsMut<[f32]>>(
    bg: &mut [V],
    fg: &[T],
    color_blending: &PatchBlending,
    ec_blending: &[PatchBlending],
    extra_channel_info: &[ExtraChannelInfo],
) {
    let has_alpha = extra_channel_info
        .iter()
        .any(|info| info.ec_type == ExtraChannel::Alpha);
    let num_ec = extra_channel_info.len();
    let xsize = bg[0].as_mut().len();

    let mut tmp = vec![vec![0.0f32; xsize]; 3 + num_ec];

    for i in 0..num_ec {
        let alpha = ec_blending[i].alpha_channel;
        let clamp = ec_blending[i].clamp;
        let alpha_associated = extra_channel_info[alpha].alpha_associated();

        match ec_blending[i].mode {
            PatchBlendMode::Add => {
                for x in 0..xsize {
                    tmp[3 + i][x] = bg[3 + i].as_mut()[x] + fg[3 + i].as_ref()[x];
                }
            }
            PatchBlendMode::BlendAbove => {
                if i == alpha {
                    for x in 0..xsize {
                        let fa = maybe_clamp(fg[3 + alpha].as_ref()[x], clamp);
                        tmp[3 + i][x] = 1.0 - (1.0 - fa) * (1.0 - bg[3 + i].as_mut()[x]);
                    }
                } else if alpha_associated {
                    for x in 0..xsize {
                        let fa = maybe_clamp(fg[3 + alpha].as_ref()[x], clamp);
                        tmp[3 + i][x] = fg[3 + i].as_ref()[x] + bg[3 + i].as_mut()[x] * (1.0 - fa);
                    }
                } else {
                    for x in 0..xsize {
                        let fa = maybe_clamp(fg[3 + alpha].as_ref()[x], clamp);
                        let new_a = 1.0 - (1.0 - fa) * (1.0 - bg[3 + alpha].as_mut()[x]);
                        let rnew_a = if new_a > 0.0 { 1.0 / new_a } else { 0.0 };
                        tmp[3 + i][x] = (fg[3 + i].as_ref()[x] * fa
                            + bg[3 + i].as_mut()[x] * bg[3 + alpha].as_mut()[x] * (1.0 - fa))
                            * rnew_a;
                    }
                }
            }
            PatchBlendMode::BlendBelow => {
                if i == alpha {
                    for x in 0..xsize {
                        let ba = maybe_clamp(bg[3 + alpha].as_mut()[x], clamp);
                        tmp[3 + i][x] = 1.0 - (1.0 - ba) * (1.0 - fg[3 + i].as_ref()[x]);
                    }
                } else if alpha_associated {
                    for x in 0..xsize {
                        let ba = maybe_clamp(bg[3 + alpha].as_mut()[x], clamp);
                        tmp[3 + i][x] = bg[3 + i].as_mut()[x] + fg[3 + i].as_ref()[x] * (1.0 - ba);
                    }
                } else {
                    for x in 0..xsize {
                        let ba = maybe_clamp(bg[3 + alpha].as_mut()[x], clamp);
                        let new_a = 1.0 - (1.0 - ba) * (1.0 - fg[3 + alpha].as_ref()[x]);
                        let rnew_a = if new_a > 0.0 { 1.0 / new_a } else { 0.0 };
                        tmp[3 + i][x] = (bg[3 + i].as_mut()[x] * ba
                            + fg[3 + i].as_ref()[x] * fg[3 + alpha].as_ref()[x] * (1.0 - ba))
                            * rnew_a;
                    }
                }
            }
            PatchBlendMode::AlphaWeightedAddAbove => {
                if i == alpha {
                    tmp[3 + i].copy_from_slice(bg[3 + i].as_mut());
                } else if clamp {
                    for x in 0..xsize {
                        tmp[3 + i][x] = bg[3 + i].as_mut()[x]
                            + fg[3 + i].as_ref()[x] * fg[3 + alpha].as_ref()[x].clamp(0.01.0);
                    }
                } else {
                    for x in 0..xsize {
                        tmp[3 + i][x] = bg[3 + i].as_mut()[x]
                            + fg[3 + i].as_ref()[x] * fg[3 + alpha].as_ref()[x];
                    }
                }
            }
            PatchBlendMode::AlphaWeightedAddBelow => {
                if i == alpha {
                    tmp[3 + i].copy_from_slice(fg[3 + i].as_ref());
                } else if clamp {
                    for x in 0..xsize {
                        tmp[3 + i][x] = fg[3 + i].as_ref()[x]
                            + bg[3 + i].as_mut()[x] * bg[3 + alpha].as_mut()[x].clamp(0.01.0);
                    }
                } else {
                    for x in 0..xsize {
                        tmp[3 + i][x] = fg[3 + i].as_ref()[x]
                            + bg[3 + i].as_mut()[x] * bg[3 + alpha].as_mut()[x];
                    }
                }
            }
            PatchBlendMode::Mul => {
                if clamp {
                    for x in 0..xsize {
                        tmp[3 + i][x] =
                            bg[3 + i].as_mut()[x] * fg[3 + i].as_ref()[x].clamp(0.01.0);
                    }
                } else {
                    for x in 0..xsize {
                        tmp[3 + i][x] = bg[3 + i].as_mut()[x] * fg[3 + i].as_ref()[x];
                    }
                }
            }
            PatchBlendMode::Replace => {
                tmp[3 + i].copy_from_slice(fg[3 + i].as_ref());
            }
            PatchBlendMode::None => {
                tmp[3 + i].copy_from_slice(bg[3 + i].as_mut());
            }
        }
    }

    let alpha = color_blending.alpha_channel;
    let clamp = color_blending.clamp;

    match color_blending.mode {
        PatchBlendMode::Add => {
            for c in 0..3 {
                for x in 0..xsize {
                    tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x];
                }
            }
        }
        PatchBlendMode::AlphaWeightedAddAbove => {
            for c in 0..3 {
                if !has_alpha {
                    for x in 0..xsize {
                        tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x];
                    }
                } else if clamp {
                    for x in 0..xsize {
                        tmp[c][x] = bg[c].as_mut()[x]
                            + fg[c].as_ref()[x] * fg[3 + alpha].as_ref()[x].clamp(0.01.0);
                    }
                } else {
                    for x in 0..xsize {
                        tmp[c][x] =
                            bg[c].as_mut()[x] + fg[c].as_ref()[x] * fg[3 + alpha].as_ref()[x];
                    }
                }
            }
        }
        PatchBlendMode::AlphaWeightedAddBelow => {
            for c in 0..3 {
                if !has_alpha {
                    for x in 0..xsize {
                        tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x];
                    }
                } else if clamp {
                    for x in 0..xsize {
                        tmp[c][x] = fg[c].as_ref()[x]
                            + bg[c].as_mut()[x] * bg[3 + alpha].as_mut()[x].clamp(0.01.0);
                    }
                } else {
                    for x in 0..xsize {
                        tmp[c][x] =
                            fg[c].as_ref()[x] + bg[c].as_mut()[x] * bg[3 + alpha].as_mut()[x];
                    }
                }
            }
        }
        PatchBlendMode::BlendAbove => {
            if !has_alpha {
                for c in 0..3 {
                    tmp[c].copy_from_slice(fg[c].as_ref());
                }
            } else if extra_channel_info[alpha].alpha_associated() {
                for x in 0..xsize {
                    let fa = maybe_clamp(fg[3 + alpha].as_ref()[x], clamp);
                    for c in 0..3 {
                        tmp[c][x] = fg[c].as_ref()[x] + bg[c].as_mut()[x] * (1.0 - fa);
                    }
                    tmp[3 + alpha][x] = 1.0 - (1.0 - fa) * (1.0 - bg[3 + alpha].as_mut()[x]);
                }
            } else {
                for x in 0..xsize {
                    let fa = maybe_clamp(fg[3 + alpha].as_ref()[x], clamp);
                    let new_a = 1.0 - (1.0 - fa) * (1.0 - bg[3 + alpha].as_mut()[x]);
                    let rnew_a = if new_a > 0.0 { 1.0 / new_a } else { 0.0 };
                    for c in 0..3 {
                        tmp[c][x] = (fg[c].as_ref()[x] * fa
                            + bg[c].as_mut()[x] * bg[3 + alpha].as_mut()[x] * (1.0 - fa))
                            * rnew_a;
                    }
                    tmp[3 + alpha][x] = new_a;
                }
            }
        }
        PatchBlendMode::BlendBelow => {
            if !has_alpha {
                for c in 0..3 {
                    tmp[c].copy_from_slice(bg[c].as_mut());
                }
            } else if extra_channel_info[alpha].alpha_associated() {
                for x in 0..xsize {
                    let ba = maybe_clamp(bg[3 + alpha].as_mut()[x], clamp);
                    for c in 0..3 {
                        tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x] * (1.0 - ba);
                    }
                    tmp[3 + alpha][x] = 1.0 - (1.0 - ba) * (1.0 - fg[3 + alpha].as_ref()[x]);
                }
            } else {
                for x in 0..xsize {
                    let ba = maybe_clamp(bg[3 + alpha].as_mut()[x], clamp);
                    let new_a = 1.0 - (1.0 - ba) * (1.0 - fg[3 + alpha].as_ref()[x]);
                    let rnew_a = if new_a > 0.0 { 1.0 / new_a } else { 0.0 };
                    for c in 0..3 {
                        tmp[c][x] = (bg[c].as_mut()[x] * ba
                            + fg[c].as_ref()[x] * fg[3 + alpha].as_ref()[x] * (1.0 - ba))
                            * rnew_a;
                    }
                    tmp[3 + alpha][x] = new_a;
                }
            }
        }
        PatchBlendMode::Mul => {
            for c in 0..3 {
                for x in 0..xsize {
                    tmp[c][x] = bg[c].as_mut()[x] * maybe_clamp(fg[c].as_ref()[x], clamp);
                }
            }
        }
        PatchBlendMode::Replace => {
            for c in 0..3 {
                tmp[c].copy_from_slice(fg[c].as_ref());
            }
        }
        PatchBlendMode::None => {
            for c in 0..3 {
                tmp[c].copy_from_slice(bg[c].as_mut());
            }
        }
    }
    for i in 0..(3 + num_ec) {
        bg[i].as_mut().copy_from_slice(&tmp[i]);
    }
}

#[cfg(test)]
mod tests {
    fn clamp(x: f32) -> f32 {
        x.clamp(0.01.0)
    }

    mod perform_blending_tests {
        use super::{super::*, *};
        use crate::{headers::bit_depth::BitDepth, util::test::assert_all_almost_abs_eq};
        use test_log::test;

        const ABS_DELTA: f32 = 1e-6;

        // Helper for expected value calculations based on C++ logic

        // Alpha compositing formula: Ao = As + Ab * (1 - As)
        // Used for kBlend modes for the alpha channel itself.
        fn expected_alpha_blend(fg_a: f32, bg_a: f32) -> f32 {
            fg_a + bg_a * (1.0 - fg_a)
        }

        // Color compositing for kBlend, premultiplied alpha: Co = Cs_premult + Cb_premult * (1 - As)
        fn expected_color_blend_premultiplied(c_fg: f32, c_bg: f32, fg_a: f32) -> f32 {
            c_fg + c_bg * (1.0 - fg_a)
        }

        // Color compositing for kBlend, non-premultiplied alpha: Co = (Cs * As + Cb * Ab * (1 - As)) / Ao_blend
        fn expected_color_blend_non_premultiplied(
            c_fg: f32,
            fg_a: f32, // Foreground color and its alpha
            c_bg: f32,
            bg_a: f32,            // Background color and its alpha
            alpha_blend_out: f32, // The resulting alpha from expected_alpha_blend(fg_a, bg_a)
        ) -> f32 {
            if alpha_blend_out.abs() < ABS_DELTA {
                // Avoid division by zero
                0.0
            } else {
                (c_fg * fg_a + c_bg * bg_a * (1.0 - fg_a)) / alpha_blend_out
            }
        }

        // For kAlphaWeightedAdd modes: Co = Cb + Cs * As
        fn expected_alpha_weighted_add(c_bg: f32, c_fg: f32, fg_a: f32) -> f32 {
            c_bg + c_fg * fg_a
        }

        // For kMul mode: Co = Cb * Cs
        fn expected_mul_blend(c_bg: f32, c_fg: f32) -> f32 {
            c_bg * c_fg
        }

        #[test]
        fn test_color_replace_fg_over_bg() {
            let mut bg_r = [0.1];
            let mut bg_g = [0.2];
            let mut bg_b = [0.3];
            let fg_r = [0.7];
            let fg_g = [0.8];
            let fg_b = [0.9];

            let mut bg_channels: [&mut [f32]; 3] = [&mut bg_r, &mut bg_g, &mut bg_b];
            let fg_channels: [&[f32]; 3] = [&fg_r, &fg_g, &fg_b];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::Replace,
                alpha_channel: 0// Not used for Replace
                clamp: false,
            };

            let ec_blending: [PatchBlending; 0] = [];
            let extra_channel_info: [ExtraChannelInfo; 0] = [];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            // Expected: output color is fg color
            assert_all_almost_abs_eq(&bg_r, &fg_r, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &fg_g, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &fg_b, ABS_DELTA);
        }

        #[test]
        fn test_color_add() {
            let mut bg_r = [0.1];
            let mut bg_g = [0.2];
            let mut bg_b = [0.3];
            let fg_r = [0.7];
            let fg_g = [0.6];
            let fg_b = [0.5];
            let expected_r = [bg_r[0] + fg_r[0]];
            let expected_g = [bg_g[0] + fg_g[0]];
            let expected_b = [bg_b[0] + fg_b[0]];

            let mut bg_channels: [&mut [f32]; 3] = [&mut bg_r, &mut bg_g, &mut bg_b];
            let fg_channels: [&[f32]; 3] = [&fg_r, &fg_g, &fg_b];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::Add,
                alpha_channel: 0// Not used
                clamp: false,
            };
            let ec_blending: [PatchBlending; 0] = [];
            let extra_channel_info: [ExtraChannelInfo; 0] = [];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            assert_all_almost_abs_eq(&bg_r, &expected_r, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &expected_g, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &expected_b, ABS_DELTA);
        }

        #[test]
        fn test_color_blend_above_premultiplied_alpha() {
            // BG: R=0.1, G=0.2, B=0.3, A=0.8 (premultiplied)
            // FG: R=0.4, G=0.3, B=0.2, A=0.5 (premultiplied)
            let mut bg_r = [0.1];
            let mut bg_g = [0.2];
            let mut bg_b = [0.3];
            let mut bg_a = [0.8];
            let fg_r = [0.4];
            let fg_g = [0.3];
            let fg_b = [0.2];
            let fg_a = [0.5];
            let fga = fg_a[0]; // Not clamped
            let bga = bg_a[0];

            // Expected alpha: Ao = Afg + Abg * (1 - Afg)
            let expected_a_val = expected_alpha_blend(fga, bga);
            // Expected color: Co = Cfg_premult + Cbg_premult * (1 - Afg)
            let expected_r_val = expected_color_blend_premultiplied(fg_r[0], bg_r[0], fga);
            let expected_g_val = expected_color_blend_premultiplied(fg_g[0], bg_g[0], fga);
            let expected_b_val = expected_color_blend_premultiplied(fg_b[0], bg_b[0], fga);

            let mut bg_channels: [&mut [f32]; 4] = [&mut bg_r, &mut bg_g, &mut bg_b, &mut bg_a];
            let fg_channels: [&[f32]; 4] = [&fg_r, &fg_g, &fg_b, &fg_a];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::BlendAbove,
                alpha_channel: 0// EC0 is the alpha channel
                clamp: false,
            };
            // EC0 (alpha) blending rule.
            // For BlendAbove color mode, the alpha channel itself is also blended using source-over.
            // So this ec_blending rule for alpha will be effectively overridden by color blending's alpha calculation.
            let ec_blending = [PatchBlending {
                mode: PatchBlendMode::Replace, // Arbitrary, will be overwritten by color blend
                alpha_channel: 0,
                clamp: false,
            }];
            let extra_channel_info = [ExtraChannelInfo::new(
                false,
                ExtraChannel::Alpha,
                BitDepth::f32(), // Assuming f32
                0,
                "alpha".to_string(),
                true// alpha_associated = true (premultiplied)
                None,
                None,
            )];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            assert_all_almost_abs_eq(&bg_a, &[expected_a_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_r, &[expected_r_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &[expected_g_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &[expected_b_val], ABS_DELTA);
        }

        #[test]
        fn test_color_blend_above_non_premultiplied_alpha() {
            // BG: R=0.1, G=0.2, B=0.3 (unpremult), A=0.8
            // FG: R=0.7, G=0.6, B=0.5 (unpremult), A=0.5
            let mut bg_r = [0.1];
            let mut bg_g = [0.2];
            let mut bg_b = [0.3];
            let mut bg_a = [0.8];
            let fg_r = [0.7];
            let fg_g = [0.6];
            let fg_b = [0.5];
            let fg_a = [0.5];
            let fga = fg_a[0];
            let bga = bg_a[0];

            // Expected alpha: Ao = Afg + Abg * (1 - Afg)
            let expected_a_val = expected_alpha_blend(fga, bga);
            // Expected color: Co = (Cfg_unpremult * Afg + Cbg_unpremult * Abg * (1 - Afg)) / Ao_blend
            let expected_r_val =
                expected_color_blend_non_premultiplied(fg_r[0], fga, bg_r[0], bga, expected_a_val);
            let expected_g_val =
                expected_color_blend_non_premultiplied(fg_g[0], fga, bg_g[0], bga, expected_a_val);
            let expected_b_val =
                expected_color_blend_non_premultiplied(fg_b[0], fga, bg_b[0], bga, expected_a_val);

            let mut bg_channels: [&mut [f32]; 4] = [&mut bg_r, &mut bg_g, &mut bg_b, &mut bg_a];
            let fg_channels: [&[f32]; 4] = [&fg_r, &fg_g, &fg_b, &fg_a];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::BlendAbove,
                alpha_channel: 0// EC0
                clamp: false,
            };
            let ec_blending = [PatchBlending {
                // This will be overwritten for the alpha channel by color blending
                mode: PatchBlendMode::Replace,
                alpha_channel: 0,
                clamp: false,
            }];
            let extra_channel_info = [ExtraChannelInfo::new(
                false,
                ExtraChannel::Alpha,
                BitDepth::f32(),
                0,
                "alpha".to_string(),
                false// alpha_associated = false (non-premultiplied)
                None,
                None,
            )];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            assert_all_almost_abs_eq(&bg_a, &[expected_a_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_r, &[expected_r_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &[expected_g_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &[expected_b_val], ABS_DELTA);
        }

        #[test]
        fn test_color_alpha_weighted_add_above() {
            let mut bg_r = [0.1];
            let mut bg_g = [0.2];
            let mut bg_b = [0.3];
            let mut bg_a = [0.8]; // bg alpha used by ec_blending
            let fg_r = [0.7];
            let fg_g = [0.6];
            let fg_b = [0.5];
            let fg_a = [0.5]; // fg alpha used for weighting
            let fga_for_weighting = fg_a[0]; // Not clamped as color_blending.clamp is false

            // Expected color: Co = Cbg + Cfg * Afg_for_weighting
            let expected_r_val = expected_alpha_weighted_add(bg_r[0], fg_r[0], fga_for_weighting);
            let expected_g_val = expected_alpha_weighted_add(bg_g[0], fg_g[0], fga_for_weighting);
            let expected_b_val = expected_alpha_weighted_add(bg_b[0], fg_b[0], fga_for_weighting);

            // Expected alpha (EC0): Blended according to ec_blending[0].
            // Mode is BlendAbove, alpha_channel is 0 (itself).
            // C++: PerformAlphaBlending(bg[3+0], bg[3+0], fg[3+0], fg[3+0], tmp.Row(3+0), ...)
            // This means it's the "alpha channel blends itself" case: Ao = Afg + Abg * (1 - Afg)
            // fg_alpha_for_ec0 = fg_a[0], bg_alpha_for_ec0 = bg_a[0]. ec_blending[0].clamp is false.
            let expected_a_val = expected_alpha_blend(fg_a[0], bg_a[0]);

            let mut bg_channels: [&mut [f32]; 4] = [&mut bg_r, &mut bg_g, &mut bg_b, &mut bg_a];
            let fg_channels: [&[f32]; 4] = [&fg_r, &fg_g, &fg_b, &fg_a];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::AlphaWeightedAddAbove,
                alpha_channel: 0// EC0 is alpha
                clamp: false,
            };
            // For AlphaWeightedAdd color mode, the alpha channel (EC0) value is determined by its ec_blending rule.
            // Let's make EC0 blend itself using BlendAbove mode.
            let ec_blending = [PatchBlending {
                mode: PatchBlendMode::BlendAbove, // Alpha channel EC0 blends itself
                alpha_channel: 0,                 //  using itself as alpha reference
                clamp: false,
            }];
            let extra_channel_info = [ExtraChannelInfo::new(
                false,
                ExtraChannel::Alpha,
                BitDepth::f32(),
                0,
                "alpha".to_string(),
                true// alpha_associated (doesn't directly affect AWA color, but affects EC0's own blend)
                None,
                None,
            )];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            assert_all_almost_abs_eq(&bg_r, &[expected_r_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &[expected_g_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &[expected_b_val], ABS_DELTA);
            assert_all_almost_abs_eq(&bg_a, &[expected_a_val], ABS_DELTA);
        }

        #[test]
        fn test_color_mul_with_clamp() {
            let mut bg_r = [0.5];
            let mut bg_g = [0.8];
            let mut bg_b = [1.0];
            let fg_r = [1.5];
            let fg_g = [-0.2];
            let fg_b = [0.5]; // fg values will be clamped
            let expected_r = [expected_mul_blend(bg_r[0], clamp(fg_r[0]))]; // 0.5 * 1.0 = 0.5
            let expected_g = [expected_mul_blend(bg_g[0], clamp(fg_g[0]))]; // 0.8 * 0.0 = 0.0
            let expected_b = [expected_mul_blend(bg_b[0], clamp(fg_b[0]))]; // 1.0 * 0.5 = 0.5

            let mut bg_channels: [&mut [f32]; 3] = [&mut bg_r, &mut bg_g, &mut bg_b];
            let fg_channels: [&[f32]; 3] = [&fg_r, &fg_g, &fg_b];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::Mul,
                alpha_channel: 0// Not used
                clamp: true,      // Clamp fg values
            };
            let ec_blending: [PatchBlending; 0] = [];
            let extra_channel_info: [ExtraChannelInfo; 0] = [];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            assert_all_almost_abs_eq(&bg_r, &expected_r, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &expected_g, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &expected_b, ABS_DELTA);
        }

        #[test]
        fn test_ec_blend_data_with_separate_alpha_premultiplied() {
            // Color: Replace FG over BG (to keep it simple)
            // EC0: Data channel
            // EC1: Alpha channel for EC0
            let mut bg_r = [0.1];
            let mut bg_g = [0.1];
            let mut bg_b = [0.1];
            let mut bg_ec0 = [0.2];
            let mut bg_ec1_alpha = [0.9]; // EC1 is alpha for EC0

            let fg_r = [0.5];
            let fg_g = [0.5];
            let fg_b = [0.5];
            let fg_ec0 = [0.6];
            let fg_ec1_alpha = [0.4];

            // EC1 (Alpha channel for EC0) blending: BlendAbove, uses itself as alpha.
            // fg_alpha = fg_ec1_alpha[0], bg_alpha = bg_ec1_alpha[0]
            let expected_out_ec1_alpha = expected_alpha_blend(fg_ec1_alpha[0], bg_ec1_alpha[0]);

            // EC0 (Data channel) blending: BlendAbove, uses EC1 as alpha.
            // fg_alpha_for_ec0 = fg_ec1_alpha[0] (not clamped as ec_blending[0].clamp is false)
            // is_premultiplied = extra_channel_info[ec_blending[0].alpha_channel (is 1)].alpha_associated = true.
            // Formula: out = fg_data + bg_data * (1.f - fg_alpha_of_data)
            let expected_out_ec0 =
                expected_color_blend_premultiplied(fg_ec0[0], bg_ec0[0], fg_ec1_alpha[0]);

            let mut bg_channels: [&mut [f32]; 5] = [
                &mut bg_r,
                &mut bg_g,
                &mut bg_b,
                &mut bg_ec0,
                &mut bg_ec1_alpha,
            ];
            let fg_channels: [&[f32]; 5] = [&fg_r, &fg_g, &fg_b, &fg_ec0, &fg_ec1_alpha];

            let color_blending = PatchBlending {
                // Simple color replace
                mode: PatchBlendMode::Replace,
                alpha_channel: 0,
                clamp: false,
            };

            let ec_blending = [
                PatchBlending {
                    // EC0 (data) uses EC1 as alpha
                    mode: PatchBlendMode::BlendAbove,
                    alpha_channel: 1// EC1 is alpha for EC0
                    clamp: false,
                },
                PatchBlending {
                    // EC1 (alpha) blends itself
                    mode: PatchBlendMode::BlendAbove,
                    alpha_channel: 1// EC1 uses itself as alpha
                    clamp: false,
                },
            ];
            let extra_channel_info = [
                ExtraChannelInfo::new(
                    false,
                    ExtraChannel::Unknown,
                    BitDepth::f32(),
                    0,
                    "ec0".to_string(),
                    false,
                    None,
                    None,
                ), // EC0 data
                ExtraChannelInfo::new(
                    false,
                    ExtraChannel::Alpha,
                    BitDepth::f32(),
                    0,
                    "alpha_for_ec0".to_string(),
                    true// EC1 is premultiplied alpha
                    None,
                    None,
                ), // EC1 alpha
            ];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            // Expected Color (Replace)
            assert_all_almost_abs_eq(&bg_r, &fg_r, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &fg_g, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &fg_b, ABS_DELTA);

            assert_all_almost_abs_eq(&bg_ec1_alpha, &[expected_out_ec1_alpha], ABS_DELTA);

            assert_all_almost_abs_eq(&bg_ec0, &[expected_out_ec0], ABS_DELTA);
        }

        #[test]
        fn test_no_alpha_channel_blend_above_falls_back_to_copy_fg() {
            let mut bg_r = [0.1];
            let mut bg_g = [0.2];
            let mut bg_b = [0.3];
            let fg_r = [0.7];
            let fg_g = [0.8];
            let fg_b = [0.9];

            let mut bg_channels: [&mut [f32]; 3] = [&mut bg_r, &mut bg_g, &mut bg_b];
            let fg_channels: [&[f32]; 3] = [&fg_r, &fg_g, &fg_b];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::BlendAbove,
                alpha_channel: 0// Irrelevant as no alpha EIs
                clamp: false,
            };

            let ec_blending: [PatchBlending; 0] = [];
            // No ExtraChannelInfo means has_alpha will be false.
            let extra_channel_info: [ExtraChannelInfo; 0] = [];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            // Expected: output color is fg color due to fallback
            assert_all_almost_abs_eq(&bg_r, &fg_r, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_g, &fg_g, ABS_DELTA);
            assert_all_almost_abs_eq(&bg_b, &fg_b, ABS_DELTA);
        }

        #[test]
        fn test_empty_pixels() {
            let mut bg_r: [f32; 0] = [];
            let mut bg_g: [f32; 0] = [];
            let mut bg_b: [f32; 0] = [];
            let fg_r: [f32; 0] = [];
            let fg_g: [f32; 0] = [];
            let fg_b: [f32; 0] = [];

            let mut bg_channels: [&mut [f32]; 3] = [&mut bg_r, &mut bg_g, &mut bg_b];
            let fg_channels: [&[f32]; 3] = [&fg_r, &fg_g, &fg_b];

            let color_blending = PatchBlending {
                mode: PatchBlendMode::Replace,
                alpha_channel: 0,
                clamp: false,
            };
            let ec_blending: [PatchBlending; 0] = [];
            let extra_channel_info: [ExtraChannelInfo; 0] = [];

            perform_blending(
                &mut bg_channels,
                &fg_channels,
                &color_blending,
                &ec_blending,
                &extra_channel_info,
            );

            // Expect output slices to also be empty and unchanged.
            assert_eq!(bg_r.len(), 0);
            assert_eq!(bg_g.len(), 0);
            assert_eq!(bg_b.len(), 0);
        }
    }
}

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

¤ Dauer der Verarbeitung: 0.15 Sekunden  ¤

*© 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

letze Version des Elbe Quellennavigators


Jenseits des Üblichen ....
    

Besucher

Besucher

Statistik
#Sources=277311
#Domains=752002