1use crate::geometry::union_rect;
2use incular_core::finite_non_negative;
3use incular_core::{Color, Offset, Rect, Size, finite_or_zero};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct GaussianBlur {
11 pub sigma_x: f32,
12 pub sigma_y: f32,
13}
14impl GaussianBlur {
15 #[must_use]
16 pub fn new(sigma_x: f32, sigma_y: f32) -> Self {
17 Self {
18 sigma_x: normalize_sigma(sigma_x),
19 sigma_y: normalize_sigma(sigma_y),
20 }
21 }
22 #[must_use]
23 pub fn uniform(sigma: f32) -> Self {
24 Self::new(sigma, sigma)
25 }
26}
27
28#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct DropShadowEffect {
32 pub offset: Offset,
33 pub sigma_x: f32,
34 pub sigma_y: f32,
35 pub color: Color,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq)]
43pub struct ColorFilter {
44 matrix: [f32; 20],
45}
46pub type ColorMatrix = ColorFilter;
50impl ColorFilter {
51 #[must_use]
52 pub fn new(matrix: [f32; 20]) -> Self {
53 Self::matrix(matrix)
54 }
55 #[must_use]
56 pub fn matrix(matrix: [f32; 20]) -> Self {
57 Self {
58 matrix: matrix.map(finite_or_zero),
59 }
60 }
61 #[must_use]
62 pub fn identity() -> Self {
63 Self::matrix([
64 1., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0.,
65 ])
66 }
67 #[must_use]
68 pub fn to_matrix(self) -> [f32; 20] {
69 self.matrix
70 }
71 #[must_use]
72 pub fn is_identity(self) -> bool {
73 self == Self::identity()
74 }
75 #[must_use]
79 pub fn apply(self, rgba: [f32; 4]) -> [f32; 4] {
80 let alpha = finite_unit(rgba[3]);
81 let input = [
82 if alpha > f32::EPSILON {
83 finite_unit(rgba[0])
84 } else {
85 0.
86 },
87 if alpha > f32::EPSILON {
88 finite_unit(rgba[1])
89 } else {
90 0.
91 },
92 if alpha > f32::EPSILON {
93 finite_unit(rgba[2])
94 } else {
95 0.
96 },
97 alpha,
98 ];
99 let mut out = [0.; 4];
100 for (row, value) in out.iter_mut().enumerate() {
101 let base = row * 5;
102 *value = self.matrix[base] * input[0]
103 + self.matrix[base + 1] * input[1]
104 + self.matrix[base + 2] * input[2]
105 + self.matrix[base + 3] * input[3]
106 + self.matrix[base + 4];
107 }
108 let out = out.map(finite_unit);
109 if out[3] <= f32::EPSILON {
110 [0., 0., 0., 0.]
111 } else {
112 out
113 }
114 }
115 #[must_use]
118 pub fn then(self, next: Self) -> Self {
119 let a = self.matrix;
120 let b = next.matrix;
121 let mut out = [0.; 20];
122 for row in 0..4 {
123 for column in 0..4 {
124 out[row * 5 + column] = (0..4)
125 .map(|index| b[row * 5 + index] * a[index * 5 + column])
126 .sum();
127 }
128 out[row * 5 + 4] = b[row * 5 + 4]
129 + (0..4)
130 .map(|index| b[row * 5 + index] * a[index * 5 + 4])
131 .sum::<f32>();
132 }
133 Self::matrix(out)
134 }
135 #[must_use]
138 pub fn compose(self, next: Self) -> Self {
139 self.then(next)
140 }
141 #[must_use]
142 pub fn grayscale(amount: f32) -> Self {
143 let amount = finite_unit(amount);
144 let inv = 1. - amount;
145 let r = 0.2126 * amount;
146 let g = 0.7152 * amount;
147 let b = 0.0722 * amount;
148 Self::matrix([
149 r + inv,
150 g,
151 b,
152 0.,
153 0.,
154 r,
155 g + inv,
156 b,
157 0.,
158 0.,
159 r,
160 g,
161 b + inv,
162 0.,
163 0.,
164 0.,
165 0.,
166 0.,
167 1.,
168 0.,
169 ])
170 }
171 #[must_use]
172 pub fn sepia(amount: f32) -> Self {
173 let amount = finite_unit(amount);
174 let inv = 1. - amount;
175 Self::matrix([
176 0.393 * amount + inv,
177 0.769 * amount,
178 0.189 * amount,
179 0.,
180 0.,
181 0.349 * amount,
182 0.686 * amount + inv,
183 0.168 * amount,
184 0.,
185 0.,
186 0.272 * amount,
187 0.534 * amount,
188 0.131 * amount + inv,
189 0.,
190 0.,
191 0.,
192 0.,
193 0.,
194 1.,
195 0.,
196 ])
197 }
198 #[must_use]
200 pub fn brightness(amount: f32) -> Self {
201 let amount = finite_non_negative(amount);
202 Self::matrix([
203 amount, 0., 0., 0., 0., 0., amount, 0., 0., 0., 0., 0., amount, 0., 0., 0., 0., 0., 1.,
204 0.,
205 ])
206 }
207 #[must_use]
209 pub fn contrast(amount: f32) -> Self {
210 let amount = finite_non_negative(amount);
211 let bias = 0.5 * (1. - amount);
212 Self::matrix([
213 amount, 0., 0., 0., bias, 0., amount, 0., 0., bias, 0., 0., amount, 0., bias, 0., 0.,
214 0., 1., 0.,
215 ])
216 }
217 #[must_use]
219 pub fn saturate(amount: f32) -> Self {
220 let amount = finite_non_negative(amount);
221 let inv = 1. - amount;
222 let r = 0.2126 * inv;
223 let g = 0.7152 * inv;
224 let b = 0.0722 * inv;
225 Self::matrix([
226 r + amount,
227 g,
228 b,
229 0.,
230 0.,
231 r,
232 g + amount,
233 b,
234 0.,
235 0.,
236 r,
237 g,
238 b + amount,
239 0.,
240 0.,
241 0.,
242 0.,
243 0.,
244 1.,
245 0.,
246 ])
247 }
248 #[must_use]
250 pub fn invert(amount: f32) -> Self {
251 let amount = finite_unit(amount);
252 Self::matrix([
253 1. - 2. * amount,
254 0.,
255 0.,
256 0.,
257 amount,
258 0.,
259 1. - 2. * amount,
260 0.,
261 0.,
262 amount,
263 0.,
264 0.,
265 1. - 2. * amount,
266 0.,
267 amount,
268 0.,
269 0.,
270 0.,
271 1.,
272 0.,
273 ])
274 }
275 #[must_use]
279 pub fn modulate(color: Color) -> Self {
280 let [red, green, blue, alpha] = crate::working_rgba(color);
281 Self::matrix([
282 red, 0., 0., 0., 0., 0., green, 0., 0., 0., 0., 0., blue, 0., 0., 0., 0., 0., alpha, 0.,
283 ])
284 }
285 #[must_use]
294 pub fn tint(color: Color) -> Self {
295 let [red, green, blue, alpha] = crate::working_rgba(color);
296 Self::matrix([
297 0., 0., 0., 0., red, 0., 0., 0., 0., green, 0., 0., 0., 0., blue, 0., 0., 0., alpha, 0.,
298 ])
299 }
300 #[must_use]
303 pub fn opacity(amount: f32) -> Self {
304 Self::matrix([
305 1.,
306 0.,
307 0.,
308 0.,
309 0.,
310 0.,
311 1.,
312 0.,
313 0.,
314 0.,
315 0.,
316 0.,
317 1.,
318 0.,
319 0.,
320 0.,
321 0.,
322 0.,
323 finite_unit(amount),
324 0.,
325 ])
326 }
327}
328impl Default for ColorFilter {
329 fn default() -> Self {
330 Self::identity()
331 }
332}
333
334fn finite_unit(value: f32) -> f32 {
335 finite_or_zero(value).clamp(0., 1.)
336}
337
338#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
342pub enum BlendMode {
343 #[default]
344 SrcOver,
345 Src,
346 DstOver,
347 SrcIn,
348 DstIn,
349 SrcOut,
350 DstOut,
351 SrcAtop,
352 DstAtop,
353 Xor,
354 Plus,
355 Multiply,
356 Screen,
357 Overlay,
358 Darken,
359 Lighten,
360 ColorDodge,
361 ColorBurn,
362 HardLight,
363 SoftLight,
364 Difference,
365 Exclusion,
366 Modulate,
369}
370impl BlendMode {
371 #[must_use]
372 pub const fn requires_destination_read(self) -> bool {
373 !matches!(
374 self,
375 Self::SrcOver
376 | Self::Src
377 | Self::DstOver
378 | Self::SrcIn
379 | Self::DstIn
380 | Self::SrcOut
381 | Self::DstOut
382 | Self::SrcAtop
383 | Self::DstAtop
384 | Self::Xor
385 | Self::Plus
386 )
387 }
388 #[must_use]
389 pub const fn code(self) -> u32 {
390 match self {
391 Self::SrcOver => 0,
392 Self::Src => 1,
393 Self::DstOver => 2,
394 Self::SrcIn => 3,
395 Self::DstIn => 4,
396 Self::SrcOut => 5,
397 Self::DstOut => 6,
398 Self::SrcAtop => 7,
399 Self::DstAtop => 8,
400 Self::Xor => 9,
401 Self::Plus => 10,
402 Self::Multiply => 11,
403 Self::Screen => 12,
404 Self::Overlay => 13,
405 Self::Darken => 14,
406 Self::Lighten => 15,
407 Self::ColorDodge => 16,
408 Self::ColorBurn => 17,
409 Self::HardLight => 18,
410 Self::SoftLight => 19,
411 Self::Difference => 20,
412 Self::Exclusion => 21,
413 Self::Modulate => 22,
414 }
415 }
416}
417
418#[must_use]
421pub fn blend_premultiplied(mode: BlendMode, source: [f32; 4], destination: [f32; 4]) -> [f32; 4] {
422 let src = sanitize_premultiplied(source);
423 let dst = sanitize_premultiplied(destination);
424 let as_ = src[3];
425 let ad = dst[3];
426 let ao = (as_ + ad - as_ * ad).clamp(0., 1.);
427 let result = match mode {
428 BlendMode::SrcOver => [
429 src[0] + dst[0] * (1. - as_),
430 src[1] + dst[1] * (1. - as_),
431 src[2] + dst[2] * (1. - as_),
432 ao,
433 ],
434 BlendMode::Src => src,
435 BlendMode::DstOver => [
436 dst[0] + src[0] * (1. - ad),
437 dst[1] + src[1] * (1. - ad),
438 dst[2] + src[2] * (1. - ad),
439 ao,
440 ],
441 BlendMode::SrcIn => [src[0] * ad, src[1] * ad, src[2] * ad, as_ * ad],
442 BlendMode::DstIn => [dst[0] * as_, dst[1] * as_, dst[2] * as_, ad * as_],
443 BlendMode::SrcOut => [
444 src[0] * (1. - ad),
445 src[1] * (1. - ad),
446 src[2] * (1. - ad),
447 as_ * (1. - ad),
448 ],
449 BlendMode::DstOut => [
450 dst[0] * (1. - as_),
451 dst[1] * (1. - as_),
452 dst[2] * (1. - as_),
453 ad * (1. - as_),
454 ],
455 BlendMode::SrcAtop => [
456 src[0] * ad + dst[0] * (1. - as_),
457 src[1] * ad + dst[1] * (1. - as_),
458 src[2] * ad + dst[2] * (1. - as_),
459 ad,
460 ],
461 BlendMode::DstAtop => [
462 dst[0] * as_ + src[0] * (1. - ad),
463 dst[1] * as_ + src[1] * (1. - ad),
464 dst[2] * as_ + src[2] * (1. - ad),
465 as_,
466 ],
467 BlendMode::Xor => [
468 src[0] * (1. - ad) + dst[0] * (1. - as_),
469 src[1] * (1. - ad) + dst[1] * (1. - as_),
470 src[2] * (1. - ad) + dst[2] * (1. - as_),
471 (as_ + ad - 2. * as_ * ad).clamp(0., 1.),
472 ],
473 BlendMode::Plus => [
474 (src[0] + dst[0]).min(1.),
475 (src[1] + dst[1]).min(1.),
476 (src[2] + dst[2]).min(1.),
477 (as_ + ad).min(1.),
478 ],
479 BlendMode::Modulate => [src[0] * dst[0], src[1] * dst[1], src[2] * dst[2], as_ * ad],
480 artistic => {
481 let cs = straight_rgb(src);
482 let cd = straight_rgb(dst);
483 let blended = artistic_blend_rgb(artistic, cs, cd);
484 [
485 (src[0] * (1. - ad) + dst[0] * (1. - as_) + as_ * ad * blended[0]).clamp(0., 1.),
486 (src[1] * (1. - ad) + dst[1] * (1. - as_) + as_ * ad * blended[1]).clamp(0., 1.),
487 (src[2] * (1. - ad) + dst[2] * (1. - as_) + as_ * ad * blended[2]).clamp(0., 1.),
488 ao,
489 ]
490 }
491 };
492 sanitize_premultiplied(result)
493}
494
495fn sanitize_premultiplied([r, g, b, a]: [f32; 4]) -> [f32; 4] {
496 let alpha = finite_unit(a);
497 let channel = |value: f32| finite_or_zero(value).clamp(0., alpha);
498 [channel(r), channel(g), channel(b), alpha]
499}
500fn straight_rgb(value: [f32; 4]) -> [f32; 3] {
501 if value[3] <= f32::EPSILON {
502 [0.; 3]
503 } else {
504 [
505 (value[0] / value[3]).clamp(0., 1.),
506 (value[1] / value[3]).clamp(0., 1.),
507 (value[2] / value[3]).clamp(0., 1.),
508 ]
509 }
510}
511fn artistic_blend_rgb(mode: BlendMode, source: [f32; 3], destination: [f32; 3]) -> [f32; 3] {
512 let mut result = [0.; 3];
513 for index in 0..3 {
514 let s = source[index];
515 let d = destination[index];
516 result[index] = match mode {
517 BlendMode::Multiply => s * d,
518 BlendMode::Screen => s + d - s * d,
519 BlendMode::Overlay => {
520 if d <= 0.5 {
521 2. * s * d
522 } else {
523 1. - 2. * (1. - s) * (1. - d)
524 }
525 }
526 BlendMode::Darken => s.min(d),
527 BlendMode::Lighten => s.max(d),
528 BlendMode::ColorDodge => {
529 if s >= 1. {
530 1.
531 } else {
532 (d / (1. - s)).min(1.)
533 }
534 }
535 BlendMode::ColorBurn => {
536 if s <= 0. {
537 0.
538 } else {
539 (1. - (1. - d) / s).max(0.)
540 }
541 }
542 BlendMode::HardLight => {
543 if s <= 0.5 {
544 2. * s * d
545 } else {
546 1. - 2. * (1. - s) * (1. - d)
547 }
548 }
549 BlendMode::SoftLight => {
550 if s <= 0.5 {
551 d - (1. - 2. * s) * d * (1. - d)
552 } else {
553 let g = if d <= 0.25 {
554 ((16. * d - 12.) * d + 4.) * d
555 } else {
556 d.sqrt()
557 };
558 d + (2. * s - 1.) * (g - d)
559 }
560 }
561 BlendMode::Difference => (d - s).abs(),
562 BlendMode::Exclusion => s + d - 2. * s * d,
563 BlendMode::Modulate => s * d,
564 _ => s,
565 };
566 }
567 result.map(|value| value.clamp(0., 1.))
568}
569
570#[derive(Clone, Copy, Debug, PartialEq)]
574pub enum Effect {
575 GaussianBlur(GaussianBlur),
576 ColorMatrix(ColorFilter),
577}
578#[derive(Clone, Debug, Default, PartialEq)]
579pub struct EffectChain {
580 effects: Vec<Effect>,
581}
582impl EffectChain {
583 #[must_use]
584 pub const fn new() -> Self {
585 Self {
586 effects: Vec::new(),
587 }
588 }
589 #[must_use]
590 pub fn from_effects(effects: Vec<Effect>) -> Self {
591 Self { effects }
592 }
593 #[must_use]
594 pub fn effects(&self) -> &[Effect] {
595 &self.effects
596 }
597 pub fn push(&mut self, effect: Effect) {
598 self.effects.push(effect);
599 }
600 #[must_use]
601 pub fn with_effect(mut self, effect: Effect) -> Self {
602 self.push(effect);
603 self
604 }
605 #[must_use]
606 pub fn color_filter(self, filter: ColorFilter) -> Self {
607 self.with_effect(Effect::ColorMatrix(filter))
608 }
609 #[must_use]
610 pub fn blur(self, sigma: f32) -> Self {
611 self.with_effect(Effect::GaussianBlur(GaussianBlur::uniform(sigma)))
612 }
613 #[must_use]
616 pub fn optimized(&self) -> Self {
617 let mut out = Self::new();
618 for effect in &self.effects {
619 if let (Some(Effect::ColorMatrix(previous)), Effect::ColorMatrix(next)) =
620 (out.effects.last_mut(), effect)
621 {
622 *previous = previous.then(*next);
623 } else {
624 out.effects.push(*effect);
625 }
626 }
627 out
628 }
629 #[must_use]
630 pub fn fusion_count(&self) -> usize {
631 self.effects
632 .len()
633 .saturating_sub(self.optimized().effects.len())
634 }
635}
636impl DropShadowEffect {
637 #[must_use]
638 pub fn new(offset: Offset, sigma: f32, color: Color) -> Self {
639 Self {
640 offset: Offset::finite_or_zero(offset),
641 sigma_x: normalize_sigma(sigma),
642 sigma_y: normalize_sigma(sigma),
643 color,
644 }
645 }
646 #[must_use]
647 pub fn asymmetric(offset: Offset, sigma_x: f32, sigma_y: f32, color: Color) -> Self {
648 Self {
649 offset: Offset::finite_or_zero(offset),
650 sigma_x: normalize_sigma(sigma_x),
651 sigma_y: normalize_sigma(sigma_y),
652 color,
653 }
654 }
655 #[must_use]
656 pub fn blur(self) -> GaussianBlur {
657 GaussianBlur::new(self.sigma_x, self.sigma_y)
658 }
659}
660
661#[must_use]
665pub fn normalize_sigma(sigma: f32) -> f32 {
666 finite_non_negative(sigma)
667}
668
669#[must_use]
671pub fn blur_margin(sigma: f32) -> f32 {
672 (3. * normalize_sigma(sigma)).max(0.)
673}
674
675#[must_use]
677pub fn blur_bounds(source: Rect, sigma_x: f32, sigma_y: f32) -> Rect {
678 let mx = blur_margin(sigma_x);
679 let my = blur_margin(sigma_y);
680 Rect::from_origin_size(
681 Offset::new(source.origin.x - mx, source.origin.y - my),
682 Size::new(source.size.width + 2. * mx, source.size.height + 2. * my),
683 )
684}
685
686#[must_use]
689pub fn drop_shadow_bounds(source: Rect, offset: Offset, sigma_x: f32, sigma_y: f32) -> Rect {
690 union_rect(
691 source,
692 blur_bounds(
693 Rect::from_origin_size(source.origin + Offset::finite_or_zero(offset), source.size),
694 sigma_x,
695 sigma_y,
696 ),
697 )
698}
699
700#[must_use]
704pub fn gaussian_kernel_weights(sigma_physical: f32) -> Vec<f32> {
705 let sigma = normalize_sigma(sigma_physical);
706 if sigma <= f32::EPSILON {
707 return vec![1.];
708 }
709 let radius = (3. * sigma).ceil().max(1.) as i32;
710 let denominator = 2. * sigma * sigma;
711 let mut weights = (-radius..=radius)
712 .map(|i| (-(i * i) as f32 / denominator).exp())
713 .collect::<Vec<_>>();
714 let sum: f32 = weights.iter().sum();
715 if sum.is_finite() && sum > 0. {
716 for weight in &mut weights {
717 *weight /= sum;
718 }
719 } else {
720 weights.fill(0.);
721 weights[radius as usize] = 1.;
722 }
723 weights
724}
725
726#[must_use]
729pub fn premultiplied_shadow_sample(color: Color, mask: f32) -> [f32; 4] {
730 let alpha = (f32::from(color.alpha) / 255.) * mask.clamp(0., 1.);
731 let [r, g, b, _] = crate::working_rgba(color);
732 [r * alpha, g * alpha, b * alpha, alpha]
733}