mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Deprecate all usages of the Color struct representing gamma space values, fixing round-trip precision bugs (#4149)
* Deprecate all usages of the Color struct representing gamma space values, fixing round-trip precision bugs * Code review fixes
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::renderer::{RenderParams, format_transform_matrix};
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::uuid::generate_uuid;
|
||||
use glam::DAffine2;
|
||||
use graphic_types::vector_types::gradient::{Gradient, GradientType};
|
||||
@@ -22,7 +23,7 @@ impl RenderExt for Gradient {
|
||||
if position != 0. {
|
||||
let _ = write!(stop, r#" offset="{}""#, (position * 1_000_000.).round() / 1_000_000.);
|
||||
}
|
||||
let _ = write!(stop, r##" stop-color="#{}""##, color.to_rgb_hex_srgb_from_gamma());
|
||||
let _ = write!(stop, r##" stop-color="#{}""##, SRGBA8::from(color).to_rgb_hex());
|
||||
if color.a() < 1. {
|
||||
let _ = write!(stop, r#" stop-opacity="{}""#, (color.a() * 1000.).round() / 1000.);
|
||||
}
|
||||
@@ -86,7 +87,7 @@ impl RenderExt for Fill {
|
||||
match self {
|
||||
Self::None => r#" fill="none""#.to_string(),
|
||||
Self::Solid(color) => {
|
||||
let mut result = format!(r##" fill="#{}""##, color.to_rgb_hex_srgb_from_gamma());
|
||||
let mut result = format!(r##" fill="#{}""##, SRGBA8::from(*color).to_rgb_hex());
|
||||
if color.a() < 1. {
|
||||
let _ = write!(result, r#" fill-opacity="{}""#, (color.a() * 1000.).round() / 1000.);
|
||||
}
|
||||
@@ -132,7 +133,7 @@ impl RenderExt for Stroke {
|
||||
let paint_order = (self.paint_order != PaintOrder::StrokeAbove || render_params.override_paint_order).then_some(PaintOrder::StrokeBelow);
|
||||
|
||||
// Render the needed stroke attributes
|
||||
let mut attributes = format!(r##" stroke="#{}""##, color.to_rgb_hex_srgb_from_gamma());
|
||||
let mut attributes = format!(r##" stroke="#{}""##, SRGBA8::from(color).to_rgb_hex());
|
||||
if color.a() < 1. {
|
||||
let _ = write!(&mut attributes, r#" stroke-opacity="{}""#, (color.a() * 1000.).round() / 1000.);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::render_ext::RenderExt;
|
||||
use crate::to_peniko::BlendModeExt;
|
||||
use crate::to_peniko::{BlendModeExt, ToPenikoColor};
|
||||
use core_types::CacheHash;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::BoundingBox;
|
||||
use core_types::bounds::RenderBoundingBox;
|
||||
use core_types::color::Color;
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::math::quad::Quad;
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
@@ -273,19 +274,20 @@ pub fn black_or_white_for_best_contrast(background: Option<Color>) -> Color {
|
||||
|
||||
let alpha = bg.a();
|
||||
|
||||
// Un-premultiply, then convert to gamma sRGB
|
||||
let srgb = if alpha > f32::EPSILON {
|
||||
Color::from_rgbaf32_unchecked(bg.r() / alpha, bg.g() / alpha, bg.b() / alpha, alpha).to_gamma_srgb()
|
||||
// Un-premultiply, then encode to gamma sRGB to do the composite in display space.
|
||||
let (gamma_r, gamma_g, gamma_b) = if alpha > f32::EPSILON {
|
||||
let [r, g, b, _] = Color::from_rgbaf32_unchecked(bg.r() / alpha, bg.g() / alpha, bg.b() / alpha, alpha).to_gamma_srgb_channels();
|
||||
(r, g, b)
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
(0., 0., 0.)
|
||||
};
|
||||
|
||||
// Composite over black in sRGB space, then convert back to linear for luminance
|
||||
let composited = Color::from_rgbaf32_unchecked(srgb.r() * alpha, srgb.g() * alpha, srgb.b() * alpha, 1.).to_linear_srgb();
|
||||
// Composite over black in sRGB space (premultiplied by alpha), then decode to linear for the luminance test.
|
||||
let composited = Color::from_gamma_srgb_channels(gamma_r * alpha, gamma_g * alpha, gamma_b * alpha, 1.);
|
||||
|
||||
let threshold = (1.05 * 0.05f32).sqrt() - 0.05;
|
||||
|
||||
if composited.luminance_srgb() > threshold { Color::BLACK } else { Color::WHITE }
|
||||
if composited.luminance_rec_709() > threshold { Color::BLACK } else { Color::WHITE }
|
||||
}
|
||||
|
||||
pub fn to_transform(transform: DAffine2) -> usvg::Transform {
|
||||
@@ -311,7 +313,7 @@ fn get_outline_styles(render_params: &RenderParams) -> (kurbo::Stroke, peniko::C
|
||||
};
|
||||
|
||||
let outline_color = black_or_white_for_best_contrast(render_params.artboard_background);
|
||||
let outline_color_peniko = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]);
|
||||
let outline_color_peniko = SRGBA8::from(outline_color).to_peniko_color();
|
||||
|
||||
(outline_stroke, outline_color_peniko)
|
||||
}
|
||||
@@ -573,7 +575,7 @@ impl Render for List<Artboard> {
|
||||
|
||||
// Background
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
attributes.push("fill", format!("#{}", background.to_rgb_hex_srgb_from_gamma()));
|
||||
attributes.push("fill", format!("#{}", SRGBA8::from(background).to_rgb_hex()));
|
||||
if background.a() < 1. {
|
||||
attributes.push("fill-opacity", ((background.a() * 1000.).round() / 1000.).to_string());
|
||||
}
|
||||
@@ -629,7 +631,7 @@ impl Render for List<Artboard> {
|
||||
|
||||
let artboard_transform = kurbo::Affine::new(transform.to_cols_array());
|
||||
|
||||
let color = peniko::Color::new([background.r(), background.g(), background.b(), background.a()]);
|
||||
let color = SRGBA8::from(background).to_peniko_color();
|
||||
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., artboard_transform, &rect);
|
||||
scene.fill(peniko::Fill::NonZero, artboard_transform, color, None, &rect);
|
||||
scene.pop_layer();
|
||||
@@ -1182,7 +1184,7 @@ impl Render for List<Vector> {
|
||||
// Closures to avoid duplicated fill/stroke drawing logic
|
||||
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match element.style.fill() {
|
||||
Fill::Solid(color) => {
|
||||
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
|
||||
let fill = peniko::Brush::Solid(SRGBA8::from(*color).to_peniko_color());
|
||||
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
@@ -1190,7 +1192,7 @@ impl Render for List<Vector> {
|
||||
for (position, color, _) in gradient.stops.interpolated_samples() {
|
||||
stops.push(peniko::ColorStop {
|
||||
offset: position as f32,
|
||||
color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
|
||||
color: peniko::color::DynamicColor::from_alpha_color(SRGBA8::from(color).to_peniko_color()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1267,7 +1269,7 @@ impl Render for List<Vector> {
|
||||
let do_stroke = |scene: &mut Scene, width_scale: f64| {
|
||||
if let Some(stroke) = element.style.stroke() {
|
||||
let color = match stroke.color {
|
||||
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
|
||||
Some(color) => SRGBA8::from(color).to_peniko_color(),
|
||||
None => peniko::Color::TRANSPARENT,
|
||||
};
|
||||
let cap = match stroke.cap {
|
||||
@@ -1811,7 +1813,7 @@ impl Render for List<Color> {
|
||||
const MAX: f64 = 1e7;
|
||||
attributes.push("points", format!("{MAX},{MAX} -{MAX},{MAX} -{MAX},-{MAX} {MAX},-{MAX}"));
|
||||
|
||||
attributes.push("fill", format!("#{}", color.to_rgb_hex_srgb_from_gamma()));
|
||||
attributes.push("fill", format!("#{}", SRGBA8::from(*color).to_rgb_hex()));
|
||||
if color.a() < 1. {
|
||||
attributes.push("fill-opacity", ((color.a() * 1000.).round() / 1000.).to_string());
|
||||
}
|
||||
@@ -1838,7 +1840,7 @@ impl Render for List<Color> {
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);
|
||||
let vello_color = SRGBA8::from(*color).to_peniko_color();
|
||||
|
||||
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||
|
||||
@@ -1895,7 +1897,7 @@ impl Render for List<GradientStops> {
|
||||
|
||||
let mut stop_string = String::new();
|
||||
for (position, color, original_midpoint) in gradient.interpolated_samples() {
|
||||
let _ = write!(stop_string, r##"<stop offset="{}" stop-color="#{}""##, position, color.to_rgb_hex_srgb_from_gamma());
|
||||
let _ = write!(stop_string, r##"<stop offset="{}" stop-color="#{}""##, position, SRGBA8::from(color).to_rgb_hex());
|
||||
if color.a() < 1. {
|
||||
let _ = write!(stop_string, r#" stop-opacity="{}""#, color.a());
|
||||
}
|
||||
@@ -1977,7 +1979,7 @@ impl Render for List<GradientStops> {
|
||||
for (position, color, _) in gradient.interpolated_samples() {
|
||||
stops.push(peniko::ColorStop {
|
||||
offset: position as f32,
|
||||
color: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
|
||||
color: peniko::color::DynamicColor::from_alpha_color(SRGBA8::from(color).to_peniko_color()),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
use core_types::BlendMode;
|
||||
use core_types::color::SRGBA8;
|
||||
use vello::peniko;
|
||||
|
||||
pub trait BlendModeExt {
|
||||
fn to_peniko(&self) -> peniko::Mix;
|
||||
}
|
||||
|
||||
pub trait ToPenikoColor {
|
||||
fn to_peniko_color(&self) -> peniko::Color;
|
||||
}
|
||||
|
||||
impl ToPenikoColor for SRGBA8 {
|
||||
#[inline(always)]
|
||||
fn to_peniko_color(&self) -> peniko::Color {
|
||||
peniko::Color::from_rgba8(self.red, self.green, self.blue, self.alpha)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlendModeExt for BlendMode {
|
||||
fn to_peniko(&self) -> peniko::Mix {
|
||||
match self {
|
||||
|
||||
Reference in New Issue
Block a user