// 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.
for i in0..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 in0..xsize {
tmp[3 + i][x] = bg[3 + i].as_mut()[x] + fg[3 + i].as_ref()[x];
}
}
PatchBlendMode::BlendAbove => { if i == alpha { for x in0..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]);
}
} elseif alpha_associated { for x in0..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 in0..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 in0..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]);
}
} elseif alpha_associated { for x in0..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 in0..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());
} elseif clamp { for x in0..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.0, 1.0);
}
} else { for x in0..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());
} elseif clamp { for x in0..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.0, 1.0);
}
} else { for x in0..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 in0..xsize {
tmp[3 + i][x] =
bg[3 + i].as_mut()[x] * fg[3 + i].as_ref()[x].clamp(0.0, 1.0);
}
} else { for x in0..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 in0..3 { for x in0..xsize {
tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x];
}
}
}
PatchBlendMode::AlphaWeightedAddAbove => { for c in0..3 { if !has_alpha { for x in0..xsize {
tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x];
}
} elseif clamp { for x in0..xsize {
tmp[c][x] = bg[c].as_mut()[x]
+ fg[c].as_ref()[x] * fg[3 + alpha].as_ref()[x].clamp(0.0, 1.0);
}
} else { for x in0..xsize {
tmp[c][x] =
bg[c].as_mut()[x] + fg[c].as_ref()[x] * fg[3 + alpha].as_ref()[x];
}
}
}
}
PatchBlendMode::AlphaWeightedAddBelow => { for c in0..3 { if !has_alpha { for x in0..xsize {
tmp[c][x] = bg[c].as_mut()[x] + fg[c].as_ref()[x];
}
} elseif clamp { for x in0..xsize {
tmp[c][x] = fg[c].as_ref()[x]
+ bg[c].as_mut()[x] * bg[3 + alpha].as_mut()[x].clamp(0.0, 1.0);
}
} else { for x in0..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 in0..3 {
tmp[c].copy_from_slice(fg[c].as_ref());
}
} elseif extra_channel_info[alpha].alpha_associated() { for x in0..xsize { let fa = maybe_clamp(fg[3 + alpha].as_ref()[x], clamp); for c in0..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 in0..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 in0..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 in0..3 {
tmp[c].copy_from_slice(bg[c].as_mut());
}
} elseif extra_channel_info[alpha].alpha_associated() { for x in0..xsize { let ba = maybe_clamp(bg[3 + alpha].as_mut()[x], clamp); for c in0..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 in0..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 in0..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 in0..3 { for x in0..xsize {
tmp[c][x] = bg[c].as_mut()[x] * maybe_clamp(fg[c].as_ref()[x], clamp);
}
}
}
PatchBlendMode::Replace => { for c in0..3 {
tmp[c].copy_from_slice(fg[c].as_ref());
}
}
PatchBlendMode::None => { for c in0..3 {
tmp[c].copy_from_slice(bg[c].as_mut());
}
}
} for i in0..(3 + num_ec) {
bg[i].as_mut().copy_from_slice(&tmp[i]);
}
}
mod perform_blending_tests { usesuper::{super::*, *}; usecrate::{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)
}
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,
)];
#[test] fn test_color_alpha_weighted_add_above() { letmut bg_r = [0.1]; letmut bg_g = [0.2]; letmut bg_b = [0.3]; letmut 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]);
// 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);
}
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.