1use crate::{prelude::*, scalar, BlendMode, Color, Color4f, ColorSpace, NativeFlattenable};
2use skia_bindings::{self as sb, SkColorFilter, SkFlattenable, SkRefCntBase};
3use std::fmt;
4
5pub type ColorFilter = RCHandle<SkColorFilter>;
6unsafe_send_sync!(ColorFilter);
7require_type_equality!(sb::SkColorFilter_INHERITED, SkFlattenable);
8
9impl NativeBase<SkRefCntBase> for SkColorFilter {}
10
11impl NativeRefCountedBase for SkColorFilter {
12 type Base = SkRefCntBase;
13}
14
15impl NativeBase<SkFlattenable> for SkColorFilter {}
16
17impl NativeFlattenable for SkColorFilter {
18 fn native_flattenable(&self) -> &SkFlattenable {
19 self.base()
20 }
21
22 fn native_deserialize(data: &[u8]) -> *mut Self {
23 unsafe { sb::C_SkColorFilter_Deserialize(data.as_ptr() as _, length:data.len()) }
24 }
25}
26
27impl fmt::Debug for ColorFilter {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f&mut DebugStruct<'_, '_>.debug_struct("ColorFilter")
30 .field("as_a_color_mode", &self.to_a_color_mode())
31 .field("as_a_color_matrix", &self.to_a_color_matrix())
32 .field(name:"is_alpha_unchanged", &self.is_alpha_unchanged())
33 .finish()
34 }
35}
36
37/// ColorFilters are optional objects in the drawing pipeline. When present in
38/// a paint, they are called with the "src" colors, and return new colors, which
39/// are then passed onto the next stage (either ImageFilter or Xfermode).
40///
41/// All subclasses are required to be reentrant-safe : it must be legal to share
42/// the same instance between several threads.
43impl ColorFilter {
44 /// If the filter can be represented by a source color plus Mode, this
45 /// returns the color and mode appropriately.
46 /// If not, this returns `None` and ignores the parameters.
47 pub fn to_a_color_mode(&self) -> Option<(Color, BlendMode)> {
48 let mut color: Color = 0.into();
49 let mut mode: BlendMode = Default::default();
50 unsafe { self.native().asAColorMode(color.native_mut(), &mut mode) }
51 .if_true_some((color, mode))
52 }
53
54 /// If the filter can be represented by a 5x4 matrix, this
55 /// returns the matrix appropriately.
56 /// If not, this returns `None` and ignores the parameter.
57 pub fn to_a_color_matrix(&self) -> Option<[scalar; 20]> {
58 let mut matrix: [scalar; 20] = Default::default();
59 unsafe { self.native().asAColorMatrix(&mut matrix[0]) }.if_true_some(matrix)
60 }
61
62 /// Returns `true` if the filter is guaranteed to never change the alpha of a color it filters.
63 pub fn is_alpha_unchanged(&self) -> bool {
64 unsafe { self.native().isAlphaUnchanged() }
65 }
66
67 #[deprecated(since = "0.79.0", note = "Use filter_color4f()")]
68 pub fn filter_color(&self, color: impl Into<Color>) -> Color {
69 self.filter_color4f(color.into(), None, None).to_color()
70 }
71
72 /// Converts the src color (in src colorspace), into the dst colorspace,
73 /// then applies this filter to it, returning the filtered color in the dst colorspace.
74 pub fn filter_color4f(
75 &self,
76 color: impl Into<Color4f>,
77 src_color_space: Option<&ColorSpace>,
78 dst_color_space: Option<&ColorSpace>,
79 ) -> Color4f {
80 Color4f::from_native_c(unsafe {
81 sb::C_SkColorFilter_filterColor4f(
82 self.native(),
83 color.into().native(),
84 src_color_space.native_ptr_or_null_mut_force(),
85 dst_color_space.native_ptr_or_null_mut_force(),
86 )
87 })
88 }
89
90 /// Construct a color filter whose effect is to first apply the inner filter and then apply
91 /// this filter, applied to the output of the inner filter.
92 ///
93 /// result = this(inner(...))
94 pub fn composed(&self, inner: impl Into<ColorFilter>) -> Option<Self> {
95 ColorFilter::from_ptr(unsafe {
96 sb::C_SkColorFilter_makeComposed(self.native(), inner.into().into_ptr())
97 })
98 }
99
100 /// Return a color filter that will compute this filter in a specific color space. By default
101 /// all filters operate in the destination (surface) color space. This allows filters like Blend
102 /// and Matrix, or runtime color filters to perform their math in a known space.
103 pub fn with_working_color_space(&self, color_space: impl Into<ColorSpace>) -> Option<Self> {
104 ColorFilter::from_ptr(unsafe {
105 sb::C_SkColorFilter_withWorkingColorSpace(self.native(), color_space.into().into_ptr())
106 })
107 }
108}
109
110pub mod color_filters {
111 use crate::{prelude::*, Color4f, ColorSpace, ColorTable};
112 use crate::{scalar, BlendMode, Color, ColorFilter, ColorMatrix};
113 use skia_bindings as sb;
114
115 pub fn compose(
116 outer: impl Into<ColorFilter>,
117 inner: impl Into<ColorFilter>,
118 ) -> Option<ColorFilter> {
119 ColorFilter::from_ptr(unsafe {
120 sb::C_SkColorFilters_Compose(outer.into().into_ptr(), inner.into().into_ptr())
121 })
122 }
123
124 /// Blends between the constant color (src) and input color (dst) based on the [`BlendMode`].
125 /// If the color space is `None`, the constant color is assumed to be defined in sRGB.
126 pub fn blend_with_color_space(
127 c: impl Into<Color4f>,
128 color_space: impl Into<Option<ColorSpace>>,
129 mode: BlendMode,
130 ) -> Option<ColorFilter> {
131 ColorFilter::from_ptr(unsafe {
132 sb::C_SkColorFilters_Blend2(
133 c.into().native(),
134 color_space.into().into_ptr_or_null(),
135 mode,
136 )
137 })
138 }
139
140 pub fn blend(c: impl Into<Color>, mode: BlendMode) -> Option<ColorFilter> {
141 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Blend(c.into().into_native(), mode) })
142 }
143
144 pub use sb::SkColorFilters_Clamp as Clamp;
145 variant_name!(Clamp::No);
146
147 pub fn matrix(color_matrix: &ColorMatrix, clamp: impl Into<Option<Clamp>>) -> ColorFilter {
148 let clamp = clamp.into().unwrap_or(Clamp::Yes);
149 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Matrix(color_matrix.native(), clamp) })
150 .unwrap()
151 }
152
153 pub fn matrix_row_major(array: &[scalar; 20], clamp: impl Into<Option<Clamp>>) -> ColorFilter {
154 let clamp = clamp.into().unwrap_or(Clamp::Yes);
155 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_MatrixRowMajor(array.as_ptr(), clamp) })
156 .unwrap()
157 }
158
159 // A version of Matrix which operates in HSLA space instead of RGBA.
160 // I.e. HSLA-to-RGBA(Matrix(RGBA-to-HSLA(input))).
161 pub fn hsla_matrix_of_color_matrix(color_matrix: &ColorMatrix) -> ColorFilter {
162 ColorFilter::from_ptr(unsafe {
163 sb::C_SkColorFilters_HSLAMatrixOfColorMatrix(color_matrix.native())
164 })
165 .unwrap()
166 }
167
168 /// See [`hsla_matrix_of_color_matrix()`]
169 pub fn hsla_matrix(row_major: &[f32; 20]) -> ColorFilter {
170 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_HSLAMatrix(row_major.as_ptr()) })
171 .unwrap()
172 }
173
174 pub fn linear_to_srgb_gamma() -> ColorFilter {
175 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_LinearToSRGBGamma() }).unwrap()
176 }
177
178 pub fn srgb_to_linear_gamma() -> ColorFilter {
179 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_SRGBToLinearGamma() }).unwrap()
180 }
181
182 pub fn lerp(
183 t: f32,
184 dst: impl Into<ColorFilter>,
185 src: impl Into<ColorFilter>,
186 ) -> Option<ColorFilter> {
187 ColorFilter::from_ptr(unsafe {
188 sb::C_SkColorFilters_Lerp(t, dst.into().into_ptr(), src.into().into_ptr())
189 })
190 }
191
192 /// Create a table color filter, copying the table into the filter, and
193 /// applying it to all 4 components.
194 /// `a' = table[a];`
195 /// `r' = table[r];`
196 /// `g' = table[g];`
197 /// `b' = table[b];`
198 /// Components are operated on in unpremultiplied space. If the incoming
199 /// colors are premultiplied, they are temporarily unpremultiplied, then
200 /// the table is applied, and then the result is re-multiplied.
201 pub fn table(table: &[u8; 256]) -> Option<ColorFilter> {
202 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Table(table.as_ptr()) })
203 }
204
205 /// Create a table color filter, with a different table for each
206 /// component [A, R, G, B]. If a given table is `None`, then it is
207 /// treated as identity, with the component left unchanged. If a table
208 /// is not `None`, then its contents are copied into the filter.
209 pub fn table_argb<'a>(
210 table_a: impl Into<Option<&'a [u8; 256]>>,
211 table_r: impl Into<Option<&'a [u8; 256]>>,
212 table_g: impl Into<Option<&'a [u8; 256]>>,
213 table_b: impl Into<Option<&'a [u8; 256]>>,
214 ) -> Option<ColorFilter> {
215 ColorFilter::from_ptr(unsafe {
216 sb::C_SkColorFilters_TableARGB(
217 table_a.into().map(|t| t.as_ref()).as_ptr_or_null(),
218 table_r.into().map(|t| t.as_ref()).as_ptr_or_null(),
219 table_g.into().map(|t| t.as_ref()).as_ptr_or_null(),
220 table_b.into().map(|t| t.as_ref()).as_ptr_or_null(),
221 )
222 })
223 }
224
225 /// Create a table color filter that holds a ref to the shared color table.
226 pub fn table_from_color_table(table: impl Into<ColorTable>) -> Option<ColorFilter> {
227 ColorFilter::from_ptr(unsafe { sb::C_SkColorFilters_Table2(table.into().into_ptr()) })
228 }
229
230 /// Create a color filter that multiplies the RGB channels by one color, and
231 /// then adds a second color, pinning the result for each component to
232 /// [0..255]. The alpha components of the mul and add arguments
233 /// are ignored.
234 pub fn lighting(mul: impl Into<Color>, add: impl Into<Color>) -> Option<ColorFilter> {
235 ColorFilter::from_ptr(unsafe {
236 sb::C_SkColorFilters_Lighting(mul.into().into_native(), add.into().into_native())
237 })
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use crate::prelude::*;
244 use crate::{color_filters, BlendMode, Color, Color4f, ColorSpace};
245
246 #[test]
247 fn color_mode_roundtrip() {
248 let color = Color::CYAN;
249 let mode = BlendMode::ColorBurn;
250 let cf = color_filters::blend(color, mode).unwrap();
251 let (c, m) = cf.to_a_color_mode().unwrap();
252 assert_eq!(color, c);
253 assert_eq!(mode, m);
254 }
255
256 #[test]
257 fn ref_count() {
258 let color = Color::CYAN;
259 let mode = BlendMode::ColorBurn;
260 let cf = color_filters::blend(color, mode).unwrap();
261 let rc = cf.native()._ref_cnt();
262 assert_eq!(1, rc);
263 }
264
265 #[test]
266 fn filter_color() {
267 let color = Color::CYAN;
268 let mode = BlendMode::ColorBurn;
269 let cf = color_filters::blend(color, mode).unwrap();
270 #[allow(deprecated)]
271 let _fc = cf.filter_color(Color::DARK_GRAY);
272 let _fc = cf.filter_color4f(
273 Color4f::new(0.0, 0.0, 0.0, 0.0),
274 Some(&ColorSpace::new_srgb()),
275 None,
276 );
277 }
278}
279