use crate::StableHash;
use alpha2_model::FillRule;
use kurbo::{Affine, BezPath};
use std::sync::Arc;
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct IRect {
pub x0: i32,
pub y0: i32,
pub x1: i32,
pub y1: i32,
}
impl IRect {
#[must_use]
pub const fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self {
Self { x0, y0, x1, y1 }
}
#[must_use]
pub fn width(self) -> u32 {
u32::try_from(self.x1.saturating_sub(self.x0).max(0)).unwrap_or(0)
}
#[must_use]
pub fn height(self) -> u32 {
u32::try_from(self.y1.saturating_sub(self.y0).max(0)).unwrap_or(0)
}
#[must_use]
pub fn is_empty(self) -> bool {
self.x1 <= self.x0 || self.y1 <= self.y0
}
#[must_use]
pub fn intersect(self, other: Self) -> Self {
Self {
x0: self.x0.max(other.x0),
y0: self.y0.max(other.y0),
x1: self.x1.min(other.x1),
y1: self.y1.min(other.y1),
}
}
#[must_use]
pub fn union(self, other: Self) -> Self {
if self.is_empty() {
return other;
}
if other.is_empty() {
return self;
}
Self {
x0: self.x0.min(other.x0),
y0: self.y0.min(other.y0),
x1: self.x1.max(other.x1),
y1: self.y1.max(other.y1),
}
}
#[must_use]
pub fn covering(path: &BezPath, canvas: Self) -> Self {
let bounds = kurbo::Shape::bounding_box(path);
if !(bounds.x0.is_finite()
&& bounds.y0.is_finite()
&& bounds.x1.is_finite()
&& bounds.y1.is_finite())
{
return Self::default();
}
#[allow(clippy::cast_possible_truncation)]
let edge =
|value: f64, low: i32, high: i32| value.clamp(f64::from(low), f64::from(high)) as i32;
Self {
x0: edge((bounds.x0 - 1.0).floor(), canvas.x0, canvas.x1),
y0: edge((bounds.y0 - 1.0).floor(), canvas.y0, canvas.y1),
x1: edge((bounds.x1 + 1.0).ceil(), canvas.x0, canvas.x1),
y1: edge((bounds.y1 + 1.0).ceil(), canvas.y0, canvas.y1),
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum PaintKind {
Solid,
Linear,
Radial,
Conic,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SceneStop {
pub offset: f32,
pub color: [f32; 4],
}
#[derive(Clone, Debug, PartialEq)]
pub struct ScenePaint {
pub kind: PaintKind,
pub a: [f32; 2],
pub b: [f32; 2],
pub spread: u32,
pub stops: Vec<SceneStop>,
pub inverse: Affine,
pub space: [f32; 2],
}
#[derive(Clone, Debug, PartialEq)]
pub enum VectorOp {
PushLayer {
opacity: f32,
bounds: IRect,
},
PopLayer,
Fill {
path: Arc<BezPath>,
rule: FillRule,
paint: ScenePaint,
opacity: f32,
bounds: IRect,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct VectorScene {
pub ops: Vec<VectorOp>,
pub bounds: IRect,
pub width: u32,
pub height: u32,
pub key: u128,
}
impl VectorScene {
#[must_use]
pub fn new(ops: Vec<VectorOp>, width: u32, height: u32) -> Self {
let mut hash = StableHash::new();
hash.bytes(b"vector-scene");
hash.u32(width);
hash.u32(height);
let mut bounds = IRect::default();
for op in &ops {
match op {
VectorOp::PushLayer {
opacity,
bounds: layer,
} => {
hash.u32(1);
hash.f32(*opacity);
hash_rect(&mut hash, *layer);
}
VectorOp::PopLayer => hash.u32(2),
VectorOp::Fill {
path,
rule,
paint,
opacity,
bounds: covered,
} => {
hash.u32(3);
hash.path(path);
hash.u32(match rule {
FillRule::Nonzero => 0,
FillRule::Evenodd => 1,
});
hash_paint(&mut hash, paint);
hash.f32(*opacity);
hash_rect(&mut hash, *covered);
bounds = bounds.union(*covered);
}
}
}
Self {
ops,
bounds,
width,
height,
key: hash.finish(),
}
}
#[must_use]
pub fn fill_count(&self) -> usize {
self.ops
.iter()
.filter(|op| matches!(op, VectorOp::Fill { .. }))
.count()
}
}
fn hash_rect(hash: &mut StableHash, rect: IRect) {
for edge in [rect.x0, rect.y0, rect.x1, rect.y1] {
hash.u32(edge.cast_unsigned());
}
}
fn hash_paint(hash: &mut StableHash, paint: &ScenePaint) {
hash.u32(paint.kind as u32);
for value in paint.a.iter().chain(&paint.b) {
hash.f32(*value);
}
hash.u32(paint.spread);
hash.u64(paint.stops.len() as u64);
for stop in &paint.stops {
hash.f32(stop.offset);
for channel in stop.color {
hash.f32(channel);
}
}
for coefficient in paint.inverse.as_coeffs() {
hash.f64(coefficient);
}
hash.f32(paint.space[0]);
hash.f32(paint.space[1]);
}