fix: optimize performance by wrapping trace logs, adding captured drag event subscriptions, and adding regression tests for adjustment compositing.
This commit is contained in:
+40
-14
@@ -384,6 +384,16 @@ pub fn composite_tiled_into(
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies one adjustment-layer pixel to the current composite row.
|
||||
///
|
||||
/// **Purpose:** Evaluates curves, gradient-map, or hue/saturation data while
|
||||
/// respecting masks, opacity, and blend mode. **Logic & Workflow:** Transparent
|
||||
/// or masked-out destinations exit early; Normal blend bypasses the generic
|
||||
/// blend dispatcher, and fully opaque adjustment pixels are assigned directly.
|
||||
/// **Arguments:** `row` is the destination row, `x`/`gy` are canvas coordinates,
|
||||
/// `layer` owns adjustment and mask data, and `blend`/`opacity` control mixing.
|
||||
/// **Returns:** Nothing. **Side Effects / Dependencies:** Mutates only the RGB
|
||||
/// channels of one destination pixel and uses `hcie-blend` for non-Normal modes.
|
||||
#[inline]
|
||||
fn blend_adjustment_pixel(
|
||||
row: &mut [u8],
|
||||
@@ -435,20 +445,36 @@ fn blend_adjustment_pixel(
|
||||
};
|
||||
|
||||
let eff_opacity = opacity * (mask_val as f32 / 255.0);
|
||||
let blended_opaque = blend_pixels(
|
||||
[dst[0], dst[1], dst[2], 255],
|
||||
[adj_rgb[0], adj_rgb[1], adj_rgb[2], 255],
|
||||
blend,
|
||||
1.0,
|
||||
);
|
||||
row[out_idx] = ((1.0 - eff_opacity) * dst[0] as f32 + eff_opacity * blended_opaque[0] as f32)
|
||||
.round() as u8;
|
||||
row[out_idx + 1] = ((1.0 - eff_opacity) * dst[1] as f32
|
||||
+ eff_opacity * blended_opaque[1] as f32)
|
||||
.round() as u8;
|
||||
row[out_idx + 2] = ((1.0 - eff_opacity) * dst[2] as f32
|
||||
+ eff_opacity * blended_opaque[2] as f32)
|
||||
.round() as u8;
|
||||
if eff_opacity <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let blended_rgb = if blend == hcie_blend::BlendMode::Normal {
|
||||
adj_rgb
|
||||
} else {
|
||||
let blended = blend_pixels(
|
||||
[dst[0], dst[1], dst[2], 255],
|
||||
[adj_rgb[0], adj_rgb[1], adj_rgb[2], 255],
|
||||
blend,
|
||||
1.0,
|
||||
);
|
||||
[blended[0], blended[1], blended[2]]
|
||||
};
|
||||
|
||||
if eff_opacity >= 1.0 {
|
||||
row[out_idx] = blended_rgb[0];
|
||||
row[out_idx + 1] = blended_rgb[1];
|
||||
row[out_idx + 2] = blended_rgb[2];
|
||||
return;
|
||||
}
|
||||
|
||||
let inverse_opacity = 1.0 - eff_opacity;
|
||||
row[out_idx] =
|
||||
(inverse_opacity * dst[0] as f32 + eff_opacity * blended_rgb[0] as f32).round() as u8;
|
||||
row[out_idx + 1] =
|
||||
(inverse_opacity * dst[1] as f32 + eff_opacity * blended_rgb[1] as f32).round() as u8;
|
||||
row[out_idx + 2] =
|
||||
(inverse_opacity * dst[2] as f32 + eff_opacity * blended_rgb[2] as f32).round() as u8;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
//! Regression coverage for optimized Normal-blend adjustment compositing.
|
||||
//!
|
||||
//! **Purpose:** Verifies that bypassing the generic blend dispatcher preserves
|
||||
//! exact RGB output for opaque and partially opaque adjustments.
|
||||
//! **Logic & Workflow:** Builds a one-pixel base and Curves layer, composites
|
||||
//! through the public tiled path, and compares deterministic channel values.
|
||||
//! **Side Effects / Dependencies:** Allocates only one-pixel protocol layers.
|
||||
|
||||
use hcie_blend::Adjustment;
|
||||
use hcie_composite::tiled::composite_tiled;
|
||||
use hcie_protocol::Layer;
|
||||
use hcie_tile::TiledLayer;
|
||||
|
||||
/// Builds a Curves adjustment whose lookup tables map selected input channels.
|
||||
///
|
||||
/// **Arguments:** `opacity` controls the layer-level adjustment mix.
|
||||
/// **Returns:** A one-pixel adjustment layer. **Side Effects / Dependencies:** None.
|
||||
fn curves_layer(opacity: f32) -> Layer {
|
||||
let mut lut_r: Vec<u8> = (0..=255).map(|value| value as u8).collect();
|
||||
let mut lut_g = lut_r.clone();
|
||||
let mut lut_b = lut_r.clone();
|
||||
lut_r[10] = 100;
|
||||
lut_g[20] = 110;
|
||||
lut_b[30] = 120;
|
||||
|
||||
let mut layer = Layer::new_transparent("Curves", 1, 1);
|
||||
layer.adjustment = Some(Adjustment::Curves {
|
||||
lut_r,
|
||||
lut_g,
|
||||
lut_b,
|
||||
});
|
||||
layer.opacity = opacity;
|
||||
layer
|
||||
}
|
||||
|
||||
/// Confirms a fully opaque Normal adjustment assigns the LUT result exactly.
|
||||
#[test]
|
||||
fn opaque_normal_adjustment_matches_lookup_result() {
|
||||
let base = Layer::from_rgba("Base", 1, 1, vec![10, 20, 30, 255]);
|
||||
let adjustment = curves_layer(1.0);
|
||||
let tiles = vec![
|
||||
Some(TiledLayer::from_dense(&base.pixels, 1, 1)),
|
||||
Some(TiledLayer::from_dense(&adjustment.pixels, 1, 1)),
|
||||
];
|
||||
|
||||
let output = composite_tiled(&[base, adjustment], &tiles, 1, 1);
|
||||
|
||||
assert_eq!(output, vec![100, 110, 120, 255]);
|
||||
}
|
||||
|
||||
/// Confirms partial opacity retains the previous rounded interpolation behavior.
|
||||
#[test]
|
||||
fn partial_normal_adjustment_preserves_rounded_interpolation() {
|
||||
let base = Layer::from_rgba("Base", 1, 1, vec![10, 20, 30, 255]);
|
||||
let adjustment = curves_layer(0.5);
|
||||
let tiles = vec![
|
||||
Some(TiledLayer::from_dense(&base.pixels, 1, 1)),
|
||||
Some(TiledLayer::from_dense(&adjustment.pixels, 1, 1)),
|
||||
];
|
||||
|
||||
let output = composite_tiled(&[base, adjustment], &tiles, 1, 1);
|
||||
|
||||
assert_eq!(output, vec![55, 65, 75, 255]);
|
||||
}
|
||||
Reference in New Issue
Block a user