### Image struct with color space conversion and ownership handling Source: https://docs.rs/resvg/latest/src/resvg/filter/mod.rs.html Defines the internal Image wrapper around a tiny_skia::Pixmap, storing its region and color interpolation. Provides methods to create from a pixmap, convert between SRGB and LinearRGB color spaces, take ownership of the underlying pixmap (cloning if needed), and query dimensions or get a reference. ```rust impl Image { fn from_image(image: tiny_skia::Pixmap, color_space: usvg::filter::ColorInterpolation) -> Self { let (w, h) = (image.width(), image.height()); Image { image: Rc::new(image), region: IntRect::from_xywh(0, 0, w, h).unwrap(), color_space, } } fn into_color_space( self, color_space: usvg::filter::ColorInterpolation, ) -> Result { if color_space != self.color_space { let region = self.region; let mut image = self.take()?; match color_space { usvg::filter::ColorInterpolation::SRGB => image.into_srgb(), usvg::filter::ColorInterpolation::LinearRGB => image.into_linear_rgb(), } Ok(Image { image: Rc::new(image), region, color_space, }) } else { Ok(self) } } fn take(self) -> Result { match Rc::try_unwrap(self.image) { Ok(v) => Ok(v), Err(v) => Ok((*v).clone()), } } fn width(&self) -> u32 { self.image.width() } fn height(&self) -> u32 { self.image.height() } fn as_ref(&self) -> &tiny_skia::Pixmap { &self.image } } ``` -------------------------------- ### Lighting implementation in lighting.rs Source: https://docs.rs/resvg/latest/src/resvg/filter/lighting.rs.html The core implementation of lighting filters including vector math structures and the public diffuse and specular lighting functions. ```rust 1// Copyright 2020 the Resvg Authors 2// SPDX-License-Identifier: Apache-2.0 OR MIT 3 4use super::{ImageRef, ImageRefMut, f32_bound}; 5use rgb::RGBA8; 6use usvg::filter::{DiffuseLighting, LightSource, SpecularLighting}; 7use usvg::{ApproxEqUlps, ApproxZeroUlps, Color}; 8 9const FACTOR_1_2: f32 = 1.0 / 2.0; 10const FACTOR_1_3: f32 = 1.0 / 3.0; 11const FACTOR_1_4: f32 = 1.0 / 4.0; 12const FACTOR_2_3: f32 = 2.0 / 3.0; 13 14#[derive(Clone, Copy, Debug)] 15struct Vector2 { 16 x: f32, 17 y: f32, 18} 19 20impl Vector2 { 21 #[inline] 22 fn new(x: f32, y: f32) -> Self { 23 Vector2 { x, y } 24 } 25 26 #[inline] 27 fn approx_zero(&self) -> bool { 28 self.x.approx_zero_ulps(4) && self.y.approx_zero_ulps(4) 29 } 30} 31 32impl core::ops::Mul for Vector2 { 33 type Output = Self; 34 35 #[inline] 36 fn mul(self, c: f32) -> Self::Output { 37 Vector2 { 38 x: self.x * c, 39 y: self.y * c, 40 } 41 } 42} 43 44#[derive(Clone, Copy, Debug)] 45struct Vector3 { 46 x: f32, 47 y: f32, 48 z: f32, 49} 50 51impl Vector3 { 52 #[inline] 53 fn new(x: f32, y: f32, z: f32) -> Self { 54 Vector3 { x, y, z } 55 } 56 57 #[inline] 58 fn dot(&self, other: &Self) -> f32 { 59 self.x * other.x + self.y * other.y + self.z * other.z 60 } 61 62 #[inline] 63 fn length(&self) -> f32 { 64 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() 65 } 66 67 #[inline] 68 fn normalized(&self) -> Option { 69 let length = self.length(); 70 if !length.approx_zero_ulps(4) { 71 Some(Vector3 { 72 x: self.x / length, 73 y: self.y / length, 74 z: self.z / length, 75 }) 76 } else { 77 None 78 } 79 } 80} 81 82impl core::ops::Add for Vector3 { 83 type Output = Self; 84 85 #[inline] 86 fn add(self, rhs: Vector3) -> Self::Output { 87 Vector3 { 88 x: self.x + rhs.x, 89 y: self.y + rhs.y, 90 z: self.z + rhs.z, 91 } 92 } 93} 94 95impl core::ops::Sub for Vector3 { 96 type Output = Self; 97 98 #[inline] 99 fn sub(self, rhs: Vector3) -> Self::Output { 100 Vector3 { 101 x: self.x - rhs.x, 102 y: self.y - rhs.y, 103 z: self.z - rhs.z, 104 } 105 } 106} 107 108#[derive(Clone, Copy, Debug)] 109struct Normal { 110 factor: Vector2, 111 normal: Vector2, 112} 113 114impl Normal { 115 #[inline] 116 fn new(factor_x: f32, factor_y: f32, nx: i16, ny: i16) -> Self { 117 Normal { 118 factor: Vector2::new(factor_x, factor_y), 119 normal: Vector2::new(-nx as f32, -ny as f32), 120 } 121 } 122} 123 124/// Renders a diffuse lighting. 125/// 126/// - `src` pixels can have any alpha method, since only the alpha channel is used. 127/// - `dest` will have an **unpremultiplied alpha**. 128/// 129/// Does nothing when `src` is less than 3x3. 130/// 131/// `src` and `dest` dimensions must match. 132pub fn diffuse_lighting( 133 fe: &DiffuseLighting, 134 light_source: LightSource, 135 src: ImageRef, 136 dest: ImageRefMut, 137) { 138 debug_assert!(src.width == dest.width && src.height == dest.height); 139 140 let light_factor = |normal: Normal, light_vector: Vector3| { 141 let k = if normal.normal.approx_zero() { 142 light_vector.z 143 } else { 144 let mut n = normal.normal * (fe.surface_scale() / 255.0); 145 n.x *= normal.factor.x; 146 n.y *= normal.factor.y; 147 148 let normal = Vector3::new(n.x, n.y, 1.0); 149 150 normal.dot(&light_vector) / normal.length() 151 }; 152 153 fe.diffuse_constant() * k 154 }; 155 156 apply( 157 light_source, 158 fe.surface_scale(), 159 fe.lighting_color(), 160 &light_factor, 161 calc_diffuse_alpha, 162 src, 163 dest, 164 ); 165} 166 167/// Renders a specular lighting. 168/// 169/// - `src` pixels can have any alpha method, since only the alpha channel is used. 170/// - `dest` will have a **premultiplied alpha**. 171/// 172/// Does nothing when `src` is less than 3x3. 173/// 174/// `src` and `dest` dimensions must match. 175pub fn specular_lighting( 176 fe: &SpecularLighting, 177 light_source: LightSource, 178 src: ImageRef, 179 dest: ImageRefMut, 180) { 181 debug_assert!(src.width == dest.width && src.height == dest.height); 182 183 let light_factor = |normal: Normal, light_vector: Vector3| { 184 let h = light_vector + Vector3::new(0.0, 0.0, 1.0); 185 let h_length = h.length(); 186 187 if h_length.approx_zero_ulps(4) { 188 return 0.0; 189 } ``` -------------------------------- ### Apply Lighting Function Source: https://docs.rs/resvg/latest/src/resvg/filter/lighting.rs.html Applies light sources to an image buffer by iterating over pixels and calculating lighting factors. ```rust fn apply( light_source: LightSource, surface_scale: f32, lighting_color: Color, light_factor: &dyn Fn(Normal, Vector3) -> f32, calc_alpha: fn(u8, u8, u8) -> u8, src: ImageRef, mut dest: ImageRefMut, ) { if src.width < 3 || src.height < 3 { return; } let width = src.width; let height = src.height; // `feDistantLight` has a fixed vector, so calculate it beforehand. let mut light_vector = match light_source { LightSource::DistantLight(light) => { let azimuth = light.azimuth.to_radians(); let elevation = light.elevation.to_radians(); Vector3::new( azimuth.cos() * elevation.cos(), azimuth.sin() * elevation.cos(), elevation.sin(), ) } _ => Vector3::new(1.0, 1.0, 1.0), }; let mut calc = |nx, ny, normal: Normal| { match light_source { LightSource::DistantLight(_) => {} LightSource::PointLight(ref light) => { let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale; let origin = Vector3::new(light.x, light.y, light.z); let v = origin - Vector3::new(nx as f32, ny as f32, nz); light_vector = v.normalized().unwrap_or(v); } LightSource::SpotLight(ref light) => { let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale; let origin = Vector3::new(light.x, light.y, light.z); let v = origin - Vector3::new(nx as f32, ny as f32, nz); light_vector = v.normalized().unwrap_or(v); } } let light_color = light_color(&light_source, lighting_color, light_vector); let factor = light_factor(normal, light_vector); let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8; let r = compute(light_color.red); let g = compute(light_color.green); let b = compute(light_color.blue); let a = calc_alpha(r, g, b); *dest.pixel_at_mut(nx, ny) = RGBA8 { b, g, r, a }; }; calc(0, 0, top_left_normal(src)); calc(width - 1, 0, top_right_normal(src)); calc(0, height - 1, bottom_left_normal(src)); calc(width - 1, height - 1, bottom_right_normal(src)); for x in 1..width - 1 { calc(x, 0, top_row_normal(src, x)); calc(x, height - 1, bottom_row_normal(src, x)); } for y in 1..height - 1 { calc(0, y, left_column_normal(src, y)); calc(width - 1, y, right_column_normal(src, y)); } for y in 1..height - 1 { for x in 1..width - 1 { calc(x, y, interior_normal(src, x, y)); } } } ``` -------------------------------- ### Gradient Conversion Utilities Source: https://docs.rs/resvg/latest/src/resvg/path.rs.html Functions to convert SVG linear and radial gradients to tiny_skia shaders. ```rust fn convert_linear_gradient( gradient: &usvg::LinearGradient, opacity: usvg::Opacity, ) -> Option> { let (mode, points) = convert_base_gradient(gradient, opacity)?; let shader = tiny_skia::LinearGradient::new( (gradient.x1(), gradient.y1()).into(), (gradient.x2(), gradient.y2()).into(), points, mode, gradient.transform(), )?; Some(shader) } fn convert_radial_gradient( gradient: &usvg::RadialGradient, opacity: usvg::Opacity, ) -> Option> { let (mode, points) = convert_base_gradient(gradient, opacity)?; let shader = tiny_skia::RadialGradient::new( (gradient.fx(), gradient.fy()).into(), gradient.fr().get(), ``` -------------------------------- ### Image Rendering Logic Source: https://docs.rs/resvg/latest/src/resvg/image.rs.html The primary entry point for rendering an image, which checks visibility and delegates to the appropriate rendering implementation. ```rust pub fn render( image: &usvg::Image, transform: tiny_skia::Transform, pixmap: &mut tiny_skia::PixmapMut, ) { if !image.is_visible() { return; } render_inner(image.kind(), transform, image.rendering_mode(), pixmap); } ``` -------------------------------- ### Fill Path Implementation Source: https://docs.rs/resvg/latest/src/resvg/path.rs.html Fills a path using the specified paint, handling colors, gradients, and patterns. ```rust pub fn fill_path( path: &usvg::Path, blend_mode: tiny_skia::BlendMode, ctx: &Context, transform: tiny_skia::Transform, pixmap: &mut tiny_skia::PixmapMut, ) -> Option<()> { let fill = path.fill()?; // Horizontal and vertical lines cannot be filled. Skip. if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 { return None; } let rule = match fill.rule() { usvg::FillRule::NonZero => tiny_skia::FillRule::Winding, usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd, }; let pattern_pixmap; let mut paint = tiny_skia::Paint::default(); match fill.paint() { usvg::Paint::Color(c) => { paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8()); } usvg::Paint::LinearGradient(lg) => { paint.shader = convert_linear_gradient(lg, fill.opacity())?; } usvg::Paint::RadialGradient(rg) => { paint.shader = convert_radial_gradient(rg, fill.opacity())?; } usvg::Paint::Pattern(pattern) => { let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?; pattern_pixmap = patt_pix; paint.shader = tiny_skia::Pattern::new( pattern_pixmap.as_ref(), tiny_skia::SpreadMode::Repeat, tiny_skia::FilterQuality::Bicubic, fill.opacity().get(), patt_ts, ); } } paint.anti_alias = path.rendering_mode().use_shape_antialiasing(); paint.blend_mode = blend_mode; pixmap.fill_path(path.data(), &paint, rule, transform, None); Some(()) } ``` -------------------------------- ### Convert Blend Mode Source: https://docs.rs/resvg/latest/src/resvg/render.rs.html Maps usvg blend modes to tiny-skia blend modes. ```rust pub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode { match mode { usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver, ``` -------------------------------- ### arithmetic(k1, k2, k3, k4, src1, src2, dest) Source: https://docs.rs/resvg/latest/src/resvg/filter/composite.rs.html Performs an arithmetic composition. `src1` and `src2` image pixels should have a premultiplied alpha; `dest` image pixels will have a premultiplied alpha. The dimensions of `src1`, `src2`, and `dest` must match. ```APIDOC ## arithmetic(k1, k2, k3, k4, src1, src2, dest) ### Description Performs an arithmetic composition. `src1` and `src2` image pixels should have a **premultiplied alpha**. `dest` image pixels will have a **premultiplied alpha**. The `src1`, `src2`, and `dest` dimensions must match. ### Method Rust function call. ### Signature ```rust pub fn arithmetic( k1: f32, k2: f32, k3: f32, k4: f32, src1: ImageRef, src2: ImageRef, dest: ImageRefMut, ) ``` ### Parameters - **k1** (f32) - Required - Arithmetic coefficient k1. - **k2** (f32) - Required - Arithmetic coefficient k2. - **k3** (f32) - Required - Arithmetic coefficient k3. - **k4** (f32) - Required - Arithmetic coefficient k4. - **src1** (ImageRef) - Required - First source image with premultiplied alpha. - **src2** (ImageRef) - Required - Second source image with premultiplied alpha. - **dest** (ImageRefMut) - Required - Destination image that will receive the result with premultiplied alpha. ### Notes - Input images (`src1`, `src2`) must have premultiplied alpha. - The output image (`dest`) will contain premultiplied alpha pixels. - The width and height of `src1`, `src2`, and `dest` must all match; otherwise, assertions fail in debug builds. ``` -------------------------------- ### apply_tile function Source: https://docs.rs/resvg/latest/src/resvg/filter/mod.rs.html Creates a tiled pattern from an input image to fill the specified region. ```rust fn apply_tile(input: Image, region: IntRect) -> Result { let subregion = input.region.translate(-region.x(), -region.y()).unwrap(); let tile_pixmap = input.image.copy_region(subregion)?; let mut paint = tiny_skia::Paint::default(); paint.shader = tiny_skia::Pattern::new( tile_pixmap.as_ref(), tiny_skia::SpreadMode::Repeat, tiny_skia::FilterQuality::Bicubic, 1.0, tiny_skia::Transform::from_translate(subregion.x() as f32, subregion.y() as f32), ); let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; ``` -------------------------------- ### IIR Blur Implementation Source: https://docs.rs/resvg/latest/src/resvg/filter/iir_blur.rs.html The core implementation of the IIR blur algorithm, including the main apply function and the recursive Gaussian channel processing. ```rust // Copyright 2020 the Resvg Authors // SPDX-License-Identifier: Apache-2.0 OR MIT // An IIR blur. // // Based on http://www.getreuer.info/home/gaussianiir // // Licensed under 'Simplified BSD License'. // // // Implements the fast Gaussian convolution algorithm of Alvarez and Mazorra, // where the Gaussian is approximated by a cascade of first-order infinite // impulsive response (IIR) filters. Boundaries are handled with half-sample // symmetric extension. // // Gaussian convolution is approached as approximating the heat equation and // each timestep is performed with an efficient recursive computation. Using // more steps yields a more accurate approximation of the Gaussian. A // reasonable default value for `numsteps` is 4. // // Reference: // Alvarez, Mazorra, "Signal and Image Restoration using Shock Filters and // Anisotropic Diffusion," SIAM J. on Numerical Analysis, vol. 31, no. 2, // pp. 590-605, 1994. // TODO: Blurs right and bottom sides twice for some reason. use super::ImageRefMut; struct BlurData { width: usize, height: usize, sigma_x: f64, sigma_y: f64, steps: usize, } /// Applies an IIR blur. /// /// Input image pixels should have a **premultiplied alpha**. /// /// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis. /// /// # Allocations /// /// This method will allocate a 2x `src` buffer. pub fn apply(sigma_x: f64, sigma_y: f64, src: ImageRefMut) { let buf_size = (src.width * src.height) as usize; let mut buf = vec![0.0; buf_size]; let buf = &mut buf; let d = BlurData { width: src.width as usize, height: src.height as usize, sigma_x, sigma_y, steps: 4, }; let data = bytemuck::cast_slice_mut(src.data); gaussian_channel(data, &d, 0, buf); gaussian_channel(data, &d, 1, buf); gaussian_channel(data, &d, 2, buf); gaussian_channel(data, &d, 3, buf); } fn gaussian_channel(data: &mut [u8], d: &BlurData, channel: usize, buf: &mut [f64]) { for i in 0..data.len() / 4 { buf[i] = data[i * 4 + channel] as f64 / 255.0; } gaussianiir2d(d, buf); for i in 0..data.len() / 4 { data[i * 4 + channel] = (buf[i] * 255.0) as u8; } } fn gaussianiir2d(d: &BlurData, buf: &mut [f64]) { // Filter horizontally along each row. let (lambda_x, dnu_x) = if d.sigma_x > 0.0 { let (lambda, dnu) = gen_coefficients(d.sigma_x, d.steps); for y in 0..d.height { for _ in 0..d.steps { let idx = d.width * y; // Filter rightwards. for x in 1..d.width { buf[idx + x] += dnu * buf[idx + x - 1]; } let mut x = d.width - 1; // Filter leftwards. while x > 0 { buf[idx + x - 1] += dnu * buf[idx + x]; x -= 1; } } } (lambda, dnu) } else { (1.0, 1.0) }; // Filter vertically along each column. let (lambda_y, dnu_y) = if d.sigma_y > 0.0 { let (lambda, dnu) = gen_coefficients(d.sigma_y, d.steps); for x in 0..d.width { for _ in 0..d.steps { let idx = x; // Filter downwards. let mut y = d.width; while y < buf.len() { buf[idx + y] += dnu * buf[idx + y - d.width]; y += d.width; } y = buf.len() - d.width; // Filter upwards. while y > 0 { buf[idx + y - d.width] += dnu * buf[idx + y]; y -= d.width; } } } (lambda, dnu) } else { (1.0, 1.0) }; let post_scale = ((dnu_x * dnu_y).sqrt() / (lambda_x * lambda_y).sqrt()).powi(2 * d.steps as i32); buf.iter_mut().for_each(|v| *v *= post_scale); } fn gen_coefficients(sigma: f64, steps: usize) -> (f64, f64) { let lambda = (sigma * sigma) / (2.0 * steps as f64); let dnu = (1.0 + 2.0 * lambda - (1.0 + 4.0 * lambda).sqrt()) / (2.0 * lambda); (lambda, dnu) } ``` -------------------------------- ### render_pattern_pixmap function Source: https://docs.rs/resvg/latest/src/resvg/path.rs.html Renders a usvg::Pattern into a tiny_skia::Pixmap, calculating the necessary transformation matrix for the pattern content. ```rust fn render_pattern_pixmap( pattern: &usvg::Pattern, ctx: &Context, transform: tiny_skia::Transform, ) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> { let (sx, sy) = { let ts2 = transform.pre_concat(pattern.transform()); ts2.get_scale() }; let rect = pattern.rect(); let img_size = tiny_skia::IntSize::from_wh( (rect.width() * sx).round() as u32, (rect.height() * sy).round() as u32, )?; let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?; let transform = tiny_skia::Transform::from_scale(sx, sy); crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut()); let mut ts = tiny_skia::Transform::default(); ts = ts.pre_concat(pattern.transform()); ts = ts.pre_translate(rect.x(), rect.y()); ts = ts.pre_scale(1.0 / sx, 1.0 / sy); Some((pixmap, ts)) } ``` -------------------------------- ### Render raster image Source: https://docs.rs/resvg/latest/src/resvg/image.rs.html Renders a raster image onto a tiny-skia PixmapMut using specified rendering modes and transformations. ```rust pub(crate) fn render_raster( image: &usvg::ImageKind, transform: tiny_skia::Transform, rendering_mode: usvg::ImageRendering, pixmap: &mut tiny_skia::PixmapMut, ) -> Option<()> { let raster = decode_raster(image)?; let rect = tiny_skia::Size::from_wh(raster.width() as f32, raster.height() as f32)? .to_rect(0.0, 0.0)?; let quality = match rendering_mode { ImageRendering::OptimizeQuality => tiny_skia::FilterQuality::Bicubic, ImageRendering::OptimizeSpeed => tiny_skia::FilterQuality::Nearest, ImageRendering::Smooth => tiny_skia::FilterQuality::Bilinear, ImageRendering::HighQuality => tiny_skia::FilterQuality::Bicubic, ImageRendering::CrispEdges => tiny_skia::FilterQuality::Nearest, ImageRendering::Pixelated => tiny_skia::FilterQuality::Nearest, }; let pattern = tiny_skia::Pattern::new( raster.as_ref(), tiny_skia::SpreadMode::Pad, quality, 1.0, tiny_skia::Transform::default(), ); let mut paint = tiny_skia::Paint::default(); paint.shader = pattern; pixmap.fill_rect(rect, &paint, transform, None); Some(()) } ``` -------------------------------- ### Convert sRGB pixel data to LinearRGB (into_linear_rgb) Source: https://docs.rs/resvg/latest/src/resvg/filter/mod.rs.html Converts each pixel's RGB channels from sRGB to LinearRGB using the precomputed table. Requires unpremultiplied alpha; alpha channel must be last, but RGB channel order is irrelevant. ```rust /// Converts input pixel from sRGB into LinearRGB. /// /// Provided pixels should have an **unpremultiplied alpha**. /// /// RGB channels order of the input image doesn't matter, but alpha channel must be the last one. fn into_linear_rgb(data: &mut [RGBA8]) { for p in data { p.r = SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; p.g = SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; p.b = SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; } } ``` -------------------------------- ### apply_specular_lighting Source: https://docs.rs/resvg/latest/src/resvg/filter/mod.rs.html Applies specular lighting to an input image within a specified region. ```rust fn apply_specular_lighting( fe: &usvg::filter::SpecularLighting, region: IntRect, cs: usvg::filter::ColorInterpolation, ts: usvg::Transform, input: Image, ) -> Result { let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; let light_source = transform_light_source(fe.light_source(), region, ts); lighting::specular_lighting( fe, light_source, input.as_ref().as_image_ref(), pixmap.as_image_ref_mut(), ); Ok(Image::from_image(pixmap, cs)) } ``` -------------------------------- ### Path Rendering Logic Source: https://docs.rs/resvg/latest/src/resvg/path.rs.html The render function handles the order of filling and stroking based on the path's paint order. ```rust pub fn render( path: &usvg::Path, blend_mode: tiny_skia::BlendMode, ctx: &Context, transform: tiny_skia::Transform, pixmap: &mut tiny_skia::PixmapMut, ) { if !path.is_visible() { return; } if path.paint_order() == usvg::PaintOrder::FillAndStroke { fill_path(path, blend_mode, ctx, transform, pixmap); stroke_path(path, blend_mode, ctx, transform, pixmap); } else { stroke_path(path, blend_mode, ctx, transform, pixmap); fill_path(path, blend_mode, ctx, transform, pixmap); } } ``` -------------------------------- ### Calculate Light Color Source: https://docs.rs/resvg/latest/src/resvg/filter/lighting.rs.html Determines the color of the light source, handling spot light cone limitations. ```rust fn light_color(light: &LightSource, lighting_color: Color, light_vector: Vector3) -> Color { match *light { LightSource::DistantLight(_) | LightSource::PointLight(_) => lighting_color, LightSource::SpotLight(ref light) => { let origin = Vector3::new(light.x, light.y, light.z); let direction = Vector3::new(light.points_at_x, light.points_at_y, light.points_at_z); let direction = direction - origin; let direction = direction.normalized().unwrap_or(direction); let minus_l_dot_s = -light_vector.dot(&direction); if minus_l_dot_s <= 0.0 { return Color::black(); } if let Some(limiting_cone_angle) = light.limiting_cone_angle { if minus_l_dot_s < limiting_cone_angle.to_radians().cos() { return Color::black(); ``` -------------------------------- ### Precomputed LinearRGB to sRGB table (LINEAR_RGB_TO_SRGB_TABLE) Source: https://docs.rs/resvg/latest/src/resvg/filter/mod.rs.html A 256-entry lookup table mapping LinearRGB byte values to sRGB byte values. Used to avoid expensive per-pixel computations. The table is marked with #[rustfmt::skip] to preserve formatting. ```rust /// Precomputed LinearRGB to sRGB table. /// /// Since we are storing the result in `u8`, there is no need to compute those /// values each time. Mainly because it's very expensive. /// /// ```text /// if (C_lin <= 0.0031308) /// C_srgb = C_lin * 12.92; /// else /// C_srgb = 1.055 * pow(C_lin, 1.0 / 2.4) - 0.055; /// ``` /// /// Thanks to librsvg for the idea. #[rustfmt::skip] const LINEAR_RGB_TO_SRGB_TABLE: &[u8; 256] = &[ 0, 13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 120, 121, 122, 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, 148, 149, 150, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, ]; ``` -------------------------------- ### Convert RGB data to Pixmap Source: https://docs.rs/resvg/latest/src/resvg/image.rs.html Converts a slice of RGB data into a tiny-skia Pixmap, setting the alpha channel to 255. ```rust fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) { use rgb::FromSlice; let mut i = 0; let dst = pixmap.data_mut(); for p in data.as_rgb() { dst[i + 0] = p.r; dst[i + 1] = p.g; dst[i + 2] = p.b; dst[i + 3] = 255; i += tiny_skia::BYTES_PER_PIXEL; } } ``` -------------------------------- ### resvg lib.rs source code Source: https://docs.rs/resvg/latest/src/resvg/lib.rs.html The complete source code of the resvg library's lib.rs file, including the render and render_node functions, the OptionLog trait, the max_filter_bbox helper, and a test module. ```rust // Copyright 2017 the Resvg Authors // SPDX-License-Identifier: Apache-2.0 OR MIT /*! [resvg](https://github.com/linebender/resvg) is an SVG rendering library. */ #![forbid(unsafe_code)] #![warn(missing_docs)] #![allow(clippy::field_reassign_with_default)] #![allow(clippy::identity_op)] #![allow(clippy::too_many_arguments)] #![allow(clippy::uninlined_format_args)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::wrong_self_convention)] pub use tiny_skia; pub use usvg; mod clip; mod filter; mod geom; mod image; mod mask; mod path; mod render; /// Renders a tree onto the pixmap. /// /// `transform` will be used as a root transform. /// Can be used to position SVG inside the `pixmap`. /// /// The produced content is in the sRGB color space. pub fn render( tree: &usvg::Tree, transform: tiny_skia::Transform, pixmap: &mut tiny_skia::PixmapMut, ) { let max_bbox = max_filter_bbox(pixmap.width(), pixmap.height()); let ctx = render::Context { max_bbox }; render::render_nodes(tree.root(), &ctx, transform, pixmap); } /// Renders a node onto the pixmap. /// /// `transform` will be used as a root transform. /// Can be used to position SVG inside the `pixmap`. /// /// The expected pixmap size can be retrieved from `usvg::Node::abs_layer_bounding_box()`. /// /// Returns `None` when `node` has a zero size. /// /// The produced content is in the sRGB color space. pub fn render_node( node: &usvg::Node, mut transform: tiny_skia::Transform, pixmap: &mut tiny_skia::PixmapMut, ) -> Option<()> { let bbox = node.abs_layer_bounding_box()?; let max_bbox = max_filter_bbox(pixmap.width(), pixmap.height()); transform = transform.pre_translate(-bbox.x(), -bbox.y()); let ctx = render::Context { max_bbox }; render::render_node(node, &ctx, transform, pixmap); Some(()) } pub(crate) trait OptionLog { fn log_none(self, f: F) -> Self; } impl OptionLog for Option { #[inline] fn log_none(self, f: F) -> Self { self.or_else(|| { f(); None }) } } fn max_filter_bbox(width: u32, height: u32) -> tiny_skia::IntRect { tiny_skia::IntRect::from_xywh( i32::try_from(width).unwrap_or(i32::MAX).saturating_mul(-2), i32::try_from(height).unwrap_or(i32::MAX).saturating_mul(-2), width.saturating_mul(5), height.saturating_mul(5), ) .unwrap_or_else(|| { tiny_skia::IntRect::from_ltrb(i32::MIN / 2, i32::MIN / 2, i32::MAX / 2, i32::MAX / 2) .unwrap() }) } #[cfg(test)] mod tests { #[test] fn max_filter_bbox_is_clamped() { let bbox = super::max_filter_bbox(u32::MAX, u32::MAX); assert_eq!(bbox.left(), i32::MIN / 2); assert_eq!(bbox.top(), i32::MIN / 2); assert_eq!(bbox.right(), i32::MAX / 2); assert_eq!(bbox.bottom(), i32::MAX / 2); } } ``` -------------------------------- ### Pixel normalization helpers Source: https://docs.rs/resvg/latest/src/resvg/filter/color_matrix.rs.html Utility functions to convert between 8-bit RGBA pixels and normalized floating-point components. ```rust 97#[inline] 98fn to_normalized_components(pixel: RGBA8) -> (f32, f32, f32, f32) { 99 ( 100 pixel.r as f32 / 255.0, 101 pixel.g as f32 / 255.0, 102 pixel.b as f32 / 255.0, 103 pixel.a as f32 / 255.0, 104 ) 105} 106 107#[inline] 108fn from_normalized(c: f32) -> u8 { 109 (f32_bound(0.0, c, 1.0) * 255.0) as u8 110} ``` -------------------------------- ### Fast Rounding Helper Source: https://docs.rs/resvg/latest/src/resvg/filter/box_blur.rs.html An optimized rounding function for x <= 2^23, significantly faster than standard rounding intrinsics. ```rust #[inline] fn round(mut x: f32) -> f32 { x += 12582912.0; x -= 12582912.0; x } ``` -------------------------------- ### Map usvg::BlendMode to tiny_skia::BlendMode Source: https://docs.rs/resvg/latest/src/resvg/render.rs.html This match arm maps various usvg blend modes to their corresponding tiny_skia implementations. ```rust usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply, usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen, usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay, usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken, usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten, usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge, usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn, usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight, usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight, usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference, usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion, usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue, usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation, usvg::BlendMode::Color => tiny_skia::BlendMode::Color, usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity, } } ```