use crate::filter::drop_shadow::{DropShadow, transform_shadow_params};
use crate::filter::flood::Flood;
use crate::filter::gaussian_blur::{GaussianBlur, transform_blur_params};
use crate::filter::offset::Offset;
use crate::filter_effects::{Filter, FilterPrimitive};
use crate::geometry::{PaddingU16, RectU16};
use crate::kurbo::{Affine, Rect, Vec2};
use crate::math::snap_up;
use crate::tile::Tile;
use crate::util::RectExt;
pub mod drop_shadow;
pub mod flood;
pub mod gaussian_blur;
pub mod offset;
#[derive(Debug)]
pub enum PreparedFilter {
Flood(Flood),
GaussianBlur(GaussianBlur),
Offset(Offset),
DropShadow(DropShadow),
}
impl PreparedFilter {
pub fn new(filter: &Filter, transform: &Affine) -> Self {
if filter.graph.primitives.len() != 1 {
unimplemented!("Multi-primitive filter graphs are not yet supported");
}
match &filter.graph.primitives[0] {
FilterPrimitive::Flood { color } => {
let flood = Flood::new(*color);
Self::Flood(flood)
}
FilterPrimitive::GaussianBlur {
std_deviation,
edge_mode,
} => {
let scaled_std_dev = transform_blur_params(*std_deviation, transform);
let blur = GaussianBlur::new(scaled_std_dev, *edge_mode);
Self::GaussianBlur(blur)
}
FilterPrimitive::DropShadow {
dx,
dy,
std_deviation,
color,
edge_mode,
} => {
let (scaled_dx, scaled_dy, scaled_std_dev) =
transform_shadow_params(*dx, *dy, *std_deviation, transform);
let drop_shadow =
DropShadow::new(scaled_dx, scaled_dy, scaled_std_dev, *edge_mode, *color);
Self::DropShadow(drop_shadow)
}
FilterPrimitive::DropShadowOnly {
dx,
dy,
std_deviation,
color,
edge_mode,
} => {
let (scaled_dx, scaled_dy, scaled_std_dev) =
transform_shadow_params(*dx, *dy, *std_deviation, transform);
let drop_shadow = DropShadow::new_shadow_only(
scaled_dx,
scaled_dy,
scaled_std_dev,
*edge_mode,
*color,
);
Self::DropShadow(drop_shadow)
}
FilterPrimitive::Offset { dx, dy } => {
let (scaled_dx, scaled_dy) = transform_offset_params(*dx, *dy, transform);
let offset = Offset::new(scaled_dx, scaled_dy);
Self::Offset(offset)
}
_ => {
unimplemented!("Other filter primitives not yet implemented");
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct FilterLayerPlacement {
pub pixmap_bbox: RectU16,
pub dest_bbox: RectU16,
pub src_x: u16,
pub src_y: u16,
}
impl FilterLayerPlacement {
pub(crate) const EMPTY: Self = Self {
pixmap_bbox: RectU16::ZERO,
dest_bbox: RectU16::ZERO,
src_x: 0,
src_y: 0,
};
pub(crate) fn new(bbox: RectU16, filter_plan: &FilterData) -> Self {
if bbox.is_empty() {
return Self::EMPTY;
}
let pixmap_bbox = bbox
.expand(filter_plan.filter_padding)
.snap_to_tile_coordinates();
let (shift_x, shift_y) = filter_plan.source_shift();
let src_x = shift_x.saturating_sub(pixmap_bbox.x0);
let src_y = shift_y.saturating_sub(pixmap_bbox.y0);
let dest_bbox = pixmap_bbox.relative_to_origin((shift_x, shift_y));
Self {
pixmap_bbox,
dest_bbox,
src_x,
src_y,
}
}
pub fn src_origin(self) -> (u16, u16) {
(self.src_x, self.src_y)
}
}
#[derive(Debug, Clone)]
pub struct FilterData {
pub filter: Filter,
pub transform: Affine,
pub filter_padding: PaddingU16,
pub source_padding: PaddingU16,
}
impl FilterData {
pub fn new(filter: Filter, transform: Affine) -> Self {
fn snapped_padding(expansion: Rect) -> PaddingU16 {
debug_assert!(
expansion.x0 <= 0.0
&& expansion.y0 <= 0.0
&& expansion.x1 >= 0.0
&& expansion.y1 >= 0.0,
"filter expansion must contain the origin"
);
PaddingU16::new(
snap_up(-expansion.x0, Tile::WIDTH) as u16,
snap_up(-expansion.y0, Tile::HEIGHT) as u16,
snap_up(expansion.x1, Tile::WIDTH) as u16,
snap_up(expansion.y1, Tile::HEIGHT) as u16,
)
}
let source_padding = snapped_padding(filter.source_expansion(&transform));
let filter_padding = snapped_padding(filter.filter_expansion(&transform));
Self {
filter,
transform,
filter_padding,
source_padding,
}
}
pub fn source_shift(&self) -> (u16, u16) {
(self.source_padding.left, self.source_padding.top)
}
}
fn transform_offset_params(dx: f32, dy: f32, transform: &Affine) -> (f32, f32) {
let offset = Vec2::new(dx as f64, dy as f64);
let [a, b, c, d, _, _] = transform.as_coeffs();
let transformed_offset = Vec2::new(a * offset.x + c * offset.y, b * offset.x + d * offset.y);
(transformed_offset.x as f32, transformed_offset.y as f32)
}