//! Lighting filters and light nodes. use float_cmp::approx_eq; use markup5ever::{expanded_name, local_name, namespace_url, ns}; use nalgebra::{Vector2, Vector3}; use num_traits::identities::Zero; use rayon::prelude::*; use std::cmp::max; use crate::document::AcquiredNodes; use crate::drawing_ctx::DrawingCtx; use crate::element::{set_attribute, ElementData, ElementTrait}; use crate::filters::{ bounds::BoundsBuilder, context::{FilterContext, FilterOutput}, FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams, ResolvedPrimitive, }; use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::paint_server::resolve_color; use crate::parsers::{NonNegative, NumberOptionalNumber, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; use crate::session::Session; use crate::surface_utils::{ shared_surface::{ExclusiveImageSurface, SharedImageSurface, SurfaceType}, ImageSurfaceDataExt, Pixel, }; use crate::transform::Transform; use crate::unit_interval::UnitInterval; use crate::util::clamp; use crate::xml::Attributes; /// The `feDiffuseLighting` filter primitives. #[derive(Default)] pub struct FeDiffuseLighting { base: Primitive, params: DiffuseLightingParams, } #[derive(Clone)] pub struct DiffuseLightingParams { in1: Input, surface_scale: f64, kernel_unit_length: Option<(f64, f64)>, diffuse_constant: NonNegative, } impl Default for DiffuseLightingParams { fn default() -> Self { Self { in1: Default::default(), surface_scale: 1.0, kernel_unit_length: None, diffuse_constant: NonNegative(1.0), } } } /// The `feSpecularLighting` filter primitives. #[derive(Default)] pub struct FeSpecularLighting { base: Primitive, params: SpecularLightingParams, } #[derive(Clone)] pub struct SpecularLightingParams { in1: Input, surface_scale: f64, kernel_unit_length: Option<(f64, f64)>, specular_constant: NonNegative, specular_exponent: f64, } impl Default for SpecularLightingParams { fn default() -> Self { Self { in1: Default::default(), surface_scale: 1.0, kernel_unit_length: None, specular_constant: NonNegative(1.0), specular_exponent: 1.0, } } } /// Resolved `feDiffuseLighting` primitive for rendering. pub struct DiffuseLighting { params: DiffuseLightingParams, light: Light, } /// Resolved `feSpecularLighting` primitive for rendering. pub struct SpecularLighting { params: SpecularLightingParams, light: Light, } /// A light source before applying affine transformations, straight out of the SVG. #[derive(Debug, PartialEq)] enum UntransformedLightSource { Distant(FeDistantLight), Point(FePointLight), Spot(FeSpotLight), } /// A light source with affine transformations applied. enum LightSource { Distant { azimuth: f64, elevation: f64, }, Point { origin: Vector3, }, Spot { origin: Vector3, direction: Vector3, specular_exponent: f64, limiting_cone_angle: Option, }, } impl UntransformedLightSource { fn transform(&self, paffine: Transform) -> LightSource { match *self { UntransformedLightSource::Distant(ref l) => l.transform(), UntransformedLightSource::Point(ref l) => l.transform(paffine), UntransformedLightSource::Spot(ref l) => l.transform(paffine), } } } struct Light { source: UntransformedLightSource, lighting_color: cssparser::RGBA, color_interpolation_filters: ColorInterpolationFilters, } impl Light { /// Returns the color and unit (or null) vector from the image sample to the light. #[inline] pub fn color_and_vector( &self, source: &LightSource, x: f64, y: f64, z: f64, ) -> (cssparser::RGBA, Vector3) { let vector = match *source { LightSource::Distant { azimuth, elevation } => { let azimuth = azimuth.to_radians(); let elevation = elevation.to_radians(); Vector3::new( azimuth.cos() * elevation.cos(), azimuth.sin() * elevation.cos(), elevation.sin(), ) } LightSource::Point { origin } | LightSource::Spot { origin, .. } => { let mut v = origin - Vector3::new(x, y, z); let _ = v.try_normalize_mut(0.0); v } }; let color = match *source { LightSource::Spot { direction, specular_exponent, limiting_cone_angle, .. } => { let minus_l_dot_s = -vector.dot(&direction); match limiting_cone_angle { _ if minus_l_dot_s <= 0.0 => cssparser::RGBA::transparent(), Some(a) if minus_l_dot_s < a.to_radians().cos() => { cssparser::RGBA::transparent() } _ => { let factor = minus_l_dot_s.powf(specular_exponent); let compute = |x| (clamp(f64::from(x) * factor, 0.0, 255.0) + 0.5) as u8; cssparser::RGBA { red: compute(self.lighting_color.red), green: compute(self.lighting_color.green), blue: compute(self.lighting_color.blue), alpha: 255, } } } } _ => self.lighting_color, }; (color, vector) } } #[derive(Clone, Debug, Default, PartialEq)] pub struct FeDistantLight { azimuth: f64, elevation: f64, } impl FeDistantLight { fn transform(&self) -> LightSource { LightSource::Distant { azimuth: self.azimuth, elevation: self.elevation, } } } impl ElementTrait for FeDistantLight { fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "azimuth") => { set_attribute(&mut self.azimuth, attr.parse(value), session) } expanded_name!("", "elevation") => { set_attribute(&mut self.elevation, attr.parse(value), session) } _ => (), } } } } #[derive(Clone, Debug, Default, PartialEq)] pub struct FePointLight { x: f64, y: f64, z: f64, } impl FePointLight { fn transform(&self, paffine: Transform) -> LightSource { let (x, y) = paffine.transform_point(self.x, self.y); let z = transform_dist(paffine, self.z); LightSource::Point { origin: Vector3::new(x, y, z), } } } impl ElementTrait for FePointLight { fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session), expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session), expanded_name!("", "z") => set_attribute(&mut self.z, attr.parse(value), session), _ => (), } } } } #[derive(Clone, Debug, PartialEq)] pub struct FeSpotLight { x: f64, y: f64, z: f64, points_at_x: f64, points_at_y: f64, points_at_z: f64, specular_exponent: f64, limiting_cone_angle: Option, } // We need this because, per the spec, the initial values for all fields are 0.0 // except for specular_exponent, which is 1. impl Default for FeSpotLight { fn default() -> FeSpotLight { FeSpotLight { x: 0.0, y: 0.0, z: 0.0, points_at_x: 0.0, points_at_y: 0.0, points_at_z: 0.0, specular_exponent: 1.0, limiting_cone_angle: None, } } } impl FeSpotLight { fn transform(&self, paffine: Transform) -> LightSource { let (x, y) = paffine.transform_point(self.x, self.y); let z = transform_dist(paffine, self.z); let (points_at_x, points_at_y) = paffine.transform_point(self.points_at_x, self.points_at_y); let points_at_z = transform_dist(paffine, self.points_at_z); let origin = Vector3::new(x, y, z); let mut direction = Vector3::new(points_at_x, points_at_y, points_at_z) - origin; let _ = direction.try_normalize_mut(0.0); LightSource::Spot { origin, direction, specular_exponent: self.specular_exponent, limiting_cone_angle: self.limiting_cone_angle, } } } impl ElementTrait for FeSpotLight { fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session), expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session), expanded_name!("", "z") => set_attribute(&mut self.z, attr.parse(value), session), expanded_name!("", "pointsAtX") => { set_attribute(&mut self.points_at_x, attr.parse(value), session) } expanded_name!("", "pointsAtY") => { set_attribute(&mut self.points_at_y, attr.parse(value), session) } expanded_name!("", "pointsAtZ") => { set_attribute(&mut self.points_at_z, attr.parse(value), session) } expanded_name!("", "specularExponent") => { set_attribute(&mut self.specular_exponent, attr.parse(value), session); } expanded_name!("", "limitingConeAngle") => { set_attribute(&mut self.limiting_cone_angle, attr.parse(value), session); } _ => (), } } } } /// Applies the `primitiveUnits` coordinate transformation to a non-x or y distance. #[inline] fn transform_dist(t: Transform, d: f64) -> f64 { d * (t.xx.powi(2) + t.yy.powi(2)).sqrt() / std::f64::consts::SQRT_2 } impl ElementTrait for FeDiffuseLighting { fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { self.params.in1 = self.base.parse_one_input(attrs, session); for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "surfaceScale") => { set_attribute(&mut self.params.surface_scale, attr.parse(value), session); } expanded_name!("", "kernelUnitLength") => { let v: Result, _> = attr.parse(value); match v { Ok(NumberOptionalNumber(x, y)) => { self.params.kernel_unit_length = Some((x, y)); } Err(e) => { rsvg_log!(session, "ignoring attribute with invalid value: {}", e); } } } expanded_name!("", "diffuseConstant") => { set_attribute( &mut self.params.diffuse_constant, attr.parse(value), session, ); } _ => (), } } } } impl DiffuseLighting { #[inline] fn compute_factor(&self, normal: Normal, light_vector: Vector3) -> f64 { let k = if normal.normal.is_zero() { // Common case of (0, 0, 1) normal. light_vector.z } else { let mut n = normal .normal .map(|x| f64::from(x) * self.params.surface_scale / 255.); n.component_mul_assign(&normal.factor); let normal = Vector3::new(n.x, n.y, 1.0); normal.dot(&light_vector) / normal.norm() }; self.params.diffuse_constant.0 * k } } impl ElementTrait for FeSpecularLighting { fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { self.params.in1 = self.base.parse_one_input(attrs, session); for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "surfaceScale") => { set_attribute(&mut self.params.surface_scale, attr.parse(value), session); } expanded_name!("", "kernelUnitLength") => { let v: Result, _> = attr.parse(value); match v { Ok(NumberOptionalNumber(x, y)) => { self.params.kernel_unit_length = Some((x, y)); } Err(e) => { rsvg_log!(session, "ignoring attribute with invalid value: {}", e); } } } expanded_name!("", "specularConstant") => { set_attribute( &mut self.params.specular_constant, attr.parse(value), session, ); } expanded_name!("", "specularExponent") => { set_attribute( &mut self.params.specular_exponent, attr.parse(value), session, ); } _ => (), } } } } impl SpecularLighting { #[inline] fn compute_factor(&self, normal: Normal, light_vector: Vector3) -> f64 { let h = light_vector + Vector3::new(0.0, 0.0, 1.0); let h_norm = h.norm(); if h_norm == 0.0 { return 0.0; } let n_dot_h = if normal.normal.is_zero() { // Common case of (0, 0, 1) normal. h.z / h_norm } else { let mut n = normal .normal .map(|x| f64::from(x) * self.params.surface_scale / 255.); n.component_mul_assign(&normal.factor); let normal = Vector3::new(n.x, n.y, 1.0); normal.dot(&h) / normal.norm() / h_norm }; if approx_eq!(f64, self.params.specular_exponent, 1.0) { self.params.specular_constant.0 * n_dot_h } else { self.params.specular_constant.0 * n_dot_h.powf(self.params.specular_exponent) } } } macro_rules! impl_lighting_filter { ($lighting_type:ty, $params_name:ident, $alpha_func:ident) => { impl $params_name { pub fn render( &self, bounds_builder: BoundsBuilder, ctx: &FilterContext, acquired_nodes: &mut AcquiredNodes<'_>, draw_ctx: &mut DrawingCtx, ) -> Result { let input_1 = ctx.get_input( acquired_nodes, draw_ctx, &self.params.in1, self.light.color_interpolation_filters, )?; let mut bounds: IRect = bounds_builder .add_input(&input_1) .compute(ctx) .clipped .into(); let original_bounds = bounds; let scale = self .params .kernel_unit_length .and_then(|(x, y)| { if x <= 0.0 || y <= 0.0 { None } else { Some((x, y)) } }) .map(|(dx, dy)| ctx.paffine().transform_distance(dx, dy)); let mut input_surface = input_1.surface().clone(); if let Some((ox, oy)) = scale { // Scale the input surface to match kernel_unit_length. let (new_surface, new_bounds) = input_surface.scale(bounds, 1.0 / ox, 1.0 / oy)?; input_surface = new_surface; bounds = new_bounds; } let (bounds_w, bounds_h) = bounds.size(); // Check if the surface is too small for normal computation. This case is // unspecified; WebKit doesn't render anything in this case. if bounds_w < 2 || bounds_h < 2 { return Err(FilterError::LightingInputTooSmall); } let (ox, oy) = scale.unwrap_or((1.0, 1.0)); let source = self.light.source.transform(ctx.paffine()); let mut surface = ExclusiveImageSurface::new( input_surface.width(), input_surface.height(), SurfaceType::from(self.light.color_interpolation_filters), )?; { let output_stride = surface.stride() as usize; let mut output_data = surface.data(); let output_slice = &mut *output_data; let compute_output_pixel = |output_slice: &mut [u8], base_y, x, y, normal: Normal| { let pixel = input_surface.get_pixel(x, y); let scaled_x = f64::from(x) * ox; let scaled_y = f64::from(y) * oy; let z = f64::from(pixel.a) / 255.0 * self.params.surface_scale; let (color, vector) = self.light.color_and_vector(&source, scaled_x, scaled_y, z); // compute the factor just once for the three colors let factor = self.compute_factor(normal, vector); let compute = |x| (clamp(factor * f64::from(x), 0.0, 255.0) + 0.5) as u8; let r = compute(color.red); let g = compute(color.green); let b = compute(color.blue); let a = $alpha_func(r, g, b); let output_pixel = Pixel { r, g, b, a }; output_slice.set_pixel(output_stride, output_pixel, x, y - base_y); }; // Top left. compute_output_pixel( output_slice, 0, bounds.x0 as u32, bounds.y0 as u32, Normal::top_left(&input_surface, bounds), ); // Top right. compute_output_pixel( output_slice, 0, bounds.x1 as u32 - 1, bounds.y0 as u32, Normal::top_right(&input_surface, bounds), ); // Bottom left. compute_output_pixel( output_slice, 0, bounds.x0 as u32, bounds.y1 as u32 - 1, Normal::bottom_left(&input_surface, bounds), ); // Bottom right. compute_output_pixel( output_slice, 0, bounds.x1 as u32 - 1, bounds.y1 as u32 - 1, Normal::bottom_right(&input_surface, bounds), ); if bounds_w >= 3 { // Top row. for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 { compute_output_pixel( output_slice, 0, x, bounds.y0 as u32, Normal::top_row(&input_surface, bounds, x), ); } // Bottom row. for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 { compute_output_pixel( output_slice, 0, x, bounds.y1 as u32 - 1, Normal::bottom_row(&input_surface, bounds, x), ); } } if bounds_h >= 3 { // Left column. for y in bounds.y0 as u32 + 1..bounds.y1 as u32 - 1 { compute_output_pixel( output_slice, 0, bounds.x0 as u32, y, Normal::left_column(&input_surface, bounds, y), ); } // Right column. for y in bounds.y0 as u32 + 1..bounds.y1 as u32 - 1 { compute_output_pixel( output_slice, 0, bounds.x1 as u32 - 1, y, Normal::right_column(&input_surface, bounds, y), ); } } if bounds_w >= 3 && bounds_h >= 3 { // Interior pixels. let first_row = bounds.y0 as u32 + 1; let one_past_last_row = bounds.y1 as u32 - 1; let first_pixel = (first_row as usize) * output_stride; let one_past_last_pixel = (one_past_last_row as usize) * output_stride; output_slice[first_pixel..one_past_last_pixel] .par_chunks_mut(output_stride) .zip(first_row..one_past_last_row) .for_each(|(slice, y)| { for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 { compute_output_pixel( slice, y, x, y, Normal::interior(&input_surface, bounds, x, y), ); } }); } } let mut surface = surface.share()?; if let Some((ox, oy)) = scale { // Scale the output surface back. surface = surface.scale_to( ctx.source_graphic().width(), ctx.source_graphic().height(), original_bounds, ox, oy, )?; bounds = original_bounds; } Ok(FilterOutput { surface, bounds }) } } impl FilterEffect for $lighting_type { fn resolve( &self, _acquired_nodes: &mut AcquiredNodes<'_>, node: &Node, ) -> Result, FilterResolveError> { let mut sources = node.children().rev().filter(|c| { c.is_element() && matches!( *c.borrow_element_data(), ElementData::FeDistantLight(_) | ElementData::FePointLight(_) | ElementData::FeSpotLight(_) ) }); let source_node = sources.next(); if source_node.is_none() || sources.next().is_some() { return Err(FilterResolveError::InvalidLightSourceCount); } let source_node = source_node.unwrap(); let source = match &*source_node.borrow_element_data() { ElementData::FeDistantLight(l) => { UntransformedLightSource::Distant((**l).clone()) } ElementData::FePointLight(l) => UntransformedLightSource::Point((**l).clone()), ElementData::FeSpotLight(l) => UntransformedLightSource::Spot((**l).clone()), _ => unreachable!(), }; let cascaded = CascadedValues::new_from_node(node); let values = cascaded.get(); Ok(vec![ResolvedPrimitive { primitive: self.base.clone(), params: PrimitiveParams::$params_name($params_name { params: self.params.clone(), light: Light { source, lighting_color: resolve_color( &values.lighting_color().0, UnitInterval::clamp(1.0), values.color().0, ), color_interpolation_filters: values.color_interpolation_filters(), }, }), }]) } } }; } const fn diffuse_alpha(_r: u8, _g: u8, _b: u8) -> u8 { 255 } fn specular_alpha(r: u8, g: u8, b: u8) -> u8 { max(max(r, g), b) } impl_lighting_filter!(FeDiffuseLighting, DiffuseLighting, diffuse_alpha); impl_lighting_filter!(FeSpecularLighting, SpecularLighting, specular_alpha); /// 2D normal and factor stored separately. /// /// The normal needs to be multiplied by `surface_scale * factor / 255` and /// normalized with 1 as the z component. /// pub for the purpose of accessing this from benchmarks. #[derive(Debug, Clone, Copy)] pub struct Normal { pub factor: Vector2, pub normal: Vector2, } impl Normal { #[inline] fn new(factor_x: f64, nx: i16, factor_y: f64, ny: i16) -> Normal { // Negative nx and ny to account for the different coordinate system. Normal { factor: Vector2::new(factor_x, factor_y), normal: Vector2::new(-nx, -ny), } } /// Computes and returns the normal vector for the top left pixel for light filters. #[inline] pub fn top_left(surface: &SharedImageSurface, bounds: IRect) -> Normal { // Surface needs to be at least 2×2. assert!(bounds.width() >= 2); assert!(bounds.height() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let (x, y) = (bounds.x0 as u32, bounds.y0 as u32); let center = get(x, y); let right = get(x + 1, y); let bottom = get(x, y + 1); let bottom_right = get(x + 1, y + 1); Self::new( 2. / 3., -2 * center + 2 * right - bottom + bottom_right, 2. / 3., -2 * center - right + 2 * bottom + bottom_right, ) } /// Computes and returns the normal vector for the top row pixels for light filters. #[inline] pub fn top_row(surface: &SharedImageSurface, bounds: IRect, x: u32) -> Normal { assert!(x as i32 > bounds.x0); assert!((x as i32) + 1 < bounds.x1); assert!(bounds.height() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let y = bounds.y0 as u32; let left = get(x - 1, y); let center = get(x, y); let right = get(x + 1, y); let bottom_left = get(x - 1, y + 1); let bottom = get(x, y + 1); let bottom_right = get(x + 1, y + 1); Self::new( 1. / 3., -2 * left + 2 * right - bottom_left + bottom_right, 1. / 2., -left - 2 * center - right + bottom_left + 2 * bottom + bottom_right, ) } /// Computes and returns the normal vector for the top right pixel for light filters. #[inline] pub fn top_right(surface: &SharedImageSurface, bounds: IRect) -> Normal { // Surface needs to be at least 2×2. assert!(bounds.width() >= 2); assert!(bounds.height() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let (x, y) = (bounds.x1 as u32 - 1, bounds.y0 as u32); let left = get(x - 1, y); let center = get(x, y); let bottom_left = get(x - 1, y + 1); let bottom = get(x, y + 1); Self::new( 2. / 3., -2 * left + 2 * center - bottom_left + bottom, 2. / 3., -left - 2 * center + bottom_left + 2 * bottom, ) } /// Computes and returns the normal vector for the left column pixels for light filters. #[inline] pub fn left_column(surface: &SharedImageSurface, bounds: IRect, y: u32) -> Normal { assert!(y as i32 > bounds.y0); assert!((y as i32) + 1 < bounds.y1); assert!(bounds.width() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let x = bounds.x0 as u32; let top = get(x, y - 1); let top_right = get(x + 1, y - 1); let center = get(x, y); let right = get(x + 1, y); let bottom = get(x, y + 1); let bottom_right = get(x + 1, y + 1); Self::new( 1. / 2., -top + top_right - 2 * center + 2 * right - bottom + bottom_right, 1. / 3., -2 * top - top_right + 2 * bottom + bottom_right, ) } /// Computes and returns the normal vector for the interior pixels for light filters. #[inline] pub fn interior(surface: &SharedImageSurface, bounds: IRect, x: u32, y: u32) -> Normal { assert!(x as i32 > bounds.x0); assert!((x as i32) + 1 < bounds.x1); assert!(y as i32 > bounds.y0); assert!((y as i32) + 1 < bounds.y1); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let top_left = get(x - 1, y - 1); let top = get(x, y - 1); let top_right = get(x + 1, y - 1); let left = get(x - 1, y); let right = get(x + 1, y); let bottom_left = get(x - 1, y + 1); let bottom = get(x, y + 1); let bottom_right = get(x + 1, y + 1); Self::new( 1. / 4., -top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right, 1. / 4., -top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right, ) } /// Computes and returns the normal vector for the right column pixels for light filters. #[inline] pub fn right_column(surface: &SharedImageSurface, bounds: IRect, y: u32) -> Normal { assert!(y as i32 > bounds.y0); assert!((y as i32) + 1 < bounds.y1); assert!(bounds.width() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let x = bounds.x1 as u32 - 1; let top_left = get(x - 1, y - 1); let top = get(x, y - 1); let left = get(x - 1, y); let center = get(x, y); let bottom_left = get(x - 1, y + 1); let bottom = get(x, y + 1); Self::new( 1. / 2., -top_left + top - 2 * left + 2 * center - bottom_left + bottom, 1. / 3., -top_left - 2 * top + bottom_left + 2 * bottom, ) } /// Computes and returns the normal vector for the bottom left pixel for light filters. #[inline] pub fn bottom_left(surface: &SharedImageSurface, bounds: IRect) -> Normal { // Surface needs to be at least 2×2. assert!(bounds.width() >= 2); assert!(bounds.height() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let (x, y) = (bounds.x0 as u32, bounds.y1 as u32 - 1); let top = get(x, y - 1); let top_right = get(x + 1, y - 1); let center = get(x, y); let right = get(x + 1, y); Self::new( 2. / 3., -top + top_right - 2 * center + 2 * right, 2. / 3., -2 * top - top_right + 2 * center + right, ) } /// Computes and returns the normal vector for the bottom row pixels for light filters. #[inline] pub fn bottom_row(surface: &SharedImageSurface, bounds: IRect, x: u32) -> Normal { assert!(x as i32 > bounds.x0); assert!((x as i32) + 1 < bounds.x1); assert!(bounds.height() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let y = bounds.y1 as u32 - 1; let top_left = get(x - 1, y - 1); let top = get(x, y - 1); let top_right = get(x + 1, y - 1); let left = get(x - 1, y); let center = get(x, y); let right = get(x + 1, y); Self::new( 1. / 3., -top_left + top_right - 2 * left + 2 * right, 1. / 2., -top_left - 2 * top - top_right + left + 2 * center + right, ) } /// Computes and returns the normal vector for the bottom right pixel for light filters. #[inline] pub fn bottom_right(surface: &SharedImageSurface, bounds: IRect) -> Normal { // Surface needs to be at least 2×2. assert!(bounds.width() >= 2); assert!(bounds.height() >= 2); let get = |x, y| i16::from(surface.get_pixel(x, y).a); let (x, y) = (bounds.x1 as u32 - 1, bounds.y1 as u32 - 1); let top_left = get(x - 1, y - 1); let top = get(x, y - 1); let left = get(x - 1, y); let center = get(x, y); Self::new( 2. / 3., -top_left + top - 2 * left + 2 * center, 2. / 3., -top_left - 2 * top + left + 2 * center, ) } } #[cfg(test)] mod tests { use super::*; use crate::document::Document; #[test] fn extracts_light_source() { let document = Document::load_from_bytes( br#" "#, ); let mut acquired_nodes = AcquiredNodes::new(&document); let node = document.lookup_internal_node("diffuse_distant").unwrap(); let lighting = borrow_element_as!(node, FeDiffuseLighting); let resolved = lighting.resolve(&mut acquired_nodes, &node).unwrap(); let ResolvedPrimitive { params, .. } = resolved.first().unwrap(); let diffuse_lighting = match params { PrimitiveParams::DiffuseLighting(l) => l, _ => unreachable!(), }; assert_eq!( diffuse_lighting.light.source, UntransformedLightSource::Distant(FeDistantLight { azimuth: 0.0, elevation: 45.0, }) ); let node = document.lookup_internal_node("specular_point").unwrap(); let lighting = borrow_element_as!(node, FeSpecularLighting); let resolved = lighting.resolve(&mut acquired_nodes, &node).unwrap(); let ResolvedPrimitive { params, .. } = resolved.first().unwrap(); let specular_lighting = match params { PrimitiveParams::SpecularLighting(l) => l, _ => unreachable!(), }; assert_eq!( specular_lighting.light.source, UntransformedLightSource::Point(FePointLight { x: 1.0, y: 2.0, z: 3.0, }) ); let node = document.lookup_internal_node("diffuse_spot").unwrap(); let lighting = borrow_element_as!(node, FeDiffuseLighting); let resolved = lighting.resolve(&mut acquired_nodes, &node).unwrap(); let ResolvedPrimitive { params, .. } = resolved.first().unwrap(); let diffuse_lighting = match params { PrimitiveParams::DiffuseLighting(l) => l, _ => unreachable!(), }; assert_eq!( diffuse_lighting.light.source, UntransformedLightSource::Spot(FeSpotLight { x: 1.0, y: 2.0, z: 3.0, points_at_x: 4.0, points_at_y: 5.0, points_at_z: 6.0, specular_exponent: 7.0, limiting_cone_angle: Some(8.0), }) ); } }