1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use std::rc::Rc;
6
7use rgb::{FromSlice, RGBA8};
8use tiny_skia::IntRect;
9use usvg::{ApproxEqUlps, ApproxZeroUlps};
10
11mod box_blur;
12mod color_matrix;
13mod component_transfer;
14mod composite;
15mod convolve_matrix;
16mod displacement_map;
17mod iir_blur;
18mod lighting;
19mod morphology;
20mod turbulence;
21
22// TODO: apply single primitive filters in-place
23
24/// An image reference.
25///
26/// Image pixels should be stored in RGBA order.
27///
28/// Some filters will require premultipled channels, some not.
29/// See specific filter documentation for details.
30#[derive(Clone, Copy)]
31pub struct ImageRef<'a> {
32 data: &'a [RGBA8],
33 width: u32,
34 height: u32,
35}
36
37impl<'a> ImageRef<'a> {
38 /// Creates a new image reference.
39 ///
40 /// Doesn't clone the provided data.
41 #[inline]
42 pub fn new(width: u32, height: u32, data: &'a [RGBA8]) -> Self {
43 ImageRef {
44 data,
45 width,
46 height,
47 }
48 }
49
50 #[inline]
51 fn alpha_at(&self, x: u32, y: u32) -> i16 {
52 self.data[(self.width * y + x) as usize].a as i16
53 }
54}
55
56/// A mutable `ImageRef` variant.
57pub struct ImageRefMut<'a> {
58 data: &'a mut [RGBA8],
59 width: u32,
60 height: u32,
61}
62
63impl<'a> ImageRefMut<'a> {
64 /// Creates a new mutable image reference.
65 ///
66 /// Doesn't clone the provided data.
67 #[inline]
68 pub fn new(width: u32, height: u32, data: &'a mut [RGBA8]) -> Self {
69 ImageRefMut {
70 data,
71 width,
72 height,
73 }
74 }
75
76 #[inline]
77 fn pixel_at(&self, x: u32, y: u32) -> RGBA8 {
78 self.data[(self.width * y + x) as usize]
79 }
80
81 #[inline]
82 fn pixel_at_mut(&mut self, x: u32, y: u32) -> &mut RGBA8 {
83 &mut self.data[(self.width * y + x) as usize]
84 }
85}
86
87#[derive(Debug)]
88pub(crate) enum Error {
89 InvalidRegion,
90 NoResults,
91}
92
93trait PixmapExt: Sized {
94 fn try_create(width: u32, height: u32) -> Result<tiny_skia::Pixmap, Error>;
95 fn copy_region(&self, region: IntRect) -> Result<tiny_skia::Pixmap, Error>;
96 fn clear(&mut self);
97 fn into_srgb(&mut self);
98 fn into_linear_rgb(&mut self);
99}
100
101impl PixmapExt for tiny_skia::Pixmap {
102 fn try_create(width: u32, height: u32) -> Result<tiny_skia::Pixmap, Error> {
103 tiny_skia::Pixmap::new(width, height).ok_or(Error::InvalidRegion)
104 }
105
106 fn copy_region(&self, region: IntRect) -> Result<tiny_skia::Pixmap, Error> {
107 let rect = IntRect::from_xywh(region.x(), region.y(), region.width(), region.height())
108 .ok_or(Error::InvalidRegion)?;
109 self.clone_rect(rect).ok_or(Error::InvalidRegion)
110 }
111
112 fn clear(&mut self) {
113 self.fill(tiny_skia::Color::TRANSPARENT);
114 }
115
116 fn into_srgb(&mut self) {
117 demultiply_alpha(self.data_mut().as_rgba_mut());
118 from_linear_rgb(self.data_mut().as_rgba_mut());
119 multiply_alpha(self.data_mut().as_rgba_mut());
120 }
121
122 fn into_linear_rgb(&mut self) {
123 demultiply_alpha(self.data_mut().as_rgba_mut());
124 into_linear_rgb(self.data_mut().as_rgba_mut());
125 multiply_alpha(self.data_mut().as_rgba_mut());
126 }
127}
128
129/// Multiplies provided pixels alpha.
130fn multiply_alpha(data: &mut [RGBA8]) {
131 for p: &mut RGBA in data {
132 let a: f32 = p.a as f32 / 255.0;
133 p.b = (p.b as f32 * a + 0.5) as u8;
134 p.g = (p.g as f32 * a + 0.5) as u8;
135 p.r = (p.r as f32 * a + 0.5) as u8;
136 }
137}
138
139/// Demultiplies provided pixels alpha.
140fn demultiply_alpha(data: &mut [RGBA8]) {
141 for p: &mut RGBA in data {
142 let a: f32 = p.a as f32 / 255.0;
143 p.b = (p.b as f32 / a + 0.5) as u8;
144 p.g = (p.g as f32 / a + 0.5) as u8;
145 p.r = (p.r as f32 / a + 0.5) as u8;
146 }
147}
148
149/// Precomputed sRGB to LinearRGB table.
150///
151/// Since we are storing the result in `u8`, there is no need to compute those
152/// values each time. Mainly because it's very expensive.
153///
154/// ```text
155/// if (C_srgb <= 0.04045)
156/// C_lin = C_srgb / 12.92;
157/// else
158/// C_lin = pow((C_srgb + 0.055) / 1.055, 2.4);
159/// ```
160///
161/// Thanks to librsvg for the idea.
162#[rustfmt::skip]
163const SRGB_TO_LINEAR_RGB_TABLE: &[u8; 256] = &[
164 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
165 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
166 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7,
167 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13,
168 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20,
169 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29,
170 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 37, 38, 39, 40, 41,
171 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54,
172 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
173 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88,
174 90, 91, 92, 93, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109,
175 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133,
176 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159,
177 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188,
178 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,
179 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255,
180];
181
182/// Precomputed LinearRGB to sRGB table.
183///
184/// Since we are storing the result in `u8`, there is no need to compute those
185/// values each time. Mainly because it's very expensive.
186///
187/// ```text
188/// if (C_lin <= 0.0031308)
189/// C_srgb = C_lin * 12.92;
190/// else
191/// C_srgb = 1.055 * pow(C_lin, 1.0 / 2.4) - 0.055;
192/// ```
193///
194/// Thanks to librsvg for the idea.
195#[rustfmt::skip]
196const LINEAR_RGB_TO_SRGB_TABLE: &[u8; 256] = &[
197 0, 13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69,
198 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98,
199 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119,
200 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136,
201 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151,
202 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164,
203 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176,
204 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187,
205 188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197,
206 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207,
207 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216,
208 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224,
209 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233,
210 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240,
211 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248,
212 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255,
213];
214
215/// Converts input pixel from sRGB into LinearRGB.
216///
217/// Provided pixels should have an **unpremultiplied alpha**.
218///
219/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one.
220fn into_linear_rgb(data: &mut [RGBA8]) {
221 for p: &mut RGBA in data {
222 p.r = SRGB_TO_LINEAR_RGB_TABLE[p.r as usize];
223 p.g = SRGB_TO_LINEAR_RGB_TABLE[p.g as usize];
224 p.b = SRGB_TO_LINEAR_RGB_TABLE[p.b as usize];
225 }
226}
227
228/// Converts input pixel from LinearRGB into sRGB.
229///
230/// Provided pixels should have an **unpremultiplied alpha**.
231///
232/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one.
233fn from_linear_rgb(data: &mut [RGBA8]) {
234 for p: &mut RGBA in data {
235 p.r = LINEAR_RGB_TO_SRGB_TABLE[p.r as usize];
236 p.g = LINEAR_RGB_TO_SRGB_TABLE[p.g as usize];
237 p.b = LINEAR_RGB_TO_SRGB_TABLE[p.b as usize];
238 }
239}
240
241// TODO: https://github.com/rust-lang/rust/issues/44095
242#[inline]
243fn f32_bound(min: f32, val: f32, max: f32) -> f32 {
244 debug_assert!(min.is_finite());
245 debug_assert!(val.is_finite());
246 debug_assert!(max.is_finite());
247
248 if val > max {
249 max
250 } else if val < min {
251 min
252 } else {
253 val
254 }
255}
256
257#[derive(Clone)]
258struct Image {
259 /// Filter primitive result.
260 ///
261 /// All images have the same size which is equal to the current filter region.
262 image: Rc<tiny_skia::Pixmap>,
263
264 /// Image's region that has actual data.
265 ///
266 /// Region is in global coordinates and not in `image` one.
267 ///
268 /// Image's content outside this region will be transparent/cleared.
269 ///
270 /// Currently used only for `feTile`.
271 region: IntRect,
272
273 /// The current color space.
274 color_space: usvg::filter::ColorInterpolation,
275}
276
277impl Image {
278 fn from_image(image: tiny_skia::Pixmap, color_space: usvg::filter::ColorInterpolation) -> Self {
279 let (w, h) = (image.width(), image.height());
280 Image {
281 image: Rc::new(image),
282 region: IntRect::from_xywh(0, 0, w, h).unwrap(),
283 color_space,
284 }
285 }
286
287 fn into_color_space(
288 self,
289 color_space: usvg::filter::ColorInterpolation,
290 ) -> Result<Self, Error> {
291 if color_space != self.color_space {
292 let region = self.region;
293
294 let mut image = self.take()?;
295
296 match color_space {
297 usvg::filter::ColorInterpolation::SRGB => image.into_srgb(),
298 usvg::filter::ColorInterpolation::LinearRGB => image.into_linear_rgb(),
299 }
300
301 Ok(Image {
302 image: Rc::new(image),
303 region,
304 color_space,
305 })
306 } else {
307 Ok(self)
308 }
309 }
310
311 fn take(self) -> Result<tiny_skia::Pixmap, Error> {
312 match Rc::try_unwrap(self.image) {
313 Ok(v) => Ok(v),
314 Err(v) => Ok((*v).clone()),
315 }
316 }
317
318 fn width(&self) -> u32 {
319 self.image.width()
320 }
321
322 fn height(&self) -> u32 {
323 self.image.height()
324 }
325
326 fn as_ref(&self) -> &tiny_skia::Pixmap {
327 &self.image
328 }
329}
330
331struct FilterResult {
332 name: String,
333 image: Image,
334}
335
336pub fn apply(
337 filter: &usvg::filter::Filter,
338 ts: tiny_skia::Transform,
339 source: &mut tiny_skia::Pixmap,
340) {
341 let result: Result = apply_inner(filter, ts, source);
342 let result: Result<(), Error> = result.and_then(|image: Image| apply_to_canvas(input:image, pixmap:source));
343
344 // Clear on error.
345 if result.is_err() {
346 source.fill(color:tiny_skia::Color::TRANSPARENT);
347 }
348
349 match result {
350 Ok(_) => {}
351 Err(Error::InvalidRegion) => {
352 log::warn!("Filter has an invalid region.");
353 }
354 Err(Error::NoResults) => {}
355 }
356}
357
358fn apply_inner(
359 filter: &usvg::filter::Filter,
360 ts: usvg::Transform,
361 source: &mut tiny_skia::Pixmap,
362) -> Result<Image, Error> {
363 let region = filter
364 .rect()
365 .transform(ts)
366 .map(|r| r.to_int_rect())
367 .ok_or(Error::InvalidRegion)?;
368
369 let mut results: Vec<FilterResult> = Vec::new();
370
371 for primitive in filter.primitives() {
372 let mut subregion = primitive
373 .rect()
374 .transform(ts)
375 .map(|r| r.to_int_rect())
376 .ok_or(Error::InvalidRegion)?;
377
378 // `feOffset` inherits its region from the input.
379 if let usvg::filter::Kind::Offset(ref fe) = primitive.kind() {
380 if let usvg::filter::Input::Reference(ref name) = fe.input() {
381 if let Some(res) = results.iter().rev().find(|v| v.name == *name) {
382 subregion = res.image.region;
383 }
384 }
385 }
386
387 let cs = primitive.color_interpolation();
388
389 let mut result = match primitive.kind() {
390 usvg::filter::Kind::Blend(ref fe) => {
391 let input1 = get_input(fe.input1(), region, source, &results)?;
392 let input2 = get_input(fe.input2(), region, source, &results)?;
393 apply_blend(fe, cs, region, input1, input2)
394 }
395 usvg::filter::Kind::DropShadow(ref fe) => {
396 let input = get_input(fe.input(), region, source, &results)?;
397 apply_drop_shadow(fe, cs, ts, input)
398 }
399 usvg::filter::Kind::Flood(ref fe) => apply_flood(fe, region),
400 usvg::filter::Kind::GaussianBlur(ref fe) => {
401 let input = get_input(fe.input(), region, source, &results)?;
402 apply_blur(fe, cs, ts, input)
403 }
404 usvg::filter::Kind::Offset(ref fe) => {
405 let input = get_input(fe.input(), region, source, &results)?;
406 apply_offset(fe, ts, input)
407 }
408 usvg::filter::Kind::Composite(ref fe) => {
409 let input1 = get_input(fe.input1(), region, source, &results)?;
410 let input2 = get_input(fe.input2(), region, source, &results)?;
411 apply_composite(fe, cs, region, input1, input2)
412 }
413 usvg::filter::Kind::Merge(ref fe) => apply_merge(fe, cs, region, source, &results),
414 usvg::filter::Kind::Tile(ref fe) => {
415 let input = get_input(fe.input(), region, source, &results)?;
416 apply_tile(input, region)
417 }
418 usvg::filter::Kind::Image(ref fe) => apply_image(fe, region, subregion, ts),
419 usvg::filter::Kind::ComponentTransfer(ref fe) => {
420 let input = get_input(fe.input(), region, source, &results)?;
421 apply_component_transfer(fe, cs, input)
422 }
423 usvg::filter::Kind::ColorMatrix(ref fe) => {
424 let input = get_input(fe.input(), region, source, &results)?;
425 apply_color_matrix(fe, cs, input)
426 }
427 usvg::filter::Kind::ConvolveMatrix(ref fe) => {
428 let input = get_input(fe.input(), region, source, &results)?;
429 apply_convolve_matrix(fe, cs, input)
430 }
431 usvg::filter::Kind::Morphology(ref fe) => {
432 let input = get_input(fe.input(), region, source, &results)?;
433 apply_morphology(fe, cs, ts, input)
434 }
435 usvg::filter::Kind::DisplacementMap(ref fe) => {
436 let input1 = get_input(fe.input1(), region, source, &results)?;
437 let input2 = get_input(fe.input2(), region, source, &results)?;
438 apply_displacement_map(fe, region, cs, ts, input1, input2)
439 }
440 usvg::filter::Kind::Turbulence(ref fe) => apply_turbulence(fe, region, cs, ts),
441 usvg::filter::Kind::DiffuseLighting(ref fe) => {
442 let input = get_input(fe.input(), region, source, &results)?;
443 apply_diffuse_lighting(fe, region, cs, ts, input)
444 }
445 usvg::filter::Kind::SpecularLighting(ref fe) => {
446 let input = get_input(fe.input(), region, source, &results)?;
447 apply_specular_lighting(fe, region, cs, ts, input)
448 }
449 }?;
450
451 if region != subregion {
452 // Clip result.
453
454 // TODO: explain
455 let subregion2 = if let usvg::filter::Kind::Offset(..) = primitive.kind() {
456 // We do not support clipping on feOffset.
457 region.translate_to(0, 0)
458 } else {
459 subregion.translate(-region.x(), -region.y())
460 }
461 .unwrap();
462
463 let color_space = result.color_space;
464
465 let pixmap = {
466 // This is cropping by clearing the pixels outside the region.
467 let mut paint = tiny_skia::Paint::default();
468 paint.set_color(tiny_skia::Color::BLACK);
469 paint.blend_mode = tiny_skia::BlendMode::Clear;
470
471 let mut pixmap = result.take()?;
472 let w = pixmap.width() as f32;
473 let h = pixmap.height() as f32;
474
475 if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, w, subregion2.y() as f32) {
476 pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
477 }
478
479 if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, subregion2.x() as f32, h) {
480 pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
481 }
482
483 if let Some(rect) = tiny_skia::Rect::from_xywh(subregion2.right() as f32, 0.0, w, h)
484 {
485 pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
486 }
487
488 if let Some(rect) =
489 tiny_skia::Rect::from_xywh(0.0, subregion2.bottom() as f32, w, h)
490 {
491 pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
492 }
493
494 pixmap
495 };
496
497 result = Image {
498 image: Rc::new(pixmap),
499 region: subregion,
500 color_space,
501 };
502 }
503
504 results.push(FilterResult {
505 name: primitive.result().to_string(),
506 image: result,
507 });
508 }
509
510 if let Some(res) = results.pop() {
511 Ok(res.image)
512 } else {
513 Err(Error::NoResults)
514 }
515}
516
517fn get_input(
518 input: &usvg::filter::Input,
519 region: IntRect,
520 source: &tiny_skia::Pixmap,
521 results: &[FilterResult],
522) -> Result<Image, Error> {
523 match input {
524 usvg::filter::Input::SourceGraphic => {
525 let image = source.clone();
526
527 Ok(Image {
528 image: Rc::new(image),
529 region,
530 color_space: usvg::filter::ColorInterpolation::SRGB,
531 })
532 }
533 usvg::filter::Input::SourceAlpha => {
534 let mut image = source.clone();
535 // Set RGB to black. Keep alpha as is.
536 for p in image.data_mut().as_rgba_mut() {
537 p.r = 0;
538 p.g = 0;
539 p.b = 0;
540 }
541
542 Ok(Image {
543 image: Rc::new(image),
544 region,
545 color_space: usvg::filter::ColorInterpolation::SRGB,
546 })
547 }
548 usvg::filter::Input::Reference(ref name) => {
549 if let Some(v) = results.iter().rev().find(|v| v.name == *name) {
550 Ok(v.image.clone())
551 } else {
552 // Technically unreachable.
553 log::warn!("Unknown filter primitive reference '{}'.", name);
554 get_input(&usvg::filter::Input::SourceGraphic, region, source, results)
555 }
556 }
557 }
558}
559
560trait PixmapToImageRef<'a> {
561 fn as_image_ref(&'a self) -> ImageRef<'a>;
562 fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a>;
563}
564
565impl<'a> PixmapToImageRef<'a> for tiny_skia::Pixmap {
566 fn as_image_ref(&'a self) -> ImageRef<'a> {
567 ImageRef::new(self.width(), self.height(), self.data().as_rgba())
568 }
569
570 fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a> {
571 ImageRefMut::new(self.width(), self.height(), self.data_mut().as_rgba_mut())
572 }
573}
574
575fn apply_drop_shadow(
576 fe: &usvg::filter::DropShadow,
577 cs: usvg::filter::ColorInterpolation,
578 ts: usvg::Transform,
579 input: Image,
580) -> Result<Image, Error> {
581 let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) {
582 Some(v) => v,
583 None => return Ok(input),
584 };
585
586 let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?;
587 let input_pixmap = input.into_color_space(cs)?.take()?;
588 let mut shadow_pixmap = input_pixmap.clone();
589
590 if let Some((std_dx, std_dy, use_box_blur)) =
591 resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts)
592 {
593 if use_box_blur {
594 box_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut());
595 } else {
596 iir_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut());
597 }
598 }
599
600 // flood
601 let color = tiny_skia::Color::from_rgba8(
602 fe.color().red,
603 fe.color().green,
604 fe.color().blue,
605 fe.opacity().to_u8(),
606 );
607 for p in shadow_pixmap.pixels_mut() {
608 let mut color = color;
609 color.apply_opacity(p.alpha() as f32 / 255.0);
610 *p = color.premultiply().to_color_u8();
611 }
612
613 match cs {
614 usvg::filter::ColorInterpolation::SRGB => shadow_pixmap.into_srgb(),
615 usvg::filter::ColorInterpolation::LinearRGB => shadow_pixmap.into_linear_rgb(),
616 }
617
618 pixmap.draw_pixmap(
619 dx as i32,
620 dy as i32,
621 shadow_pixmap.as_ref(),
622 &tiny_skia::PixmapPaint::default(),
623 tiny_skia::Transform::identity(),
624 None,
625 );
626
627 pixmap.draw_pixmap(
628 0,
629 0,
630 input_pixmap.as_ref(),
631 &tiny_skia::PixmapPaint::default(),
632 tiny_skia::Transform::identity(),
633 None,
634 );
635
636 Ok(Image::from_image(pixmap, cs))
637}
638
639fn apply_blur(
640 fe: &usvg::filter::GaussianBlur,
641 cs: usvg::filter::ColorInterpolation,
642 ts: usvg::Transform,
643 input: Image,
644) -> Result<Image, Error> {
645 let (std_dx: f64, std_dy: f64, use_box_blur: bool) =
646 match resolve_std_dev(std_dx:fe.std_dev_x().get(), std_dy:fe.std_dev_y().get(), ts) {
647 Some(v: (f64, f64, bool)) => v,
648 None => return Ok(input),
649 };
650
651 let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?;
652
653 if use_box_blur {
654 box_blur::apply(sigma_x:std_dx, sigma_y:std_dy, src:pixmap.as_image_ref_mut());
655 } else {
656 iir_blur::apply(sigma_x:std_dx, sigma_y:std_dy, src:pixmap.as_image_ref_mut());
657 }
658
659 Ok(Image::from_image(image:pixmap, color_space:cs))
660}
661
662fn apply_offset(
663 fe: &usvg::filter::Offset,
664 ts: usvg::Transform,
665 input: Image,
666) -> Result<Image, Error> {
667 let (dx: f32, dy: f32) = match scale_coordinates(x:fe.dx(), y:fe.dy(), ts) {
668 Some(v: (f32, f32)) => v,
669 None => return Ok(input),
670 };
671
672 if dx.approx_zero_ulps(4) && dy.approx_zero_ulps(4) {
673 return Ok(input);
674 }
675
676 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?;
677 pixmap.draw_pixmap(
678 x:dx as i32,
679 y:dy as i32,
680 pixmap:input.as_ref().as_ref(),
681 &tiny_skia::PixmapPaint::default(),
682 tiny_skia::Transform::identity(),
683 mask:None,
684 );
685
686 Ok(Image::from_image(image:pixmap, input.color_space))
687}
688
689fn apply_blend(
690 fe: &usvg::filter::Blend,
691 cs: usvg::filter::ColorInterpolation,
692 region: IntRect,
693 input1: Image,
694 input2: Image,
695) -> Result<Image, Error> {
696 let input1 = input1.into_color_space(cs)?;
697 let input2 = input2.into_color_space(cs)?;
698
699 let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
700
701 pixmap.draw_pixmap(
702 0,
703 0,
704 input2.as_ref().as_ref(),
705 &tiny_skia::PixmapPaint::default(),
706 tiny_skia::Transform::identity(),
707 None,
708 );
709
710 pixmap.draw_pixmap(
711 0,
712 0,
713 input1.as_ref().as_ref(),
714 &tiny_skia::PixmapPaint {
715 blend_mode: crate::render::convert_blend_mode(fe.mode()),
716 ..tiny_skia::PixmapPaint::default()
717 },
718 tiny_skia::Transform::identity(),
719 None,
720 );
721
722 Ok(Image::from_image(pixmap, cs))
723}
724
725fn apply_composite(
726 fe: &usvg::filter::Composite,
727 cs: usvg::filter::ColorInterpolation,
728 region: IntRect,
729 input1: Image,
730 input2: Image,
731) -> Result<Image, Error> {
732 use usvg::filter::CompositeOperator as Operator;
733
734 let input1 = input1.into_color_space(cs)?;
735 let input2 = input2.into_color_space(cs)?;
736
737 let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
738
739 if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator() {
740 let pixmap1 = input1.take()?;
741 let pixmap2 = input2.take()?;
742
743 composite::arithmetic(
744 k1,
745 k2,
746 k3,
747 k4,
748 pixmap1.as_image_ref(),
749 pixmap2.as_image_ref(),
750 pixmap.as_image_ref_mut(),
751 );
752
753 return Ok(Image::from_image(pixmap, cs));
754 }
755
756 pixmap.draw_pixmap(
757 0,
758 0,
759 input2.as_ref().as_ref(),
760 &tiny_skia::PixmapPaint::default(),
761 tiny_skia::Transform::identity(),
762 None,
763 );
764
765 let blend_mode = match fe.operator() {
766 Operator::Over => tiny_skia::BlendMode::SourceOver,
767 Operator::In => tiny_skia::BlendMode::SourceIn,
768 Operator::Out => tiny_skia::BlendMode::SourceOut,
769 Operator::Atop => tiny_skia::BlendMode::SourceAtop,
770 Operator::Xor => tiny_skia::BlendMode::Xor,
771 Operator::Arithmetic { .. } => tiny_skia::BlendMode::SourceOver,
772 };
773
774 pixmap.draw_pixmap(
775 0,
776 0,
777 input1.as_ref().as_ref(),
778 &tiny_skia::PixmapPaint {
779 blend_mode,
780 ..tiny_skia::PixmapPaint::default()
781 },
782 tiny_skia::Transform::identity(),
783 None,
784 );
785
786 Ok(Image::from_image(pixmap, cs))
787}
788
789fn apply_merge(
790 fe: &usvg::filter::Merge,
791 cs: usvg::filter::ColorInterpolation,
792 region: IntRect,
793 source: &tiny_skia::Pixmap,
794 results: &[FilterResult],
795) -> Result<Image, Error> {
796 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
797
798 for input: &Input in fe.inputs() {
799 let input: Image = get_input(input, region, source, results)?;
800 let input: Image = input.into_color_space(cs)?;
801 pixmap.draw_pixmap(
802 x:0,
803 y:0,
804 pixmap:input.as_ref().as_ref(),
805 &tiny_skia::PixmapPaint::default(),
806 tiny_skia::Transform::identity(),
807 mask:None,
808 );
809 }
810
811 Ok(Image::from_image(image:pixmap, color_space:cs))
812}
813
814fn apply_flood(fe: &usvg::filter::Flood, region: IntRect) -> Result<Image, Error> {
815 let c: Color = fe.color();
816
817 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
818 pixmap.fill(tiny_skia::Color::from_rgba8(
819 r:c.red,
820 g:c.green,
821 b:c.blue,
822 a:fe.opacity().to_u8(),
823 ));
824
825 Ok(Image::from_image(
826 image:pixmap,
827 color_space:usvg::filter::ColorInterpolation::SRGB,
828 ))
829}
830
831fn apply_tile(input: Image, region: IntRect) -> Result<Image, Error> {
832 let subregion: IntRect = input.region.translate(-region.x(), -region.y()).unwrap();
833
834 let tile_pixmap: Pixmap = input.image.copy_region(subregion)?;
835 let mut paint: Paint<'_> = tiny_skia::Paint::default();
836 paint.shader = tiny_skia::Pattern::new(
837 tile_pixmap.as_ref(),
838 tiny_skia::SpreadMode::Repeat,
839 quality:tiny_skia::FilterQuality::Bicubic,
840 opacity:1.0,
841 tiny_skia::Transform::from_translate(tx:subregion.x() as f32, ty:subregion.y() as f32),
842 );
843
844 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
845 let rect: Rect = tiny_skiaOption::Rect::from_xywh(x:0.0, y:0.0, w:region.width() as f32, h:region.height() as f32)
846 .unwrap();
847 pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), mask:None);
848
849 Ok(Image::from_image(
850 image:pixmap,
851 color_space:usvg::filter::ColorInterpolation::SRGB,
852 ))
853}
854
855fn apply_image(
856 fe: &usvg::filter::Image,
857 region: IntRect,
858 subregion: IntRect,
859 ts: usvg::Transform,
860) -> Result<Image, Error> {
861 let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
862
863 match fe.data() {
864 usvg::filter::ImageKind::Image(ref kind) => {
865 let dx = (subregion.x() - region.x()) as f32;
866 let dy = (subregion.y() - region.y()) as f32;
867 let transform = tiny_skia::Transform::from_translate(dx, dy);
868
869 let view_box = usvg::ViewBox {
870 rect: subregion
871 .translate_to(0, 0)
872 .unwrap()
873 .to_rect()
874 .to_non_zero_rect()
875 .unwrap(),
876 aspect: fe.aspect(),
877 };
878
879 crate::image::render_inner(
880 kind,
881 view_box,
882 transform,
883 fe.rendering_mode(),
884 &mut pixmap.as_mut(),
885 );
886 }
887 usvg::filter::ImageKind::Use(ref node) => {
888 let (sx, sy) = ts.get_scale();
889
890 let transform = tiny_skia::Transform::from_row(
891 sx,
892 0.0,
893 0.0,
894 sy,
895 subregion.x() as f32,
896 subregion.y() as f32,
897 );
898
899 let ctx = crate::render::Context {
900 max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height())
901 .unwrap(),
902 };
903
904 crate::render::render_nodes(node, &ctx, transform, &mut pixmap.as_mut());
905 }
906 }
907
908 Ok(Image::from_image(
909 pixmap,
910 usvg::filter::ColorInterpolation::SRGB,
911 ))
912}
913
914fn apply_component_transfer(
915 fe: &usvg::filter::ComponentTransfer,
916 cs: usvg::filter::ColorInterpolation,
917 input: Image,
918) -> Result<Image, Error> {
919 let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?;
920
921 demultiply_alpha(data:pixmap.data_mut().as_rgba_mut());
922 component_transfer::apply(fe, src:pixmap.as_image_ref_mut());
923 multiply_alpha(data:pixmap.data_mut().as_rgba_mut());
924
925 Ok(Image::from_image(image:pixmap, color_space:cs))
926}
927
928fn apply_color_matrix(
929 fe: &usvg::filter::ColorMatrix,
930 cs: usvg::filter::ColorInterpolation,
931 input: Image,
932) -> Result<Image, Error> {
933 let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?;
934
935 demultiply_alpha(data:pixmap.data_mut().as_rgba_mut());
936 color_matrix::apply(matrix:fe.kind(), src:pixmap.as_image_ref_mut());
937 multiply_alpha(data:pixmap.data_mut().as_rgba_mut());
938
939 Ok(Image::from_image(image:pixmap, color_space:cs))
940}
941
942fn apply_convolve_matrix(
943 fe: &usvg::filter::ConvolveMatrix,
944 cs: usvg::filter::ColorInterpolation,
945 input: Image,
946) -> Result<Image, Error> {
947 let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?;
948
949 if fe.preserve_alpha() {
950 demultiply_alpha(data:pixmap.data_mut().as_rgba_mut());
951 }
952
953 convolve_matrix::apply(matrix:fe, src:pixmap.as_image_ref_mut());
954
955 Ok(Image::from_image(image:pixmap, color_space:cs))
956}
957
958fn apply_morphology(
959 fe: &usvg::filter::Morphology,
960 cs: usvg::filter::ColorInterpolation,
961 ts: usvg::Transform,
962 input: Image,
963) -> Result<Image, Error> {
964 let mut pixmap: Pixmap = input.into_color_space(cs)?.take()?;
965
966 let (rx: f32, ry: f32) = match scale_coordinates(x:fe.radius_x().get(), y:fe.radius_y().get(), ts) {
967 Some(v: (f32, f32)) => v,
968 None => return Ok(Image::from_image(image:pixmap, color_space:cs)),
969 };
970
971 if !(rx > 0.0 && ry > 0.0) {
972 pixmap.clear();
973 return Ok(Image::from_image(image:pixmap, color_space:cs));
974 }
975
976 morphology::apply(fe.operator(), rx, ry, src:pixmap.as_image_ref_mut());
977
978 Ok(Image::from_image(image:pixmap, color_space:cs))
979}
980
981fn apply_displacement_map(
982 fe: &usvg::filter::DisplacementMap,
983 region: IntRect,
984 cs: usvg::filter::ColorInterpolation,
985 ts: usvg::Transform,
986 input1: Image,
987 input2: Image,
988) -> Result<Image, Error> {
989 let pixmap1: Pixmap = input1.into_color_space(cs)?.take()?;
990 let pixmap2: Pixmap = input2.into_color_space(cs)?.take()?;
991
992 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
993
994 let (sx: f32, sy: f32) = match scale_coordinates(x:fe.scale(), y:fe.scale(), ts) {
995 Some(v: (f32, f32)) => v,
996 None => return Ok(Image::from_image(image:pixmap1, color_space:cs)),
997 };
998
999 displacement_map::apply(
1000 fe,
1001 sx,
1002 sy,
1003 src:pixmap1.as_image_ref(),
1004 map:pixmap2.as_image_ref(),
1005 dest:pixmap.as_image_ref_mut(),
1006 );
1007
1008 Ok(Image::from_image(image:pixmap, color_space:cs))
1009}
1010
1011fn apply_turbulence(
1012 fe: &usvg::filter::Turbulence,
1013 region: IntRect,
1014 cs: usvg::filter::ColorInterpolation,
1015 ts: usvg::Transform,
1016) -> Result<Image, Error> {
1017 let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
1018
1019 let (sx, sy) = ts.get_scale();
1020 if sx.approx_zero_ulps(4) || sy.approx_zero_ulps(4) {
1021 return Ok(Image::from_image(pixmap, cs));
1022 }
1023
1024 turbulence::apply(
1025 region.x() as f64 - ts.tx as f64,
1026 region.y() as f64 - ts.ty as f64,
1027 sx as f64,
1028 sy as f64,
1029 fe.base_frequency_x().get() as f64,
1030 fe.base_frequency_y().get() as f64,
1031 fe.num_octaves(),
1032 fe.seed(),
1033 fe.stitch_tiles(),
1034 fe.kind() == usvg::filter::TurbulenceKind::FractalNoise,
1035 pixmap.as_image_ref_mut(),
1036 );
1037
1038 multiply_alpha(pixmap.data_mut().as_rgba_mut());
1039
1040 Ok(Image::from_image(pixmap, cs))
1041}
1042
1043fn apply_diffuse_lighting(
1044 fe: &usvg::filter::DiffuseLighting,
1045 region: IntRect,
1046 cs: usvg::filter::ColorInterpolation,
1047 ts: usvg::Transform,
1048 input: Image,
1049) -> Result<Image, Error> {
1050 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
1051
1052 let light_source: LightSource = transform_light_source(fe.light_source(), region, ts);
1053
1054 lighting::diffuse_lighting(
1055 fe,
1056 light_source,
1057 src:input.as_ref().as_image_ref(),
1058 dest:pixmap.as_image_ref_mut(),
1059 );
1060
1061 Ok(Image::from_image(image:pixmap, color_space:cs))
1062}
1063
1064fn apply_specular_lighting(
1065 fe: &usvg::filter::SpecularLighting,
1066 region: IntRect,
1067 cs: usvg::filter::ColorInterpolation,
1068 ts: usvg::Transform,
1069 input: Image,
1070) -> Result<Image, Error> {
1071 let mut pixmap: Pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
1072
1073 let light_source: LightSource = transform_light_source(fe.light_source(), region, ts);
1074
1075 lighting::specular_lighting(
1076 fe,
1077 light_source,
1078 src:input.as_ref().as_image_ref(),
1079 dest:pixmap.as_image_ref_mut(),
1080 );
1081
1082 Ok(Image::from_image(image:pixmap, color_space:cs))
1083}
1084
1085// TODO: do not modify LightSource
1086fn transform_light_source(
1087 mut source: usvg::filter::LightSource,
1088 region: IntRect,
1089 ts: usvg::Transform,
1090) -> usvg::filter::LightSource {
1091 use std::f32::consts::SQRT_2;
1092 use usvg::filter::LightSource;
1093
1094 match source {
1095 LightSource::DistantLight(..) => {}
1096 LightSource::PointLight(ref mut light) => {
1097 let mut point = tiny_skia::Point::from_xy(light.x, light.y);
1098 ts.map_point(&mut point);
1099 light.x = point.x - region.x() as f32;
1100 light.y = point.y - region.y() as f32;
1101 light.z = light.z * (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2;
1102 }
1103 LightSource::SpotLight(ref mut light) => {
1104 let sz = (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2;
1105
1106 let mut point = tiny_skia::Point::from_xy(light.x, light.y);
1107 ts.map_point(&mut point);
1108 light.x = point.x - region.x() as f32;
1109 light.y = point.y - region.x() as f32;
1110 light.z *= sz;
1111
1112 let mut point = tiny_skia::Point::from_xy(light.points_at_x, light.points_at_y);
1113 ts.map_point(&mut point);
1114 light.points_at_x = point.x - region.x() as f32;
1115 light.points_at_y = point.y - region.x() as f32;
1116 light.points_at_z *= sz;
1117 }
1118 }
1119
1120 source
1121}
1122
1123fn apply_to_canvas(input: Image, pixmap: &mut tiny_skia::Pixmap) -> Result<(), Error> {
1124 let input: Image = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?;
1125
1126 pixmap.fill(color:tiny_skia::Color::TRANSPARENT);
1127 pixmap.draw_pixmap(
1128 x:0,
1129 y:0,
1130 pixmap:input.as_ref().as_ref(),
1131 &tiny_skia::PixmapPaint::default(),
1132 tiny_skia::Transform::identity(),
1133 mask:None,
1134 );
1135
1136 Ok(())
1137}
1138
1139/// Calculates Gaussian blur sigmas for the current world transform.
1140///
1141/// If the last flag is set, then a box blur should be used. Or IIR otherwise.
1142fn resolve_std_dev(std_dx: f32, std_dy: f32, ts: usvg::Transform) -> Option<(f64, f64, bool)> {
1143 let (mut std_dx: f32, mut std_dy: f32) = scale_coordinates(x:std_dx, y:std_dy, ts)?;
1144
1145 // 'A negative value or a value of zero disables the effect of the given filter primitive
1146 // (i.e., the result is the filter input image).'
1147 if std_dx.approx_eq_ulps(&0.0, ulps:4) && std_dy.approx_eq_ulps(&0.0, ulps:4) {
1148 return None;
1149 }
1150
1151 // Ignore tiny sigmas. In case of IIR blur it can lead to a transparent image.
1152 if std_dx < 0.05 {
1153 std_dx = 0.0;
1154 }
1155
1156 if std_dy < 0.05 {
1157 std_dy = 0.0;
1158 }
1159
1160 const BLUR_SIGMA_THRESHOLD: f32 = 2.0;
1161 // Check that the current feGaussianBlur filter can be applied using a box blur.
1162 let box_blur: bool = std_dx >= BLUR_SIGMA_THRESHOLD || std_dy >= BLUR_SIGMA_THRESHOLD;
1163
1164 Some((std_dx as f64, std_dy as f64, box_blur))
1165}
1166
1167fn scale_coordinates(x: f32, y: f32, ts: usvg::Transform) -> Option<(f32, f32)> {
1168 let (sx: f32, sy: f32) = ts.get_scale();
1169 Some((x * sx, y * sy))
1170}
1171