motion_content/vector/ink/
paper.rs1use crate::ImageAsset;
4use motion_core::{GradientExtend, ImagePattern, Point, Transform, ValidationError};
5use std::sync::Arc;
6
7#[derive(Clone, Debug)]
12pub enum Paper {
13 Procedural {
15 seed: u32,
17 strength: f32,
19 scale: f32,
21 },
22 Texture {
24 texture: Arc<ImageAsset>,
26 grain_scale: f64,
28 absorbency: f64,
30 },
31}
32
33impl Paper {
34 pub(super) fn is_valid(&self) -> bool {
35 match self {
36 Self::Procedural {
37 strength, scale, ..
38 } => {
39 strength.is_finite()
40 && (0.0..=1.0).contains(strength)
41 && scale.is_finite()
42 && *scale > 0.0
43 }
44 Self::Texture {
45 grain_scale,
46 absorbency,
47 ..
48 } => {
49 grain_scale.is_finite()
50 && *grain_scale > 0.0
51 && absorbency.is_finite()
52 && (0.0..=1.0).contains(absorbency)
53 }
54 }
55 }
56}
57
58pub(super) struct PaperSampler {
60 pattern: ImagePattern,
61 absorbency: f64,
62}
63
64impl PaperSampler {
65 pub(super) fn new(texture: Arc<ImageAsset>, grain_scale: f64, absorbency: f64) -> Self {
66 Self {
67 pattern: ImagePattern::from_asset(texture)
68 .transform(Transform::scale(grain_scale))
69 .extend(GradientExtend::Repeat),
70 absorbency,
71 }
72 }
73
74 pub(super) fn factor(&self, point: Point) -> Result<f64, ValidationError> {
76 let sample = image_sample(&self.pattern, point)?;
77 let height = if sample[3] > 0.0 {
78 (sample[0] * 0.2126 + sample[1] * 0.7152 + sample[2] * 0.0722) / sample[3]
79 } else {
80 0.0
81 };
82 Ok(1.0 - self.absorbency * height.clamp(0.0, 1.0))
83 }
84}
85
86pub(super) fn image_sample(
88 pattern: &ImagePattern,
89 point: Point,
90) -> Result<[f64; 4], ValidationError> {
91 let inverse = pattern.transform.inverse().ok_or_else(|| {
92 ValidationError::invalid("brush_stroke", "paint", "Noninvertible paint transform")
93 })?;
94 let point = inverse.apply(point);
95 let width = pattern.image.width() as usize;
96 let height = pattern.image.height() as usize;
97 if pattern.decal
98 && (point.x < 0.0 || point.y < 0.0 || point.x >= width as f64 || point.y >= height as f64)
99 {
100 return Ok([0.0; 4]);
101 }
102 let horizontal = point.x - 0.5;
103 let vertical = point.y - 0.5;
104 let column = horizontal.floor();
105 let row = vertical.floor();
106 let fraction = [horizontal - column, vertical - row];
107 let address = |index: i64, length: usize| match pattern.extend {
108 GradientExtend::Pad => index.clamp(0, length as i64 - 1) as usize,
109 GradientExtend::Repeat => index.rem_euclid(length as i64) as usize,
110 GradientExtend::Reflect => {
111 let index = index.rem_euclid(length as i64 * 2) as usize;
112 if index >= length {
113 length * 2 - index - 1
114 } else {
115 index
116 }
117 }
118 };
119 let mut result = [0.0; 4];
120 for vertical in 0..2 {
121 for horizontal in 0..2 {
122 let weight = (if horizontal == 0 {
123 1.0 - fraction[0]
124 } else {
125 fraction[0]
126 }) * (if vertical == 0 {
127 1.0 - fraction[1]
128 } else {
129 fraction[1]
130 });
131 let texel = pattern.image.texel(
132 0,
133 address(column as i64 + horizontal, width),
134 address(row as i64 + vertical, height),
135 );
136 for channel in 0..4 {
137 result[channel] += f64::from(texel[channel]) * weight;
138 }
139 }
140 }
141 Ok(result)
142}