1use std::usize;
2
3use image::Rgba;
4
5pub type Color8 = Rgba<u8>;
14
15#[allow(dead_code)]
16pub(crate) fn merge(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
17 let [back_r, back_g, back_b, back_a] = backdrop.0;
18 let [src_r, src_g, src_b, src_a] = src.0;
19 let res_r;
20 let res_g;
21 let res_b;
22
23 if back_a == 0 {
24 res_r = src_r;
25 res_g = src_g;
26 res_b = src_b;
27 } else if src_a == 0 {
28 res_r = back_r;
29 res_g = back_g;
30 res_b = back_b;
31 } else {
32 res_r = blend8(back_r, src_r, opacity);
33 res_g = blend8(back_g, src_g, opacity);
34 res_b = blend8(back_b, src_b, opacity);
35 }
36 let res_a = blend8(back_a, src_a, opacity);
37 if res_a == 0 {
38 Rgba([0, 0, 0, 0])
39 } else {
40 Rgba([res_r, res_g, res_b, res_a])
41 }
42}
43
44pub(crate) fn normal(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
46 let (back_r, back_g, back_b, back_a) = as_rgba_i32(backdrop);
47 let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
48
49 if back_a == 0 {
50 let alpha = mul_un8(src_a, opacity as i32) as i32;
52 return from_rgba_i32(src_r, src_g, src_b, alpha);
53 } else if src_a == 0 {
54 return backdrop;
55 }
56
57 let src_a = mul_un8(src_a, opacity as i32) as i32;
58
59 let res_a = src_a + back_a - mul_un8(back_a, src_a) as i32;
60
61 let res_r = back_r + ((src_r - back_r) * src_a) / res_a;
62 let res_g = back_g + ((src_g - back_g) * src_a) / res_a;
63 let res_b = back_b + ((src_b - back_b) * src_a) / res_a;
64
65 from_rgba_i32(res_r, res_g, res_b, res_a)
66}
67
68fn blender<F>(backdrop: Color8, src: Color8, opacity: u8, f: F) -> Color8
85where
86 F: Fn(Color8, Color8, u8) -> Color8,
87{
88 if backdrop[3] != 0 {
89 let norm = normal(backdrop, src, opacity);
90 let blend = f(backdrop, src, opacity);
91 let back_alpha = backdrop[3];
92 let normal_to_blend_merge = merge(norm, blend, back_alpha);
93 let src_total_alpha = mul_un8(src[3] as i32, opacity as i32);
94 let composite_alpha = mul_un8(back_alpha as i32, src_total_alpha as i32);
95 merge(normal_to_blend_merge, blend, composite_alpha)
96 } else {
98 normal(backdrop, src, opacity)
99 }
100}
101
102fn blend_channel<F>(backdrop: Color8, src: Color8, opacity: u8, f: F) -> Color8
111where
112 F: Fn(i32, i32) -> u8,
113{
114 let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
115 let (src_r, src_g, src_b, _) = as_rgba_i32(src);
116 let r = f(back_r, src_r);
117 let g = f(back_g, src_g);
118 let b = f(back_b, src_b);
119 let src = Rgba([r, g, b, src[3]]);
120 normal(backdrop, src, opacity)
121}
122
123pub(crate) fn multiply(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
126 blender(backdrop, src, opacity, multiply_baseline)
127}
128
129fn multiply_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
130 blend_channel(backdrop, src, opacity, blend_multiply)
131}
132
133fn blend_multiply(a: i32, b: i32) -> u8 {
134 mul_un8(a, b)
135}
136
137pub(crate) fn screen(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
140 blender(backdrop, src, opacity, screen_baseline)
141}
142
143fn screen_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
144 blend_channel(backdrop, src, opacity, blend_screen)
145}
146
147fn blend_screen(a: i32, b: i32) -> u8 {
149 (a + b - mul_un8(a, b) as i32) as u8
150}
151
152pub(crate) fn overlay(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
155 blender(backdrop, src, opacity, overlay_baseline)
156}
157
158fn overlay_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
159 blend_channel(backdrop, src, opacity, blend_overlay)
160}
161
162fn blend_overlay(b: i32, s: i32) -> u8 {
168 blend_hard_light(s, b)
169}
170
171pub(crate) fn darken(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
174 blender(backdrop, src, opacity, darken_baseline)
175}
176
177fn darken_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
178 blend_channel(backdrop, src, opacity, blend_darken)
179}
180
181fn blend_darken(b: i32, s: i32) -> u8 {
182 b.min(s) as u8
183}
184
185pub(crate) fn lighten(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
188 blender(backdrop, src, opacity, lighten_baseline)
189}
190
191fn lighten_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
192 blend_channel(backdrop, src, opacity, blend_lighten)
193}
194
195fn blend_lighten(b: i32, s: i32) -> u8 {
196 b.max(s) as u8
197}
198
199pub(crate) fn color_dodge(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
202 blender(backdrop, src, opacity, color_dodge_baseline)
203}
204
205fn color_dodge_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
206 blend_channel(backdrop, src, opacity, blend_color_dodge)
207}
208
209fn blend_color_dodge(b: i32, s: i32) -> u8 {
210 if b == 0 {
211 return 0;
212 }
213 let s = 255 - s;
214 if b >= s {
215 255
216 } else {
217 div_un8(b, s)
219 }
220}
221
222pub(crate) fn color_burn(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
225 blender(backdrop, src, opacity, color_burn_baseline)
226}
227
228fn color_burn_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
229 blend_channel(backdrop, src, opacity, blend_color_burn)
230}
231
232fn blend_color_burn(b: i32, s: i32) -> u8 {
233 if b == 255 {
234 return 255;
235 }
236 let b = 255 - b;
237 if b >= s {
238 0
239 } else {
240 255 - div_un8(b, s)
242 }
243}
244
245pub(crate) fn hard_light(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
248 blender(backdrop, src, opacity, hard_light_baseline)
249}
250
251fn hard_light_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
252 blend_channel(backdrop, src, opacity, blend_hard_light)
253}
254
255fn blend_hard_light(b: i32, s: i32) -> u8 {
256 if s < 128 {
257 blend_multiply(b, s << 1)
258 } else {
259 blend_screen(b, (s << 1) - 255)
260 }
261}
262
263pub(crate) fn soft_light(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
266 blender(backdrop, src, opacity, soft_light_baseline)
267}
268
269fn soft_light_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
270 let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
271 let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
272 let r = blend_soft_light(back_r, src_r);
273 let g = blend_soft_light(back_g, src_g);
274 let b = blend_soft_light(back_b, src_b);
275
276 let src = from_rgba_i32(r, g, b, src_a);
277
278 normal(backdrop, src, opacity)
279}
280
281fn blend_soft_light(b: i32, s: i32) -> i32 {
282 let b: f64 = b as f64 / 255.0;
285 let s: f64 = s as f64 / 255.0;
286
287 let d = if b <= 0.25 {
288 ((16.0 * b - 12.0) * b + 4.0) * b
289 } else {
290 b.sqrt()
291 };
292
293 let r = if s <= 0.5 {
294 b - (1.0 - 2.0 * s) * b * (1.0 - b)
295 } else {
296 b + (2.0 * s - 1.0) * (d - b)
297 };
298
299 (r * 255.0 + 0.5) as u32 as i32
300}
301
302pub(crate) fn divide(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
305 blender(backdrop, src, opacity, divide_baseline)
306}
307
308fn divide_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
309 blend_channel(backdrop, src, opacity, blend_divide)
310}
311
312fn blend_divide(b: i32, s: i32) -> u8 {
313 if b == 0 {
314 0
315 } else if b >= s {
316 255
317 } else {
318 div_un8(b, s)
319 }
320}
321
322pub(crate) fn difference(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
325 blender(backdrop, src, opacity, difference_baseline)
326}
327
328fn difference_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
329 blend_channel(backdrop, src, opacity, blend_difference)
330}
331
332fn blend_difference(b: i32, s: i32) -> u8 {
333 (b - s).unsigned_abs() as u8
334}
335
336pub(crate) fn exclusion(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
339 blender(backdrop, src, opacity, exclusion_baseline)
340}
341
342fn exclusion_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
343 blend_channel(backdrop, src, opacity, blend_exclusion)
344}
345
346fn blend_exclusion(b: i32, s: i32) -> u8 {
348 let t = mul_un8(b, s) as i32;
349 (b + s - 2 * t) as u8
350}
351
352pub(crate) fn addition(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
355 blender(backdrop, src, opacity, addition_baseline)
356}
357
358fn addition_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
359 let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
360 let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
361 let r = back_r + src_r;
362 let g = back_g + src_g;
363 let b = back_b + src_b;
364
365 let src = from_rgba_i32(r.min(255), g.min(255), b.min(255), src_a);
366
367 normal(backdrop, src, opacity)
368}
369
370pub(crate) fn subtract(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
373 blender(backdrop, src, opacity, subtract_baseline)
374}
375
376fn subtract_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
377 let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
378 let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
379 let r = back_r - src_r;
380 let g = back_g - src_g;
381 let b = back_b - src_b;
382
383 let src = from_rgba_i32(r.max(0), g.max(0), b.max(0), src_a);
384
385 normal(backdrop, src, opacity)
386}
387
388pub(crate) fn hsl_hue(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
391 blender(backdrop, src, opacity, hsl_hue_baseline)
392}
393
394fn hsl_hue_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
395 let (r, g, b) = as_rgb_f64(backdrop);
396 let sat = saturation(r, g, b);
397 let lum = luminosity(r, g, b);
398
399 let (r, g, b) = as_rgb_f64(src);
400
401 let (r, g, b) = set_saturation(r, g, b, sat);
402 let (r, g, b) = set_luminocity(r, g, b, lum);
403
404 let src = from_rgb_f64(r, g, b, src[3]);
405
406 normal(backdrop, src, opacity)
407}
408
409pub(crate) fn hsl_saturation(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
412 blender(backdrop, src, opacity, hsl_saturation_baseline)
413}
414
415fn hsl_saturation_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
416 let (r, g, b) = as_rgb_f64(src);
418 let sat = saturation(r, g, b);
420 let (r, g, b) = as_rgb_f64(backdrop);
423 let lum = luminosity(r, g, b);
425 let (r, g, b) = set_saturation(r, g, b, sat);
428 let (r, g, b) = set_luminocity(r, g, b, lum);
430
431 let src = from_rgb_f64(r, g, b, src[3]);
434 normal(backdrop, src, opacity)
436}
437
438pub(crate) fn hsl_color(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
441 blender(backdrop, src, opacity, hsl_color_baseline)
442}
443
444fn hsl_color_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
445 let (r, g, b) = as_rgb_f64(backdrop);
446 let lum = luminosity(r, g, b);
447
448 let (r, g, b) = as_rgb_f64(src);
449
450 let (r, g, b) = set_luminocity(r, g, b, lum);
451
452 let src = from_rgb_f64(r, g, b, src[3]);
453 normal(backdrop, src, opacity)
454}
455
456pub(crate) fn hsl_luminosity(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
459 blender(backdrop, src, opacity, hsl_luminosity_baseline)
460}
461
462fn hsl_luminosity_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
463 let (r, g, b) = as_rgb_f64(src);
464 let lum = luminosity(r, g, b);
465
466 let (r, g, b) = as_rgb_f64(backdrop);
467
468 let (r, g, b) = set_luminocity(r, g, b, lum);
469
470 let src = from_rgb_f64(r, g, b, src[3]);
471
472 normal(backdrop, src, opacity)
473}
474
475fn saturation(r: f64, g: f64, b: f64) -> f64 {
482 r.max(g.max(b)) - r.min(g.min(b))
483}
484
485fn luminosity(r: f64, g: f64, b: f64) -> f64 {
486 0.3 * r + 0.59 * g + 0.11 * b
487}
488
489fn set_luminocity(r: f64, g: f64, b: f64, lum: f64) -> (f64, f64, f64) {
490 let delta = lum - luminosity(r, g, b);
491 clip_color(r + delta, g + delta, b + delta)
492}
493
494fn clip_color(mut r: f64, mut g: f64, mut b: f64) -> (f64, f64, f64) {
495 let lum = luminosity(r, g, b);
496 let min = r.min(g.min(b));
497 let max = r.max(g.max(b));
498
499 if min < 0.0 {
500 r = lum + (((r - lum) * lum) / (lum - min));
501 g = lum + (((g - lum) * lum) / (lum - min));
502 b = lum + (((b - lum) * lum) / (lum - min));
503 }
504
505 if max > 1.0 {
506 r = lum + (((r - lum) * (1.0 - lum)) / (max - lum));
507 g = lum + (((g - lum) * (1.0 - lum)) / (max - lum));
508 b = lum + (((b - lum) * (1.0 - lum)) / (max - lum));
509 }
510 (r, g, b)
511}
512
513fn static_sort3(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
525 let (min0, mid0, max0) = ((r, 0), (g, 1), (b, 2));
526 let (min1, mid1) = if min0.0 < mid0.0 {
529 (min0, mid0)
530 } else {
531 (mid0, min0)
532 };
533 let (min2, max1) = if min1.0 < max0.0 {
535 (min1, max0)
536 } else {
537 (max0, min1)
538 };
539 let (mid2, max2) = if mid1.0 < max1.0 {
541 (mid1, max1)
542 } else {
543 (max1, mid1)
544 };
545 (min2.1, mid2.1, max2.1)
547}
548
549#[cfg(test)]
551fn static_sort3_spec(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
552 let mut inp = [(r, 0), (g, 1), (b, 2)];
553 inp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
554 let res: Vec<usize> = inp.iter().map(|p| p.1).collect();
555 (res[0], res[1], res[2])
557}
558
559#[test]
560fn test_static_sort3() {
561 let (r, g, b) = (2.0, 3.0, 4.0);
562 assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
563 let (r, g, b) = (2.0, 4.0, 3.0);
564 assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
565 let (r, g, b) = (3.0, 2.0, 4.0);
566 assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
567 let (r, g, b) = (3.0, 4.0, 2.0);
568 assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
569 let (r, g, b) = (4.0, 2.0, 3.0);
570 assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
571 let (r, g, b) = (4.0, 3.0, 2.0);
572 assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
573}
574
575fn static_sort3_orig(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
578 let min = if r < g.min(b) {
598 0 } else if g < b {
600 1 } else {
602 2 };
604 let max = if r > g.max(b) {
605 0 } else if g > b {
607 1 } else {
609 2 };
611 let mid = if r > g {
612 if g > b {
613 1 } else if r > b {
615 2 } else {
617 0 }
619 } else if g > b {
620 if b > r {
621 2 } else {
623 0 }
625 } else {
626 1 };
628 (min, mid, max)
629}
630
631const ASEPRITE_SATURATION_BUG_COMPATIBLE: bool = true;
633
634fn set_saturation(r: f64, g: f64, b: f64, sat: f64) -> (f64, f64, f64) {
635 let mut col = [r, g, b];
636
637 let (min, mid, max) = if ASEPRITE_SATURATION_BUG_COMPATIBLE {
638 static_sort3_orig(r, g, b)
639 } else {
640 static_sort3(r, g, b)
641 };
642 if col[max] > col[min] {
643 col[mid] = ((col[mid] - col[min]) * sat) / (col[max] - col[min]);
645 col[max] = sat;
646 } else {
647 col[mid] = 0.0;
648 col[max] = 0.0;
649 }
650 col[min] = 0.0;
651 (col[0], col[1], col[2])
652}
653
654#[test]
656fn test_set_saturation() {
657 if ASEPRITE_SATURATION_BUG_COMPATIBLE {
658 return;
660 }
661 let steps = [0.0_f64, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
667 for r in steps.iter().cloned() {
668 for g in steps.iter().cloned() {
669 for b in steps.iter().cloned() {
670 for sat in steps.iter().cloned() {
671 let sat0 = saturation(r, g, b);
672 println!(
673 "* x = ({:.3}, {:.3}, {:.3}); x.sat() = {:.5}",
674 r, g, b, sat0
675 );
676 let (r1, g1, b1) = set_saturation(r, g, b, sat);
677 let sat1 = saturation(r1, g1, b1);
678 println!(
679 " y = x.set_sat({:.5}); y = ({:.3}, {:.3}, {:.3}), y.sat() = {:.5}",
680 sat, r1, g1, b1, sat1
681 );
682
683 if !(r == g && g == b) && (sat1 - sat).abs() > 0.00001 {
687 panic!(
688 "set_saturation({:.3}, {:.3}, {:.3}, {:.3}) => ({:.3}, {:.3}, {:.3}) => sat: {:.5} (input sat: {:.5})",
689 r, g, b, sat, r1, g1, b1, sat1, sat0
690 );
691 }
692 }
693 }
694 }
695 }
696}
697
698fn as_rgba_i32(color: Color8) -> (i32, i32, i32, i32) {
701 let [r, g, b, a] = color.0;
702 (r as i32, g as i32, b as i32, a as i32)
703}
704
705fn as_rgb_f64(color: Color8) -> (f64, f64, f64) {
706 let r = color[0] as f64 / 255.0;
707 let g = color[1] as f64 / 255.0;
708 let b = color[2] as f64 / 255.0;
709 (r, g, b)
710}
711
712fn from_rgba_i32(r: i32, g: i32, b: i32, a: i32) -> Color8 {
713 debug_assert!((0..=255).contains(&r));
714 debug_assert!((0..=255).contains(&g));
715 debug_assert!((0..=255).contains(&b));
716 debug_assert!((0..=255).contains(&a));
717
718 Rgba([r as u8, g as u8, b as u8, a as u8])
719}
720
721fn from_rgb_f64(r: f64, g: f64, b: f64, a: u8) -> Color8 {
722 from_rgba_i32(
723 (r * 255.0) as i32,
724 (g * 255.0) as i32,
725 (b * 255.0) as i32,
726 a as i32,
727 )
728}
729
730fn blend8(back: u8, src: u8, opacity: u8) -> u8 {
772 let src_x = src as i32;
773 let back_x = back as i32;
774 let a = src_x - back_x;
775 let b = opacity as i32;
776 let t = a * b + 0x80;
777 let r = ((t >> 8) + t) >> 8;
778 (back as i32 + r) as u8
779}
780
781#[test]
782fn test_blend8() {
783 assert_eq!(blend8(80, 50, 0), 80);
784 assert_eq!(blend8(80, 50, 128), 65);
785 assert_eq!(blend8(80, 50, 255), 50);
786 assert_eq!(blend8(80, 150, 128), 80 + (70 / 2));
787 assert_eq!(blend8(80, 150, 51), 80 + (70 / 5));
788 assert_eq!(blend8(80, 150, 36), 80 + (70 / 7));
789
790 }
792
793#[test]
794fn test_normal() {
795 let back = Rgba([0, 205, 249, 255]);
796 let front = Rgba([237, 118, 20, 255]);
797 let res = normal(back, front, 128);
798 assert_eq!(Rgba([118, 162, 135, 255]), res);
799}
800
801pub(crate) fn mul_un8(a: i32, b: i32) -> u8 {
802 let t = a * b + 0x80;
803 let r = ((t >> 8) + t) >> 8;
804 r as u8
805}
806
807fn div_un8(a: i32, b: i32) -> u8 {
809 let t = a * 0xff;
810 let r = (t + (b / 2)) / b;
811 r as u8
812}
813