asefile/
blend.rs

1use std::usize;
2
3use image::Rgba;
4
5// Rust port of Aseprite's blend functions:
6// https://github.com/aseprite/aseprite/blob/master/src/doc/blend_funcs.cpp
7//
8// Further references:
9//  - http://www.simplefilter.de/en/basics/mixmods.html
10//  - PDF Blend Modes addendum: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdf_reference_archive/blend_modes.pdf
11//  - Pixman source: https://github.com/servo/pixman/blob/master/pixman/pixman-combine-float.c
12
13pub 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
44// based on: rgba_blender_normal(color_t backdrop, color_t src, int opacity)
45pub(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        //todo!("NYI: invisible background")
51        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
68// --- Utilities / generic functions -------------------------------------------
69
70/*
71  if (backdrop & rgba_a_mask) {                                                 \
72    color_t normal = rgba_blender_normal(backdrop, src, opacity);               \
73    color_t blend = rgba_blender_##name(backdrop, src, opacity);                \
74    int Ba = rgba_geta(backdrop);                                               \
75    color_t normalToBlendMerge = rgba_blender_merge(normal, blend, Ba);         \
76    int t;                                                                      \
77    int srcTotalAlpha = MUL_UN8(rgba_geta(src), opacity, t);                    \
78    int compositeAlpha = MUL_UN8(Ba, srcTotalAlpha, t);                         \
79    return rgba_blender_merge(normalToBlendMerge, blend, compositeAlpha);       \
80  }                                                                             \
81  else                                                                          \
82    return rgba_blender_normal(backdrop, src, opacity);                         \
83*/
84fn 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    //todo!()
97    } else {
98        normal(backdrop, src, opacity)
99    }
100}
101
102/*
103  int t;
104  int r = blend_multiply(rgba_getr(backdrop), rgba_getr(src), t);
105  int g = blend_multiply(rgba_getg(backdrop), rgba_getg(src), t);
106  int b = blend_multiply(rgba_getb(backdrop), rgba_getb(src), t);
107  src = rgba(r, g, b, 0) | (src & rgba_a_mask);
108  return rgba_blender_normal(backdrop, src, opacity);
109*/
110fn 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
123// --- multiply ----------------------------------------------------------------
124
125pub(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
137// --- screen ------------------------------------------------------------------
138
139pub(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
147// blend_screen(b, s, t)     ((b) + (s) - MUL_UN8((b), (s), (t)))
148fn blend_screen(a: i32, b: i32) -> u8 {
149    (a + b - mul_un8(a, b) as i32) as u8
150}
151
152// --- overlay -----------------------------------------------------------------
153
154pub(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
162// blend_overlay(b, s, t)    (blend_hard_light(s, b, t))
163// blend_hard_light(b, s, t) ((s) < 128 ?                          \
164//    blend_multiply((b), (s)<<1, (t)):    \
165//    blend_screen((b), ((s)<<1)-255, (t)))
166
167fn blend_overlay(b: i32, s: i32) -> u8 {
168    blend_hard_light(s, b)
169}
170
171// --- darken ------------------------------------------------------------------
172
173pub(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
185// --- lighten -----------------------------------------------------------------
186
187pub(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
199// --- color_dodge -------------------------------------------------------------
200
201pub(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        // in floating point: b / (1-s)
218        div_un8(b, s)
219    }
220}
221
222// --- color_burn --------------------------------------------------------------
223
224pub(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        // in floating point: 1 - ((1-b)/s)
241        255 - div_un8(b, s)
242    }
243}
244
245// --- hard_light --------------------------------------------------------------
246
247pub(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
263// --- soft_light --------------------------------------------------------------
264
265pub(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    // The original uses double, but since inputs & output are only 8 bits using
283    // f32 should actually be enough.
284    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
302// --- divide ------------------------------------------------------------------
303
304pub(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
322// --- difference ------------------------------------------------------------------
323
324pub(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
336// --- exclusion ---------------------------------------------------------------
337
338pub(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
346// blend_exclusion(b, s, t)  ((t) = MUL_UN8((b), (s), (t)), ((b) + (s) - 2*(t)))
347fn 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
352// --- addition ----------------------------------------------------------------
353
354pub(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
370// --- subtract ----------------------------------------------------------------
371
372pub(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
388// --- hsl_hue -----------------------------------------------------------------
389
390pub(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
409// --- hsl_saturation ----------------------------------------------------------
410
411pub(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    //dbg!(backdrop, src);
417    let (r, g, b) = as_rgb_f64(src);
418    //dbg!("src", (r, g, b));
419    let sat = saturation(r, g, b);
420    //dbg!(sat);
421
422    let (r, g, b) = as_rgb_f64(backdrop);
423    //dbg!("back", (r, g, b));
424    let lum = luminosity(r, g, b);
425    //dbg!(lum);
426
427    let (r, g, b) = set_saturation(r, g, b, sat);
428    //dbg!("sat", (r, g, b));
429    let (r, g, b) = set_luminocity(r, g, b, lum);
430
431    //dbg!((r, g, b), saturation(r, g, b), luminosity(r, g, b));
432
433    let src = from_rgb_f64(r, g, b, src[3]);
434    // dbg!(src);
435    normal(backdrop, src, opacity)
436}
437
438// --- hsl_color ---------------------------------------------------------------
439
440pub(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
456// --- hsl_luminosity ----------------------------------------------------------
457
458pub(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
475// --- Hue/Saturation/Luminance Utils ------------------------------------------
476
477// this is actually chroma, but this is how the Aseprite's blend functions
478// define it, which in turn come from pixman, which in turn are the
479// PDF nonseperable blend modes which are specified in the "PDF Blend Modes:
480// Addendum" by Adobe.
481fn 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
513// Returns (smallest, middle, highest) where smallest is the index
514// of the smallest element. I.e., 0 if it's `r`, 1 if it's `g`, etc.
515//
516// Implements this static sorting network. Vertical lines are swaps.
517//
518//  r --*--*----- min
519//      |  |
520//  g --*--|--*-- mid
521//         |  |
522//  b -----*--*-- max
523//
524fn static_sort3(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
525    let (min0, mid0, max0) = ((r, 0), (g, 1), (b, 2));
526    // dbg!("--------");
527    // dbg!(min0, mid0, max0);
528    let (min1, mid1) = if min0.0 < mid0.0 {
529        (min0, mid0)
530    } else {
531        (mid0, min0)
532    };
533    // dbg!(min1, mid1);
534    let (min2, max1) = if min1.0 < max0.0 {
535        (min1, max0)
536    } else {
537        (max0, min1)
538    };
539    // dbg!(min2, max1);
540    let (mid2, max2) = if mid1.0 < max1.0 {
541        (mid1, max1)
542    } else {
543        (max1, mid1)
544    };
545    // dbg!(mid2, max2);
546    (min2.1, mid2.1, max2.1)
547}
548
549// Array based implementation as a reference for testing.
550#[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    //dbg!(r, g, b);
556    (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
575// implementation used in Aseprite, even though it uses a lot of compares and
576// is actually broken if r == g  and g < b.
577fn static_sort3_orig(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
578    // min = MIN(r, MIN(g, b));
579    // ((r) < (((g) < (b)) ? (g) : (b))) ? (r) : (((g) < (b)) ? (g) : (b));
580    // max = MAX(r, MAX(g, b));
581    // ((r) > (((g) > (b)) ? (g) : (b))) ? (r) : (((g) > (b)) ? (g) : (b))
582    // mid = ((r) > (g) ?
583    //          ((g) > (b) ?
584    //             (g) :
585    //             ((r) > (b) ?
586    //                (b) :
587    //                (r)
588    //             )
589    //          ) :
590    //          ((g) > (b) ?
591    //             ((b) > (r) ?
592    //                (b) :
593    //                (r)
594    //             ) :
595    //             (g)))
596
597    let min = if r < g.min(b) {
598        0 // r
599    } else if g < b {
600        1 // g
601    } else {
602        2 // b
603    };
604    let max = if r > g.max(b) {
605        0 // r
606    } else if g > b {
607        1 // g
608    } else {
609        2 // b
610    };
611    let mid = if r > g {
612        if g > b {
613            1 // g
614        } else if r > b {
615            2 // b
616        } else {
617            0 // r
618        }
619    } else if g > b {
620        if b > r {
621            2 // b
622        } else {
623            0 // r
624        }
625    } else {
626        1 // g
627    };
628    (min, mid, max)
629}
630
631// Ensure that we produce the same output as Aseprite, even though it's wrong.
632const 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        // i.e., they're not all the same
644        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// This test actually fails because Aseprite's version fails this test.
655#[test]
656fn test_set_saturation() {
657    if ASEPRITE_SATURATION_BUG_COMPATIBLE {
658        // This fails for the Aseprite implementation
659        return;
660    }
661    // Test that:
662    //
663    //     saturation(set_saturation(r, g, b, s) == s)
664    //
665    // (unless saturation(r, g, b) == 0, i.e., they're all the same color)
666    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                    // println!("set_saturation({:.3}, {:.3}, {:.3}, {:.3}) => ({:.3}, {:.3}, {:.3}) => sat: {:.5} (input sat: {:.5})",
684                    // r, g, b, sat, r1, g1, b1, sat1, sat0);
685
686                    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
698// --- rgba utils --------------------------------------------------------------
699
700fn 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
730/*
731color_t rgba_blender_merge(color_t backdrop, color_t src, int opacity)
732{
733  int Br, Bg, Bb, Ba;
734  int Sr, Sg, Sb, Sa;
735  int Rr, Rg, Rb, Ra;
736  int t;
737
738  Br = rgba_getr(backdrop);
739  Bg = rgba_getg(backdrop);
740  Bb = rgba_getb(backdrop);
741  Ba = rgba_geta(backdrop);
742
743  Sr = rgba_getr(src);
744  Sg = rgba_getg(src);
745  Sb = rgba_getb(src);
746  Sa = rgba_geta(src);
747
748  if (Ba == 0) {
749    Rr = Sr;
750    Rg = Sg;
751    Rb = Sb;
752  }
753  else if (Sa == 0) {
754    Rr = Br;
755    Rg = Bg;
756    Rb = Bb;
757  }
758  else {
759    Rr = Br + MUL_UN8((Sr - Br), opacity, t);
760    Rg = Bg + MUL_UN8((Sg - Bg), opacity, t);
761    Rb = Bb + MUL_UN8((Sb - Bb), opacity, t);
762  }
763  Ra = Ba + MUL_UN8((Sa - Ba), opacity, t);
764  if (Ra == 0)
765    Rr = Rg = Rb = 0;
766
767  return rgba(Rr, Rg, Rb, Ra);
768}
769*/
770
771fn 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    //assert_eq!(blend8(0, 237, 128), 0);
791}
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
807// DIV_UN8(a, b)    (((uint16_t) (a) * 0xff + ((b) / 2)) / (b))
808fn 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// fn mul_un8()
814
815/*
816
81767:#define MUL_UN8(a, b, t)                                             \
81868-    ((t) = (a) * (uint16_t)(b) + ONE_HALF, ((((t) >> G_SHIFT ) + (t) ) >> G_SHIFT ))
819
820*/