mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-01 13:57:18 -06:00
Merge approved PhotoCraft PR #4711: Treat Dissolve as a per-pixel choice
Port behavior and regression coverage into current modules; preserve current upstream behavior. Current modular compositors already implement spatial Dissolve for adjustments and finished effects. Preserve equivalent implementations and newer Fill/HDR behavior, append approved missing CPU/GPU regression coverage.
This commit is contained in:
@@ -1017,6 +1017,55 @@ fn dissolve_coverage_matches_opacity() {
|
||||
assert_eq!(flatten(&d), b);
|
||||
}
|
||||
|
||||
/// Invert at 50% Dissolve is black or white, not the gray of a Normal mix (#3050).
|
||||
#[test]
|
||||
fn dissolve_adjustment_is_a_per_pixel_choice() {
|
||||
let mut d = doc_white(16, 16);
|
||||
let mut inv = Layer::new("inv", LayerContent::Adjustment(Adjustment::Invert));
|
||||
inv.blend = photocraft_color::BlendMode::Dissolve;
|
||||
inv.opacity = 0.5;
|
||||
d.layers.push(inv);
|
||||
let b = flatten(&d);
|
||||
let (black, white, other) = b.px.iter().fold((0, 0, 0), |(k, w, o), p| {
|
||||
if close4(*p, [0.0, 0.0, 0.0, 1.0]) {
|
||||
(k + 1, w, o)
|
||||
} else if close4(*p, [1.0, 1.0, 1.0, 1.0]) {
|
||||
(k, w + 1, o)
|
||||
} else {
|
||||
(k, w, o + 1)
|
||||
}
|
||||
});
|
||||
assert_eq!(other, 0, "a partial mix would be gray");
|
||||
assert!(black > 40 && white > 40, "black {black} white {white}");
|
||||
}
|
||||
|
||||
/// A clipped black fill at 50% Dissolve stays black or white on an opaque base (#3052).
|
||||
#[test]
|
||||
fn clipped_dissolve_fill_is_a_per_pixel_choice() {
|
||||
let mut d = doc_white(16, 16);
|
||||
let mut l = solid_layer("k", Rect::new(0, 0, 16, 16), [0.0, 0.0, 0.0, 1.0]);
|
||||
l.clipped = true;
|
||||
l.blend = photocraft_color::BlendMode::Dissolve;
|
||||
l.opacity = 0.5;
|
||||
d.layers.push(l);
|
||||
let b = flatten(&d);
|
||||
assert!(b.px.iter().all(|p| close4(*p, [0.0, 0.0, 0.0, 1.0]) || close4(*p, [1.0; 4])));
|
||||
}
|
||||
|
||||
/// A Color Overlay must not turn Dissolve coverage into a Normal mix (#3051).
|
||||
#[test]
|
||||
fn color_overlay_keeps_dissolve_coverage() {
|
||||
use photocraft_doc::effects::{Effect, FxCommon};
|
||||
let mut d = doc_white(16, 16);
|
||||
let mut l = Layer::new("ink", LayerContent::Fill(Fill::Solid(photocraft_color::Color::BLACK)));
|
||||
l.effects.items.push(Effect::ColorOverlay { common: FxCommon::new(photocraft_color::BlendMode::Normal, 1.0), color: photocraft_color::Color::BLACK });
|
||||
l.blend = photocraft_color::BlendMode::Dissolve;
|
||||
l.opacity = 0.5;
|
||||
d.layers.push(l);
|
||||
let b = flatten(&d);
|
||||
assert!(b.px.iter().all(|p| close4(*p, [0.0, 0.0, 0.0, 1.0]) || close4(*p, [1.0; 4])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_is_tile_independent() {
|
||||
// Rendering a sub-rect must equal the same region of a full render.
|
||||
|
||||
@@ -3196,6 +3196,33 @@ fn pass_through_clipping_base_with_an_adjustment() {
|
||||
check(&mut g, &d, "pass-through clipping base with an adjustment");
|
||||
}
|
||||
|
||||
/// Dissolve's adjustment/effect decisions use document coordinates, even in offset regions
|
||||
/// and in the paged compositor. The clipped path comes from #4992.
|
||||
#[test]
|
||||
fn dissolve_adjustments_and_effects_match_cpu_in_offset_regions() {
|
||||
let Some(mut g) = gpu() else { return };
|
||||
for depth in [SampleType::U8, SampleType::U16, SampleType::F32] {
|
||||
for effects in [false, true] {
|
||||
let mut d = Document::new("Dissolve", Size::new(300, 280), ColorMode::Rgb, depth);
|
||||
d.layers.push(noise_layer("base", d.pixel_format(), Rect::new(0, 0, 300, 280), 19, 0.4));
|
||||
let mut layer = if effects {
|
||||
let mut l = noise_layer("effect", d.pixel_format(), Rect::new(-8, 13, 290, 270), 27, 0.6);
|
||||
l.effects.items = vec![Effect::ColorOverlay { common: FxCommon::new(BlendMode::Normal, 0.7), color: Color::rgb(0.2, 0.8, 0.4) }];
|
||||
l
|
||||
} else {
|
||||
Layer::new("adjustment", LayerContent::Adjustment(Adjustment::Invert))
|
||||
};
|
||||
layer.blend = BlendMode::Dissolve;
|
||||
layer.opacity = 0.37;
|
||||
layer.mask = Some(mask(Rect::new(11, 7, 287, 265), 43, 0.0));
|
||||
d.layers.push(layer);
|
||||
let label = format!("Dissolve effects={effects} {depth:?}");
|
||||
fx_check(&mut g, &d, &label);
|
||||
diff_rect(&mut g, &d, Rect::new(17, 23, 281, 269), &label).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transparency Shapes Layer off changes the special eight's blend (the layer's alpha blends as
|
||||
/// Fill, #5692); the GPU has no pass for it and leaves such documents to the CPU. Other modes look
|
||||
/// the same either way and stay on the GPU.
|
||||
|
||||
Reference in New Issue
Block a user