mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
Switch the Color struct back to storing unassociated alpha (#4518)
* Switch Color struct back to storing unassociated alpha * Address review feedback * Update the Invert node and legacy image migration for straight alpha and add round-trip tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -144,12 +144,7 @@ fn make_opaque<T: Adjust<Color>>(
|
||||
input: Item<T>,
|
||||
) -> Item<T> {
|
||||
let mut input = input;
|
||||
input.element_mut().adjust(|color| {
|
||||
if color.a() == 0. {
|
||||
return color.with_alpha(1.);
|
||||
}
|
||||
Color::from_rgbaf32_unchecked(color.r() / color.a(), color.g() / color.a(), color.b() / color.a(), 1.)
|
||||
});
|
||||
input.element_mut().adjust(|color| color.with_alpha(1.));
|
||||
input
|
||||
}
|
||||
|
||||
@@ -502,11 +497,7 @@ fn invert<T: Adjust<Color>>(
|
||||
input: Item<T>,
|
||||
) -> Item<T> {
|
||||
let mut input = input;
|
||||
input.element_mut().adjust(|color| {
|
||||
// Invert in gamma space relative to alpha
|
||||
let [r, g, b, a] = color.to_gamma_srgb_channels();
|
||||
Color::from_gamma_srgb_channels(a - r, a - g, a - b, a)
|
||||
});
|
||||
input.element_mut().adjust(|color| color.map_gamma_rgb(|channel| 1. - channel));
|
||||
input
|
||||
}
|
||||
|
||||
@@ -1147,3 +1138,19 @@ mod _graphene_hash_impls {
|
||||
SelectiveColorChoice
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", test))]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn invert_flips_straight_channels_and_keeps_alpha() {
|
||||
let color = Color::from_gamma_srgb_channels(1., 0.25, 0., 0.5);
|
||||
|
||||
let inverted = invert((), Item::new_from_element(color)).into_element();
|
||||
|
||||
let [r, g, b, a] = inverted.to_gamma_srgb_channels();
|
||||
assert!((r - 0.).abs() < 1e-5 && (g - 0.75).abs() < 1e-5 && (b - 1.).abs() < 1e-5, "inverted channels were {r} {g} {b}");
|
||||
assert!((a - 0.5).abs() < 1e-5, "alpha was {a}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,13 +115,10 @@ fn color_overlay<T: Adjust<Color>>(
|
||||
let opacity = (opacity / 100.).clamp(0., 1.);
|
||||
|
||||
image.element_mut().adjust(|pixel| {
|
||||
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
|
||||
let overlay = apply_blend_mode(color, *pixel, blend_mode);
|
||||
let mix = |image: f32, overlay: f32| image + (overlay - image) * opacity;
|
||||
|
||||
// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.
|
||||
let associated_pixel = Color::from_rgbaf32_unchecked(pixel.r() * pixel.a(), pixel.g() * pixel.a(), pixel.b() * pixel.a(), pixel.a());
|
||||
let overlay = apply_blend_mode(color, associated_pixel, blend_mode).map_rgb(|channel| channel * opacity);
|
||||
|
||||
Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())
|
||||
Color::from_rgbaf32_unchecked(mix(pixel.r(), overlay.r()), mix(pixel.g(), overlay.g()), mix(pixel.b(), overlay.b()), pixel.a())
|
||||
});
|
||||
image
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ fn gaussian_blur_algorithm(buffer: Image<Color>, radius: f64, gamma: bool) -> Im
|
||||
unpremultiply_gamma_to_linear(blurred)
|
||||
} else {
|
||||
let mut working = buffer;
|
||||
working.map_pixels(|px| px.apply_opacity(px.a()));
|
||||
working.map_pixels(|px| px.to_associated_alpha());
|
||||
let mut blurred = gaussian_separable(working, &kernel, Color::from_rgbaf32_unchecked);
|
||||
blurred.map_pixels(|px| px.to_unassociated_alpha());
|
||||
blurred
|
||||
@@ -191,7 +191,7 @@ fn box_blur_algorithm(buffer: Image<Color>, radius: f64, gamma: bool) -> Image<C
|
||||
unpremultiply_gamma_to_linear(blurred)
|
||||
} else {
|
||||
let mut working = buffer;
|
||||
working.map_pixels(|px| px.apply_opacity(px.a()));
|
||||
working.map_pixels(|px| px.to_associated_alpha());
|
||||
let mut blurred = box_separable(working, radius, Color::from_rgbaf32_unchecked);
|
||||
blurred.map_pixels(|px| px.to_unassociated_alpha());
|
||||
blurred
|
||||
|
||||
@@ -48,8 +48,10 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Item
|
||||
let (image, mut attributes) = image_frame.into_parts();
|
||||
let (width, height) = (image.width, image.height);
|
||||
|
||||
// Resize the image using the image crate
|
||||
let data = bytemuck::cast_vec(image.into_data().data);
|
||||
// Resize the image using the image crate, which filters each channel independently, so premultiply to keep transparent texels from bleeding into edges
|
||||
let mut image = image.into_data();
|
||||
image.map_pixels(|px| px.to_associated_alpha());
|
||||
let data = bytemuck::cast_vec(image.data);
|
||||
let image_size = DAffine2::from_scale(DVec2::new(width as f64, height as f64));
|
||||
let size_px = image_size.transform_vector2(size).as_uvec2();
|
||||
|
||||
@@ -77,12 +79,13 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Item
|
||||
let buffer = resized.to_rgba32f();
|
||||
let buffer = buffer.into_raw();
|
||||
let vec = bytemuck::cast_vec(buffer);
|
||||
let image = Image {
|
||||
let mut image = Image {
|
||||
width: new_width,
|
||||
height: new_height,
|
||||
data: vec,
|
||||
base64_string: None,
|
||||
};
|
||||
image.map_pixels(|px: Color| px.to_unassociated_alpha());
|
||||
// we need to adjust the offset if we truncate the offset calculation
|
||||
|
||||
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
@@ -257,13 +260,7 @@ pub fn image<'a: 'n>(_: impl Ctx, resource: Item<Resource>) -> Item<Raster<CPU>>
|
||||
};
|
||||
let image = image.to_rgba32f();
|
||||
let image = Image {
|
||||
data: image
|
||||
.chunks(4)
|
||||
.map(|pixel| {
|
||||
let alpha = pixel[3];
|
||||
Color::from_gamma_srgb_channels(pixel[0] * alpha, pixel[1] * alpha, pixel[2] * alpha, alpha)
|
||||
})
|
||||
.collect(),
|
||||
data: image.chunks(4).map(|pixel| Color::from_gamma_srgb_channels(pixel[0], pixel[1], pixel[2], pixel[3])).collect(),
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
..Default::default()
|
||||
|
||||
Reference in New Issue
Block a user