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