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] = color.to_linear_rgba();
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] = color.to_linear_rgba();
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)]
341pub enum BlendMode {
342 #[default]
343 SrcOver,
344 Src,
345 DstOver,
346 SrcIn,
347 DstIn,
348 SrcOut,
349 DstOut,
350 SrcAtop,
351 DstAtop,
352 Xor,
353 Plus,
354 Multiply,
355 Screen,
356 Overlay,
357 Darken,
358 Lighten,
359 ColorDodge,
360 ColorBurn,
361 HardLight,
362 SoftLight,
363 Difference,
364 Exclusion,
365 Modulate,
368}
369impl BlendMode {
370 #[must_use]
371 pub const fn requires_destination_read(self) -> bool {
372 !matches!(
373 self,
374 Self::SrcOver
375 | Self::Src
376 | Self::DstOver
377 | Self::SrcIn
378 | Self::DstIn
379 | Self::SrcOut
380 | Self::DstOut
381 | Self::SrcAtop
382 | Self::DstAtop
383 | Self::Xor
384 | Self::Plus
385 )
386 }
387 #[must_use]
388 pub const fn code(self) -> u32 {
389 match self {
390 Self::SrcOver => 0,
391 Self::Src => 1,
392 Self::DstOver => 2,
393 Self::SrcIn => 3,
394 Self::DstIn => 4,
395 Self::SrcOut => 5,
396 Self::DstOut => 6,
397 Self::SrcAtop => 7,
398 Self::DstAtop => 8,
399 Self::Xor => 9,
400 Self::Plus => 10,
401 Self::Multiply => 11,
402 Self::Screen => 12,
403 Self::Overlay => 13,
404 Self::Darken => 14,
405 Self::Lighten => 15,
406 Self::ColorDodge => 16,
407 Self::ColorBurn => 17,
408 Self::HardLight => 18,
409 Self::SoftLight => 19,
410 Self::Difference => 20,
411 Self::Exclusion => 21,
412 Self::Modulate => 22,
413 }
414 }
415}
416
417#[must_use]
420pub fn blend_premultiplied(mode: BlendMode, source: [f32; 4], destination: [f32; 4]) -> [f32; 4] {
421 let src = sanitize_premultiplied(source);
422 let dst = sanitize_premultiplied(destination);
423 let as_ = src[3];
424 let ad = dst[3];
425 let ao = (as_ + ad - as_ * ad).clamp(0., 1.);
426 let result = match mode {
427 BlendMode::SrcOver => [
428 src[0] + dst[0] * (1. - as_),
429 src[1] + dst[1] * (1. - as_),
430 src[2] + dst[2] * (1. - as_),
431 ao,
432 ],
433 BlendMode::Src => src,
434 BlendMode::DstOver => [
435 dst[0] + src[0] * (1. - ad),
436 dst[1] + src[1] * (1. - ad),
437 dst[2] + src[2] * (1. - ad),
438 ao,
439 ],
440 BlendMode::SrcIn => [src[0] * ad, src[1] * ad, src[2] * ad, as_ * ad],
441 BlendMode::DstIn => [dst[0] * as_, dst[1] * as_, dst[2] * as_, ad * as_],
442 BlendMode::SrcOut => [
443 src[0] * (1. - ad),
444 src[1] * (1. - ad),
445 src[2] * (1. - ad),
446 as_ * (1. - ad),
447 ],
448 BlendMode::DstOut => [
449 dst[0] * (1. - as_),
450 dst[1] * (1. - as_),
451 dst[2] * (1. - as_),
452 ad * (1. - as_),
453 ],
454 BlendMode::SrcAtop => [
455 src[0] * ad + dst[0] * (1. - as_),
456 src[1] * ad + dst[1] * (1. - as_),
457 src[2] * ad + dst[2] * (1. - as_),
458 ad,
459 ],
460 BlendMode::DstAtop => [
461 dst[0] * as_ + src[0] * (1. - ad),
462 dst[1] * as_ + src[1] * (1. - ad),
463 dst[2] * as_ + src[2] * (1. - ad),
464 as_,
465 ],
466 BlendMode::Xor => [
467 src[0] * (1. - ad) + dst[0] * (1. - as_),
468 src[1] * (1. - ad) + dst[1] * (1. - as_),
469 src[2] * (1. - ad) + dst[2] * (1. - as_),
470 (as_ + ad - 2. * as_ * ad).clamp(0., 1.),
471 ],
472 BlendMode::Plus => [
473 (src[0] + dst[0]).min(1.),
474 (src[1] + dst[1]).min(1.),
475 (src[2] + dst[2]).min(1.),
476 (as_ + ad).min(1.),
477 ],
478 BlendMode::Modulate => [src[0] * dst[0], src[1] * dst[1], src[2] * dst[2], as_ * ad],
479 artistic => {
480 let cs = straight_rgb(src);
481 let cd = straight_rgb(dst);
482 let blended = artistic_blend_rgb(artistic, cs, cd);
483 [
484 (src[0] * (1. - ad) + dst[0] * (1. - as_) + as_ * ad * blended[0]).clamp(0., 1.),
485 (src[1] * (1. - ad) + dst[1] * (1. - as_) + as_ * ad * blended[1]).clamp(0., 1.),
486 (src[2] * (1. - ad) + dst[2] * (1. - as_) + as_ * ad * blended[2]).clamp(0., 1.),
487 ao,
488 ]
489 }
490 };
491 sanitize_premultiplied(result)
492}
493
494fn sanitize_premultiplied([r, g, b, a]: [f32; 4]) -> [f32; 4] {
495 let alpha = finite_unit(a);
496 let channel = |value: f32| finite_or_zero(value).clamp(0., alpha);
497 [channel(r), channel(g), channel(b), alpha]
498}
499fn straight_rgb(value: [f32; 4]) -> [f32; 3] {
500 if value[3] <= f32::EPSILON {
501 [0.; 3]
502 } else {
503 [
504 (value[0] / value[3]).clamp(0., 1.),
505 (value[1] / value[3]).clamp(0., 1.),
506 (value[2] / value[3]).clamp(0., 1.),
507 ]
508 }
509}
510fn artistic_blend_rgb(mode: BlendMode, source: [f32; 3], destination: [f32; 3]) -> [f32; 3] {
511 let mut result = [0.; 3];
512 for index in 0..3 {
513 let s = source[index];
514 let d = destination[index];
515 result[index] = match mode {
516 BlendMode::Multiply => s * d,
517 BlendMode::Screen => s + d - s * d,
518 BlendMode::Overlay => {
519 if d <= 0.5 {
520 2. * s * d
521 } else {
522 1. - 2. * (1. - s) * (1. - d)
523 }
524 }
525 BlendMode::Darken => s.min(d),
526 BlendMode::Lighten => s.max(d),
527 BlendMode::ColorDodge => {
528 if s >= 1. {
529 1.
530 } else {
531 (d / (1. - s)).min(1.)
532 }
533 }
534 BlendMode::ColorBurn => {
535 if s <= 0. {
536 0.
537 } else {
538 (1. - (1. - d) / s).max(0.)
539 }
540 }
541 BlendMode::HardLight => {
542 if s <= 0.5 {
543 2. * s * d
544 } else {
545 1. - 2. * (1. - s) * (1. - d)
546 }
547 }
548 BlendMode::SoftLight => {
549 if s <= 0.5 {
550 d - (1. - 2. * s) * d * (1. - d)
551 } else {
552 let g = if d <= 0.25 {
553 ((16. * d - 12.) * d + 4.) * d
554 } else {
555 d.sqrt()
556 };
557 d + (2. * s - 1.) * (g - d)
558 }
559 }
560 BlendMode::Difference => (d - s).abs(),
561 BlendMode::Exclusion => s + d - 2. * s * d,
562 BlendMode::Modulate => s * d,
563 _ => s,
564 };
565 }
566 result.map(|value| value.clamp(0., 1.))
567}
568
569#[derive(Clone, Copy, Debug, PartialEq)]
573pub enum Effect {
574 GaussianBlur(GaussianBlur),
575 ColorMatrix(ColorFilter),
576}
577#[derive(Clone, Debug, Default, PartialEq)]
578pub struct EffectChain {
579 effects: Vec<Effect>,
580}
581impl EffectChain {
582 #[must_use]
583 pub const fn new() -> Self {
584 Self {
585 effects: Vec::new(),
586 }
587 }
588 #[must_use]
589 pub fn from_effects(effects: Vec<Effect>) -> Self {
590 Self { effects }
591 }
592 #[must_use]
593 pub fn effects(&self) -> &[Effect] {
594 &self.effects
595 }
596 pub fn push(&mut self, effect: Effect) {
597 self.effects.push(effect);
598 }
599 #[must_use]
600 pub fn with_effect(mut self, effect: Effect) -> Self {
601 self.push(effect);
602 self
603 }
604 #[must_use]
605 pub fn color_filter(self, filter: ColorFilter) -> Self {
606 self.with_effect(Effect::ColorMatrix(filter))
607 }
608 #[must_use]
609 pub fn blur(self, sigma: f32) -> Self {
610 self.with_effect(Effect::GaussianBlur(GaussianBlur::uniform(sigma)))
611 }
612 #[must_use]
615 pub fn optimized(&self) -> Self {
616 let mut out = Self::new();
617 for effect in &self.effects {
618 if let (Some(Effect::ColorMatrix(previous)), Effect::ColorMatrix(next)) =
619 (out.effects.last_mut(), effect)
620 {
621 *previous = previous.then(*next);
622 } else {
623 out.effects.push(*effect);
624 }
625 }
626 out
627 }
628 #[must_use]
629 pub fn fusion_count(&self) -> usize {
630 self.effects
631 .len()
632 .saturating_sub(self.optimized().effects.len())
633 }
634}
635impl DropShadowEffect {
636 #[must_use]
637 pub fn new(offset: Offset, sigma: f32, color: Color) -> Self {
638 Self {
639 offset: Offset::finite_or_zero(offset),
640 sigma_x: normalize_sigma(sigma),
641 sigma_y: normalize_sigma(sigma),
642 color,
643 }
644 }
645 #[must_use]
646 pub fn asymmetric(offset: Offset, sigma_x: f32, sigma_y: f32, color: Color) -> Self {
647 Self {
648 offset: Offset::finite_or_zero(offset),
649 sigma_x: normalize_sigma(sigma_x),
650 sigma_y: normalize_sigma(sigma_y),
651 color,
652 }
653 }
654 #[must_use]
655 pub fn blur(self) -> GaussianBlur {
656 GaussianBlur::new(self.sigma_x, self.sigma_y)
657 }
658}
659
660#[must_use]
664pub fn normalize_sigma(sigma: f32) -> f32 {
665 finite_non_negative(sigma)
666}
667
668#[must_use]
670pub fn blur_margin(sigma: f32) -> f32 {
671 (3. * normalize_sigma(sigma)).max(0.)
672}
673
674#[must_use]
676pub fn blur_bounds(source: Rect, sigma_x: f32, sigma_y: f32) -> Rect {
677 let mx = blur_margin(sigma_x);
678 let my = blur_margin(sigma_y);
679 Rect::from_origin_size(
680 Offset::new(source.origin.x - mx, source.origin.y - my),
681 Size::new(source.size.width + 2. * mx, source.size.height + 2. * my),
682 )
683}
684
685#[must_use]
688pub fn drop_shadow_bounds(source: Rect, offset: Offset, sigma_x: f32, sigma_y: f32) -> Rect {
689 union_rect(
690 source,
691 blur_bounds(
692 Rect::from_origin_size(source.origin + Offset::finite_or_zero(offset), source.size),
693 sigma_x,
694 sigma_y,
695 ),
696 )
697}
698
699#[must_use]
703pub fn gaussian_kernel_weights(sigma_physical: f32) -> Vec<f32> {
704 let sigma = normalize_sigma(sigma_physical);
705 if sigma <= f32::EPSILON {
706 return vec![1.];
707 }
708 let radius = (3. * sigma).ceil().max(1.) as i32;
709 let denominator = 2. * sigma * sigma;
710 let mut weights = (-radius..=radius)
711 .map(|i| (-(i * i) as f32 / denominator).exp())
712 .collect::<Vec<_>>();
713 let sum: f32 = weights.iter().sum();
714 if sum.is_finite() && sum > 0. {
715 for weight in &mut weights {
716 *weight /= sum;
717 }
718 } else {
719 weights.fill(0.);
720 weights[radius as usize] = 1.;
721 }
722 weights
723}
724
725#[must_use]
728pub fn premultiplied_shadow_sample(color: Color, mask: f32) -> [f32; 4] {
729 let alpha = (f32::from(color.alpha) / 255.) * mask.clamp(0., 1.);
730 let [r, g, b, _] = color.to_linear_rgba();
731 [r * alpha, g * alpha, b * alpha, alpha]
732}