1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use skia_safe::{
ColorFilter as SkColorFilter, ImageFilter as SkImageFilter, color_filters, image_filters,
luma_color_filter,
};
use crate::native::color::{RgbaLinear, linear_srgb_color_space, rgba_linear_to_unpremul_color4f};
use crate::native::error::NativeError;
/// Image-domain filter (blur, drop shadow, color matrix wrapped as image
/// filter, compose). Composed by `NativePaint` and applied to draws.
#[derive(Clone)]
pub struct NativeImageFilter {
pub(crate) inner: SkImageFilter,
}
impl std::fmt::Debug for NativeImageFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeImageFilter").finish_non_exhaustive()
}
}
/// Color-domain filter (luma, gamma transfers, color matrix, compose).
/// Composed by `NativePaint` or wrapped as an image filter via
/// `NativeImageFilter::from_color_filter`.
#[derive(Clone)]
pub struct NativeColorFilter {
pub(crate) inner: SkColorFilter,
}
impl std::fmt::Debug for NativeColorFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeColorFilter").finish_non_exhaustive()
}
}
impl NativeImageFilter {
/// Gaussian blur with separable sigmas. `input` is the upstream filter
/// to blur, or `None` to blur the source draw.
pub fn blur(
sigma_x: f32,
sigma_y: f32,
input: Option<NativeImageFilter>,
) -> Result<Self, NativeError> {
let inner = input.map(|f| f.inner);
image_filters::blur((sigma_x, sigma_y), None, inner, None)
.map(|f| NativeImageFilter { inner: f })
.ok_or_else(|| NativeError::FilterCreate {
reason: format!("blur({sigma_x}, {sigma_y}) failed"),
})
}
/// Drop shadow at `(dx, dy)` with separable blur sigmas. `color` is the
/// shadow color (premultiplied linear; treated as already in the
/// destination's working color space).
pub fn drop_shadow(
dx: f32,
dy: f32,
sigma_x: f32,
sigma_y: f32,
color: RgbaLinear,
input: Option<NativeImageFilter>,
) -> Result<Self, NativeError> {
let unpremul = rgba_linear_to_unpremul_color4f(color);
let inner = input.map(|f| f.inner);
// Tag the shadow color as linear-light sRGB. Without an
// explicit color space, Skia treats the value as
// sRGB-encoded and gamma-decodes it -- darkening the shadow.
let cs = linear_srgb_color_space();
image_filters::drop_shadow(
skia_safe::Vector::new(dx, dy),
(sigma_x, sigma_y),
unpremul,
Some(cs),
inner,
None,
)
.map(|f| NativeImageFilter { inner: f })
.ok_or_else(|| NativeError::FilterCreate {
reason: format!("drop_shadow({dx}, {dy}) failed"),
})
}
/// 4x5 color matrix in row-major order:
///
/// ```text
/// | r_r r_g r_b r_a r_offset |
/// | g_r g_g g_b g_a g_offset |
/// | b_r b_g b_b b_a b_offset |
/// | a_r a_g a_b a_a a_offset |
/// ```
///
/// Output channel `c` = `c_r * r_in + c_g * g_in + c_b * b_in + c_a *
/// a_in + c_offset`. Offsets are in the 0..1 range for u8 channels.
pub fn color_matrix(
matrix: [f32; 20],
input: Option<NativeImageFilter>,
) -> Result<Self, NativeError> {
let cf = color_filters::matrix_row_major(&matrix, None);
let inner = input.map(|f| f.inner);
image_filters::color_filter(cf, inner, None)
.map(|f| NativeImageFilter { inner: f })
.ok_or_else(|| NativeError::FilterCreate {
reason: "color_matrix failed".to_string(),
})
}
/// Wrap a `NativeColorFilter` as an image filter, optionally chained
/// onto `input`.
pub fn from_color_filter(
color_filter: NativeColorFilter,
input: Option<NativeImageFilter>,
) -> Result<Self, NativeError> {
let inner = input.map(|f| f.inner);
image_filters::color_filter(color_filter.inner, inner, None)
.map(|f| NativeImageFilter { inner: f })
.ok_or_else(|| NativeError::FilterCreate {
reason: "from_color_filter failed".to_string(),
})
}
/// Compose two image filters: `outer(inner(source))`.
pub fn compose(
outer: NativeImageFilter,
inner: NativeImageFilter,
) -> Result<Self, NativeError> {
image_filters::compose(outer.inner, inner.inner)
.map(|f| NativeImageFilter { inner: f })
.ok_or_else(|| NativeError::FilterCreate {
reason: "image filter compose failed".to_string(),
})
}
}
impl NativeColorFilter {
/// Skia's luma color filter: output alpha = perceived luminance of the
/// input RGB, output RGB = 0. Useful as the `inner` filter in a
/// `destination-in` mask path: luminance becomes the alpha mask.
pub fn luma() -> Self {
Self {
inner: luma_color_filter::new(),
}
}
/// Apply the linear-to-sRGB gamma transfer to the input color before
/// downstream draws see it. Used to bridge linear-light pipelines to
/// gamma-coded readers.
pub fn linear_to_srgb_gamma() -> Self {
Self {
inner: color_filters::linear_to_srgb_gamma(),
}
}
/// Inverse of `linear_to_srgb_gamma`.
pub fn srgb_to_linear_gamma() -> Self {
Self {
inner: color_filters::srgb_to_linear_gamma(),
}
}
/// Compose two color filters: `outer(inner(input))`.
pub fn compose(
outer: NativeColorFilter,
inner: NativeColorFilter,
) -> Result<Self, NativeError> {
color_filters::compose(outer.inner, inner.inner)
.map(|f| NativeColorFilter { inner: f })
.ok_or_else(|| NativeError::FilterCreate {
reason: "color filter compose failed".to_string(),
})
}
}