1use super::super::{BrushStroke, Prepared, invalid};
4use super::bounds;
5use super::geometry::{outline, samples};
6use super::material::material;
7use crate::ImageAsset;
8use crate::vector::ink::paint::paint_sample;
9use motion_core::{
10 Canvas, Color, DrawContext, Drawable, FillRule, ImagePattern, Paint, Path2D, PathVerb, Point,
11 Rect, Transform, ValidationError,
12};
13use std::sync::{Arc, OnceLock};
14
15#[derive(Clone, Copy, Debug)]
17pub enum PigmentMix {
18 Linear,
20 KubelkaMunk,
22}
23
24impl PigmentMix {
25 pub fn mix(self, first: Color, second: Color, amount: f64) -> Result<Color, ValidationError> {
27 if !amount.is_finite() || !(0.0..=1.0).contains(&amount) {
28 return Err(invalid("pigment", "Require amount in 0..=1"));
29 }
30 if amount == 0.0 || first == second {
31 return Ok(first);
32 }
33 if amount == 1.0 {
34 return Ok(second);
35 }
36 let mut first = first.to_rgba_float();
37 let mut second = second.to_rgba_float();
38 for value in &mut first[..3] {
39 *value = motion_core::color::srgb_to_linear(*value);
40 }
41 for value in &mut second[..3] {
42 *value = motion_core::color::srgb_to_linear(*value);
43 }
44 let mut result = [0.0; 4];
45 let reflectance = |rgb: [f64; 4]| {
46 let [red, green, blue, _] = rgb.map(|value| value.clamp(0.0, 1.0));
47 let white = red.min(green).min(blue);
48 let red = red - white;
49 let green = green - white;
50 let blue = blue - white;
51 let (secondary, remaining, secondary_basis, primary_basis) =
52 if red <= green && red <= blue {
53 if green <= blue {
54 (
55 green,
56 blue - green,
57 [1.0, 1.0, 1.0, 0.85, 0.1, 0.02],
58 [0.9, 1.0, 0.6, 0.1, 0.01, 0.01],
59 )
60 } else {
61 (
62 blue,
63 green - blue,
64 [1.0, 1.0, 1.0, 0.85, 0.1, 0.02],
65 [0.01, 0.12, 0.9, 1.0, 0.16, 0.02],
66 )
67 }
68 } else if green <= red && green <= blue {
69 if red <= blue {
70 (
71 red,
72 blue - red,
73 [1.0, 0.7, 0.1, 0.1, 0.75, 1.0],
74 [0.9, 1.0, 0.6, 0.1, 0.01, 0.01],
75 )
76 } else {
77 (
78 blue,
79 red - blue,
80 [1.0, 0.7, 0.1, 0.1, 0.75, 1.0],
81 [0.02, 0.01, 0.02, 0.25, 0.8, 1.0],
82 )
83 }
84 } else if red <= green {
85 (
86 red,
87 green - red,
88 [0.03, 0.15, 0.95, 1.0, 1.0, 0.95],
89 [0.01, 0.12, 0.9, 1.0, 0.16, 0.02],
90 )
91 } else {
92 (
93 green,
94 red - green,
95 [0.03, 0.15, 0.95, 1.0, 1.0, 0.95],
96 [0.02, 0.01, 0.02, 0.25, 0.8, 1.0],
97 )
98 };
99 std::array::from_fn::<_, 6, _>(|index| {
100 (white + secondary * secondary_basis[index] + remaining * primary_basis[index])
101 .clamp(1e-6, 1.0)
102 })
103 };
104 let spectra = if matches!(self, Self::KubelkaMunk) {
105 let first = reflectance(first);
106 let second = reflectance(second);
107 Some(std::array::from_fn::<_, 6, _>(|index| {
108 let ratio = |value: f64| (1.0 - value).powi(2) / (2.0 * value);
109 let absorption =
110 ratio(first[index]) * (1.0 - amount) + ratio(second[index]) * amount;
111 1.0 / (1.0 + absorption + libm::sqrt(absorption * absorption + 2.0 * absorption))
112 }))
113 } else {
114 None
115 };
116 for channel in 0..3 {
117 result[channel] = match self {
118 Self::Linear => first[channel] * (1.0 - amount) + second[channel] * amount,
119 Self::KubelkaMunk => {
120 let spectrum = spectra.as_ref().expect("Kubelka-Munk spectra");
121 match channel {
122 0 => spectrum[3] * 0.15 + spectrum[4] * 0.35 + spectrum[5] * 0.5,
123 1 => {
124 spectrum[1] * 0.1
125 + spectrum[2] * 0.4
126 + spectrum[3] * 0.4
127 + spectrum[4] * 0.1
128 }
129 _ => spectrum[0] * 0.5 + spectrum[1] * 0.4 + spectrum[2] * 0.1,
130 }
131 }
132 };
133 }
134 result[3] = first[3] * (1.0 - amount) + second[3] * amount;
135 let rgb = [result[0], result[1], result[2]].map(motion_core::color::linear_to_srgb);
136 Ok(Color::from_rgb_float(rgb[0], rgb[1], rgb[2]).with_alpha(result[3]))
137 }
138
139 pub fn painting(self, strokes: Vec<BrushStroke>) -> PigmentPainting {
141 PigmentPainting {
142 strokes,
143 mix: self,
144 prepared: Arc::new(OnceLock::new()),
145 }
146 }
147}
148
149#[derive(Clone, Debug)]
151pub struct PigmentPainting {
152 strokes: Vec<BrushStroke>,
153 mix: PigmentMix,
154 prepared: Arc<OnceLock<Result<Prepared, ValidationError>>>,
155}
156
157impl Drawable for PigmentPainting {
158 fn bounds(&self, _: &DrawContext) -> Result<Rect, ValidationError> {
159 if !(1..=32).contains(&self.strokes.len()) {
160 return Err(invalid("pigment_strokes", "Require 1..=32 pigment strokes"));
161 }
162 let mut result = bounds(&self.strokes[0])?;
163 for stroke in &self.strokes[1..] {
164 result = result.union(bounds(stroke)?);
165 }
166 Ok(result)
167 }
168
169 fn draw(&self, context: &DrawContext, canvas: &mut Canvas<'_>) -> Result<(), ValidationError> {
170 let bounds = self.bounds(context)?;
171 let prepared = self
172 .prepared
173 .get_or_init(|| {
174 let width = bounds.width().ceil().max(1.0) as u32;
175 let height = bounds.height().ceil().max(1.0) as u32;
176 if width > 2048 || height > 2048 || u64::from(width) * u64::from(height) > 262144 {
177 return Err(invalid(
178 "pigment_pixels",
179 "Pigment composition exceeds 262144 texels",
180 ));
181 }
182 let mut layers = Vec::new();
183 let mut work = 0_u64;
184 for stroke in &self.strokes {
185 let path = outline(&stroke.config, &samples(&stroke.config, stroke.progress)?)?;
186 work = work.saturating_add(
187 path.verbs().len() as u64 * u64::from(width) * u64::from(height),
188 );
189 if work > 32_000_000 {
190 return Err(invalid(
191 "pigment_work",
192 "Pigment coverage exceeds evaluation allowance",
193 ));
194 }
195 layers.push((path, material(&stroke.config, self::bounds(stroke)?)?));
196 }
197 let mut pixels = Vec::with_capacity(width as usize * height as usize);
198 for row in 0..height {
199 for column in 0..width {
200 let point = Point::new(
201 bounds.left + f64::from(column) + 0.5,
202 bounds.top + f64::from(row) + 0.5,
203 );
204 let mut color = Color::TRANSPARENT;
205 for (path, tiles) in &layers {
206 if !inside_outline(path, point) {
207 continue;
208 }
209 let mut source = [0.0; 4];
210 for tile in tiles {
211 if let Paint::Pattern(pattern) = &tile.paint {
212 let sample = paint_sample(&tile.paint, point, tile.bounds)?;
213 if sample[3] > 0.0 {
214 source = sample;
215 break;
216 }
217 let _ = pattern;
218 }
219 }
220 if source[3] <= 0.0 {
221 continue;
222 }
223 let source = Color::from_rgb_float(source[0], source[1], source[2])
224 .with_alpha(source[3]);
225 let previous = color.to_rgba_float();
226 let incoming = source.to_rgba_float();
227 let alpha = incoming[3] + previous[3] * (1.0 - incoming[3]);
228 let mixed = self.mix.mix(color, source, 0.5)?.to_rgba_float();
229 let overlap = previous[3] * incoming[3];
230 let mut output = [0.0; 3];
231 for index in 0..3 {
232 output[index] =
233 (previous[index] * previous[3] * (1.0 - incoming[3])
234 + incoming[index] * incoming[3] * (1.0 - previous[3])
235 + mixed[index] * overlap)
236 / alpha;
237 }
238 color = Color::from_rgb_float(output[0], output[1], output[2])
239 .with_alpha(alpha);
240 }
241 pixels.push(color.to_rgba_float());
242 }
243 }
244 let image = ImageAsset::from_rgba_float(width, height, pixels)
245 .map_err(|error| invalid("pigment_image", &error.to_string()))?;
246 let paint = ImagePattern::from_asset(Arc::new(image))
247 .transform(Transform::translate(bounds.left, bounds.top))
248 .into();
249 let mut path = Path2D::new();
250 path.try_move_to(Point::new(bounds.left, bounds.top))?;
251 path.try_line_to(Point::new(bounds.right, bounds.top))?;
252 path.try_line_to(Point::new(bounds.right, bounds.bottom))?;
253 path.try_line_to(Point::new(bounds.left, bounds.bottom))?;
254 path.try_close()?;
255 Ok(Prepared {
256 path: Arc::new(path),
257 paint,
258 bounds,
259 })
260 })
261 .as_ref()
262 .map_err(Clone::clone)?;
263 canvas.fill_path(
264 prepared.path.clone(),
265 Transform::IDENTITY,
266 prepared.paint.clone(),
267 FillRule::NonZero,
268 )
269 }
270}
271
272fn inside_outline(path: &Path2D, point: Point) -> bool {
273 let mut winding = 0_i32;
274 let mut first = Point::new(0.0, 0.0);
275 let mut previous = first;
276 for verb in path.verbs() {
277 let end = match verb {
278 PathVerb::MoveTo(position) => {
279 first = *position;
280 previous = first;
281 continue;
282 }
283 PathVerb::LineTo(position) => *position,
284 PathVerb::Close => first,
285 _ => continue,
286 };
287 let cross = (end.x - previous.x) * (point.y - previous.y)
288 - (point.x - previous.x) * (end.y - previous.y);
289 if previous.y <= point.y && end.y > point.y && cross > 0.0 {
290 winding += 1;
291 } else if previous.y > point.y && end.y <= point.y && cross < 0.0 {
292 winding -= 1;
293 }
294 previous = end;
295 }
296 winding != 0
297}