Skip to content

Commit 7b35fa7

Browse files
skia: Improve correctness of gradients
1 parent 72d781d commit 7b35fa7

File tree

1 file changed

+116
-41
lines changed
  • understory_imaging_skia/src

1 file changed

+116
-41
lines changed

understory_imaging_skia/src/lib.rs

Lines changed: 116 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use core::any::Any;
1515
use core::fmt;
1616
use kurbo::Affine;
1717
use peniko::Brush;
18+
use peniko::InterpolationAlphaSpace;
19+
use peniko::color::{ColorSpaceTag, HueDirection};
1820
use skia_safe as sk;
1921
use understory_imaging::{
2022
DrawOp, ImageDesc, ImageId, ImagingBackend, ImagingOp, PaintDesc, PaintId, PathDesc, PathId,
@@ -129,92 +131,121 @@ fn brush_to_paint(brush: &Brush, opacity: f32, paint_xf: Affine) -> sk::Paint {
129131
paint.set_color(skia_safe::Color::from_argb(a_scaled, r, g, b));
130132
}
131133
Brush::Gradient(grad) => {
132-
// Map peniko gradients to Skia shaders. For now we implement
133-
// linear gradients; other kinds fall back to the first stop.
134+
// Map peniko gradients to Skia gradient shaders, honoring
135+
// interpolation color space and hue direction.
134136
let stops = grad.stops.as_ref();
135137
if stops.is_empty() {
136138
paint.set_color(skia_safe::Color::TRANSPARENT);
137139
return paint;
138140
}
139141

140-
let mut colors: Vec<sk::Color> = Vec::with_capacity(stops.len());
142+
let mut colors: Vec<sk::Color4f> = Vec::with_capacity(stops.len());
141143
let mut pos: Vec<f32> = Vec::with_capacity(stops.len());
142144

143145
for s in stops {
144-
let color = s
145-
.color
146-
.to_alpha_color::<peniko::color::Srgb>()
147-
.multiply_alpha(alpha_scale);
148-
let rgba = color.to_rgba8();
149-
let (r, g, b, a) = (rgba.r, rgba.g, rgba.b, rgba.a);
150-
colors.push(skia_safe::Color::from_argb(a, r, g, b));
146+
// Use the dynamic color components directly and apply additional
147+
// opacity as an alpha multiplier.
148+
let comps = s.color.components;
149+
let a = comps[3] * alpha_scale;
150+
colors.push(skia_safe::Color4f::new(comps[0], comps[1], comps[2], a));
151151
pos.push(s.offset.clamp(0.0, 1.0));
152152
}
153153

154-
let tile_mode = match grad.extend {
155-
peniko::Extend::Pad => skia_safe::TileMode::Clamp,
156-
peniko::Extend::Repeat => skia_safe::TileMode::Repeat,
157-
peniko::Extend::Reflect => skia_safe::TileMode::Mirror,
158-
};
154+
let tile_mode = tile_mode_from_extend(grad.extend);
159155

160156
let local = affine_to_matrix(paint_xf);
161157

158+
let interpolation = skia_safe::gradient_shader::Interpolation {
159+
color_space: gradient_shader_cs_from_cs_tag(grad.interpolation_cs),
160+
in_premul: match grad.interpolation_alpha_space {
161+
InterpolationAlphaSpace::Premultiplied => {
162+
skia_safe::gradient_shader::interpolation::InPremul::Yes
163+
}
164+
InterpolationAlphaSpace::Unpremultiplied => {
165+
skia_safe::gradient_shader::interpolation::InPremul::No
166+
}
167+
},
168+
hue_method: gradient_shader_hue_method_from_hue_direction(grad.hue_direction),
169+
};
170+
162171
match grad.kind {
163172
peniko::GradientKind::Linear(line) => {
164173
let p0 = sk::Point::new(f64_to_f32(line.start.x), f64_to_f32(line.start.y));
165174
let p1 = sk::Point::new(f64_to_f32(line.end.x), f64_to_f32(line.end.y));
166-
if let Some(shader) = sk::Shader::linear_gradient(
175+
if let Some(shader) = sk::Shader::linear_gradient_with_interpolation(
167176
(p0, p1),
168-
colors.as_slice(),
169-
Some(pos.as_slice()),
177+
(&colors[..], None),
178+
&pos[..],
170179
tile_mode,
171-
None,
172-
Some(&local),
180+
interpolation,
181+
&Some(local),
173182
) {
174183
paint.set_shader(shader);
175184
}
176185
}
177186
peniko::GradientKind::Radial(rad) => {
178-
let center = sk::Point::new(
187+
let start_center = sk::Point::new(
179188
f64_to_f32(rad.start_center.x),
180189
f64_to_f32(rad.start_center.y),
181190
);
182-
let radius = rad.end_radius;
183-
if let Some(shader) = sk::Shader::radial_gradient(
184-
center,
185-
radius,
186-
colors.as_slice(),
187-
Some(pos.as_slice()),
188-
tile_mode,
189-
None,
190-
Some(&local),
191-
) {
191+
let start_radius = rad.start_radius;
192+
let end_center =
193+
sk::Point::new(f64_to_f32(rad.end_center.x), f64_to_f32(rad.end_center.y));
194+
let end_radius = rad.end_radius;
195+
196+
let shader = if start_center == end_center && start_radius == end_radius {
197+
sk::Shader::radial_gradient_with_interpolation(
198+
(start_center, start_radius),
199+
(&colors[..], None),
200+
&pos[..],
201+
tile_mode,
202+
interpolation,
203+
&Some(local),
204+
)
205+
} else {
206+
sk::Shader::two_point_conical_gradient_with_interpolation(
207+
(start_center, start_radius),
208+
(end_center, end_radius),
209+
(&colors[..], None),
210+
&pos[..],
211+
tile_mode,
212+
interpolation,
213+
&Some(local),
214+
)
215+
};
216+
217+
if let Some(shader) = shader {
192218
paint.set_shader(shader);
193219
}
194220
}
195221
peniko::GradientKind::Sweep(sweep) => {
196222
let center =
197223
sk::Point::new(f64_to_f32(sweep.center.x), f64_to_f32(sweep.center.y));
198224
let angles = (sweep.start_angle.to_degrees(), sweep.end_angle.to_degrees());
199-
if let Some(shader) = sk::Shader::sweep_gradient(
225+
if let Some(shader) = sk::Shader::sweep_gradient_with_interpolation(
200226
center,
201-
colors.as_slice(),
202-
Some(pos.as_slice()),
227+
(&colors[..], None),
228+
&pos[..],
203229
tile_mode,
204-
Some(angles),
205-
None,
206-
Some(&local),
230+
angles,
231+
interpolation,
232+
&Some(local),
207233
) {
208234
paint.set_shader(shader);
209235
}
210236
}
211237
}
212238

213239
// If shader creation failed for any reason, fall back to the last stop.
214-
if paint.shader().is_none()
215-
&& let Some(last) = colors.last()
216-
{
217-
paint.set_color(*last);
240+
if paint.shader().is_none() {
241+
if let Some(last_stop) = stops.last() {
242+
let color = last_stop
243+
.color
244+
.to_alpha_color::<peniko::color::Srgb>()
245+
.multiply_alpha(alpha_scale);
246+
let rgba = color.to_rgba8();
247+
paint.set_color(skia_safe::Color::from_argb(rgba.a, rgba.r, rgba.g, rgba.b));
248+
}
218249
}
219250
}
220251
// Image brushes are not yet mapped; fall back to solid black with opacity.
@@ -231,6 +262,50 @@ fn brush_to_paint(brush: &Brush, opacity: f32, paint_xf: Affine) -> sk::Paint {
231262
paint
232263
}
233264

265+
fn tile_mode_from_extend(extend: peniko::Extend) -> sk::TileMode {
266+
match extend {
267+
peniko::Extend::Pad => sk::TileMode::Clamp,
268+
peniko::Extend::Repeat => sk::TileMode::Repeat,
269+
peniko::Extend::Reflect => sk::TileMode::Mirror,
270+
}
271+
}
272+
273+
fn gradient_shader_cs_from_cs_tag(
274+
color_space: ColorSpaceTag,
275+
) -> skia_safe::gradient_shader::interpolation::ColorSpace {
276+
use skia_safe::gradient_shader::interpolation::ColorSpace as SkGradientShaderColorSpace;
277+
278+
match color_space {
279+
ColorSpaceTag::Srgb => SkGradientShaderColorSpace::SRGB,
280+
ColorSpaceTag::LinearSrgb => SkGradientShaderColorSpace::SRGBLinear,
281+
ColorSpaceTag::Lab => SkGradientShaderColorSpace::Lab,
282+
ColorSpaceTag::Lch => SkGradientShaderColorSpace::LCH,
283+
ColorSpaceTag::Hsl => SkGradientShaderColorSpace::HSL,
284+
ColorSpaceTag::Hwb => SkGradientShaderColorSpace::HWB,
285+
ColorSpaceTag::Oklab => SkGradientShaderColorSpace::OKLab,
286+
ColorSpaceTag::Oklch => SkGradientShaderColorSpace::OKLCH,
287+
ColorSpaceTag::DisplayP3 => SkGradientShaderColorSpace::DisplayP3,
288+
ColorSpaceTag::A98Rgb => SkGradientShaderColorSpace::A98RGB,
289+
ColorSpaceTag::ProphotoRgb => SkGradientShaderColorSpace::ProphotoRGB,
290+
ColorSpaceTag::Rec2020 => SkGradientShaderColorSpace::Rec2020,
291+
_ => SkGradientShaderColorSpace::SRGB,
292+
}
293+
}
294+
295+
fn gradient_shader_hue_method_from_hue_direction(
296+
direction: HueDirection,
297+
) -> skia_safe::gradient_shader::interpolation::HueMethod {
298+
use skia_safe::gradient_shader::interpolation::HueMethod as SkGradientShaderHueMethod;
299+
300+
match direction {
301+
HueDirection::Shorter => SkGradientShaderHueMethod::Shorter,
302+
HueDirection::Longer => SkGradientShaderHueMethod::Longer,
303+
HueDirection::Increasing => SkGradientShaderHueMethod::Increasing,
304+
HueDirection::Decreasing => SkGradientShaderHueMethod::Decreasing,
305+
_ => SkGradientShaderHueMethod::Shorter,
306+
}
307+
}
308+
234309
fn map_blend_mode(mode: &understory_imaging::BlendMode) -> sk::BlendMode {
235310
use peniko::{Compose, Mix};
236311

0 commit comments

Comments
 (0)