aseprite_loader/loader/
blend.rs

1use crate::binary::blend_mode::BlendMode;
2
3// Rust port of Aseprite's blend functions
4// https://github.com/aseprite/aseprite/blob/master/src/doc/blend_funcs.cpp
5// original implementation: https://github.com/alpine-alpaca/asefile/blob/main/src/blend.rs
6
7#[derive(Clone, Copy)]
8pub(crate) struct Color {
9    pub(crate) r: u8,
10    pub(crate) g: u8,
11    pub(crate) b: u8,
12    pub(crate) a: u8,
13}
14
15impl Color {
16    fn r_i32(&self) -> i32 {
17        self.r.into()
18    }
19    fn g_i32(&self) -> i32 {
20        self.g.into()
21    }
22    fn b_i32(&self) -> i32 {
23        self.b.into()
24    }
25    fn a_i32(&self) -> i32 {
26        self.a.into()
27    }
28    fn r_f64(&self) -> f64 {
29        self.r.into()
30    }
31    fn g_f64(&self) -> f64 {
32        self.g.into()
33    }
34    fn b_f64(&self) -> f64 {
35        self.b.into()
36    }
37    fn a_f64(&self) -> f64 {
38        self.a.into()
39    }
40}
41
42impl Color {
43    fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
44        Self { r, g, b, a }
45    }
46}
47
48impl From<&[u8]> for Color {
49    fn from(value: &[u8]) -> Self {
50        debug_assert!(value.len() == 4);
51        Self {
52            r: value[0],
53            g: value[1],
54            b: value[2],
55            a: value[3],
56        }
57    }
58}
59
60impl From<&[u8; 4]> for Color {
61    fn from(value: &[u8; 4]) -> Self {
62        Self::from(value.as_ref())
63    }
64}
65
66impl From<&[i32]> for Color {
67    fn from(value: &[i32]) -> Self {
68        debug_assert!(value.len() == 4);
69        debug_assert!((0..=255).contains(&value[0]));
70        debug_assert!((0..=255).contains(&value[1]));
71        debug_assert!((0..=255).contains(&value[2]));
72        debug_assert!((0..=255).contains(&value[3]));
73        Self {
74            r: value[0] as u8,
75            g: value[1] as u8,
76            b: value[2] as u8,
77            a: value[3] as u8,
78        }
79    }
80}
81
82impl From<&[i32; 4]> for Color {
83    fn from(value: &[i32; 4]) -> Self {
84        Self::from(value.as_ref())
85    }
86}
87
88impl From<&[f64]> for Color {
89    fn from(value: &[f64]) -> Self {
90        debug_assert!(value.len() == 4);
91        Self::from(
92            [
93                (value[0] * 255.0) as i32,
94                (value[1] * 255.0) as i32,
95                (value[2] * 255.0) as i32,
96                (value[3] * 255.0) as i32,
97            ]
98            .as_slice(),
99        )
100    }
101}
102impl From<&[f64; 4]> for Color {
103    fn from(value: &[f64; 4]) -> Self {
104        Self::from(value.as_ref())
105    }
106}
107
108type BlendFn = fn(Color, Color, u8) -> Color;
109pub(crate) fn blend_mode_to_blend_fn(mode: BlendMode) -> BlendFn {
110    match mode {
111        BlendMode::Normal => normal,
112        BlendMode::Multiply => multiply,
113        BlendMode::Screen => screen,
114        BlendMode::Overlay => overlay,
115        BlendMode::Darken => darken,
116        BlendMode::Lighten => lighten,
117        BlendMode::ColorDodge => color_dodge,
118        BlendMode::ColorBurn => color_burn,
119        BlendMode::HardLight => hard_light,
120        BlendMode::SoftLight => soft_light,
121        BlendMode::Difference => difference,
122        BlendMode::Exclusion => exclusion,
123        BlendMode::Hue => hsl_hue,
124        BlendMode::Saturation => hsl_saturation,
125        BlendMode::Color => hsl_color,
126        BlendMode::Luminosity => hsl_luminosity,
127        BlendMode::Addition => addition,
128        BlendMode::Subtract => subtract,
129        BlendMode::Divide => divide,
130        m => panic!("unimplemented blend mode: {:?}", m),
131    }
132}
133
134// --- addition ----------------------------------------------------------------
135fn addition(back: Color, front: Color, opacity: u8) -> Color {
136    blender(back, front, opacity, addition_baseline)
137}
138fn addition_baseline(back: Color, front: Color, opacity: u8) -> Color {
139    let r = back.r_i32() + front.r_i32();
140    let g = back.g_i32() + front.g_i32();
141    let b = back.b_i32() + front.b_i32();
142
143    normal(
144        back,
145        Color::from(&[r.min(255), g.min(255), b.min(255), front.a_i32()]),
146        opacity,
147    )
148}
149
150// --- subtract ----------------------------------------------------------------
151fn subtract(back: Color, front: Color, opacity: u8) -> Color {
152    blender(back, front, opacity, subtract_baseline)
153}
154
155fn subtract_baseline(back: Color, front: Color, opacity: u8) -> Color {
156    let r = back.r_i32() - front.r_i32();
157    let g = back.g_i32() - front.g_i32();
158    let b = back.b_i32() - front.b_i32();
159
160    normal(
161        back,
162        Color::from(&[r.max(0), g.max(0), b.max(0), front.a_i32()]),
163        opacity,
164    )
165}
166
167// --- hsl_hue -----------------------------------------------------------------
168fn hsl_hue(back: Color, front: Color, opacity: u8) -> Color {
169    blender(back, front, opacity, hsl_hue_baseline)
170}
171
172fn hsl_hue_baseline(back: Color, front: Color, opacity: u8) -> Color {
173    let sat = saturation(back.r_f64(), back.g_f64(), back.b_f64());
174    let lum = luminosity(back.r_f64(), back.g_f64(), back.b_f64());
175
176    let (r, g, b) = set_saturation(front.r_f64(), front.g_f64(), front.b_f64(), sat);
177    let (r, g, b) = set_luminocity(r, g, b, lum);
178
179    normal(back, Color::from(&[r, g, b, front.a_f64()]), opacity)
180}
181
182// --- hsl_saturation ----------------------------------------------------------
183fn hsl_saturation(back: Color, front: Color, opacity: u8) -> Color {
184    blender(back, front, opacity, hsl_saturation_baseline)
185}
186
187fn hsl_saturation_baseline(back: Color, front: Color, opacity: u8) -> Color {
188    let sat = saturation(front.r_f64(), front.g_f64(), front.b_f64());
189    let lum = luminosity(back.r_f64(), back.g_f64(), back.b_f64());
190
191    let (r, g, b) = set_saturation(back.r_f64(), back.g_f64(), back.b_f64(), sat);
192    let (r, g, b) = set_luminocity(r, g, b, lum);
193
194    normal(back, Color::from(&[r, g, b, front.a_f64()]), opacity)
195}
196
197// --- hsl_color ---------------------------------------------------------------
198fn hsl_color(back: Color, front: Color, opacity: u8) -> Color {
199    blender(back, front, opacity, hsl_color_baseline)
200}
201
202fn hsl_color_baseline(back: Color, front: Color, opacity: u8) -> Color {
203    let lum = luminosity(back.r_f64(), back.g_f64(), back.b_f64());
204    let (r, g, b) = set_luminocity(front.r_f64(), front.g_f64(), front.b_f64(), lum);
205    normal(back, Color::from(&[r, g, b, front.a_f64()]), opacity)
206}
207
208// --- hsl_luminosity ----------------------------------------------------------
209fn hsl_luminosity(back: Color, front: Color, opacity: u8) -> Color {
210    blender(back, front, opacity, hsl_luminosity_baseline)
211}
212
213fn hsl_luminosity_baseline(back: Color, front: Color, opacity: u8) -> Color {
214    let lum = luminosity(front.r_f64(), front.g_f64(), front.b_f64());
215    let (r, g, b) = set_luminocity(back.r_f64(), back.g_f64(), back.b_f64(), lum);
216
217    normal(back, Color::from(&[r, g, b, front.a_f64()]), opacity)
218}
219
220// --- exclusion ----------------------------------------------------------------
221fn exclusion(back: Color, front: Color, opacity: u8) -> Color {
222    blender(back, front, opacity, exclusion_baseline)
223}
224
225fn exclusion_baseline(back: Color, front: Color, opacity: u8) -> Color {
226    blend_channel(back, front, opacity, blend_exclusion)
227}
228
229// blend_exclusion(b, s, t)  ((t) = MUL_UN8((b), (s), (t)), ((b) + (s) - 2*(t)))
230fn blend_exclusion(b: i32, s: i32) -> u8 {
231    let t = mul8(b, s) as i32;
232    (b + s - 2 * t) as u8
233}
234// --- difference ----------------------------------------------------------------
235fn difference(back: Color, front: Color, opacity: u8) -> Color {
236    blender(back, front, opacity, difference_baseline)
237}
238
239fn difference_baseline(back: Color, front: Color, opacity: u8) -> Color {
240    blend_channel(back, front, opacity, blend_difference)
241}
242
243fn blend_difference(b: i32, s: i32) -> u8 {
244    (b - s).unsigned_abs() as u8
245}
246// --- divide ----------------------------------------------------------------
247fn divide(back: Color, front: Color, opacity: u8) -> Color {
248    blender(back, front, opacity, divide_baseline)
249}
250
251fn divide_baseline(back: Color, front: Color, opacity: u8) -> Color {
252    blend_channel(back, front, opacity, blend_divide)
253}
254
255fn blend_divide(b: i32, s: i32) -> u8 {
256    if b == 0 {
257        0
258    } else if b >= s {
259        255
260    } else {
261        div8(b, s)
262    }
263}
264// --- soft light ----------------------------------------------------------------
265fn soft_light(back: Color, front: Color, opacity: u8) -> Color {
266    blender(back, front, opacity, soft_light_baseline)
267}
268
269fn soft_light_baseline(back: Color, front: Color, opacity: u8) -> Color {
270    let r = blend_soft_light(back.r_i32(), front.r_i32());
271    let g = blend_soft_light(back.g_i32(), front.g_i32());
272    let b = blend_soft_light(back.b_i32(), back.b_i32());
273
274    normal(back, Color::from(&[r, g, b, front.a_i32()]), opacity)
275}
276
277fn blend_soft_light(b: i32, s: i32) -> i32 {
278    // The original uses double, but since inputs & output are only 8 bits using
279    // f32 should actually be enough.
280    let b: f64 = b as f64 / 255.0;
281    let s: f64 = s as f64 / 255.0;
282
283    let d = if b <= 0.25 {
284        ((16.0 * b - 12.0) * b + 4.0) * b
285    } else {
286        b.sqrt()
287    };
288
289    let r = if s <= 0.5 {
290        b - (1.0 - 2.0 * s) * b * (1.0 - b)
291    } else {
292        b + (2.0 * s - 1.0) * (d - b)
293    };
294
295    (r * 255.0 + 0.5) as u32 as i32
296}
297// --- hard light ----------------------------------------------------------------
298fn hard_light(back: Color, front: Color, opacity: u8) -> Color {
299    blender(back, front, opacity, hard_light_baseline)
300}
301
302fn hard_light_baseline(back: Color, front: Color, opacity: u8) -> Color {
303    blend_channel(back, front, opacity, blend_hard_light)
304}
305
306// --- color burn ----------------------------------------------------------------
307fn color_burn(back: Color, front: Color, opacity: u8) -> Color {
308    blender(back, front, opacity, color_burn_baseline)
309}
310
311fn color_burn_baseline(back: Color, front: Color, opacity: u8) -> Color {
312    blend_channel(back, front, opacity, blend_color_burn)
313}
314
315fn blend_color_burn(b: i32, s: i32) -> u8 {
316    if b == 255 {
317        return 255;
318    }
319    let b = 255 - b;
320    if b >= s {
321        0
322    } else {
323        255 - div8(b, s)
324    }
325}
326
327// --- color doge ----------------------------------------------------------------
328fn color_dodge(back: Color, front: Color, opacity: u8) -> Color {
329    blender(back, front, opacity, color_dodge_baseline)
330}
331
332fn color_dodge_baseline(back: Color, front: Color, opacity: u8) -> Color {
333    blend_channel(back, front, opacity, blend_color_dodge)
334}
335fn blend_color_dodge(b: i32, s: i32) -> u8 {
336    if b == 0 {
337        return 0;
338    }
339    let s = 255 - s;
340    if b >= s {
341        255
342    } else {
343        // in floating point: b / (1-s)
344        div8(b, s)
345    }
346}
347
348// --- lighten ----------------------------------------------------------------
349fn lighten(backdrop: Color, src: Color, opacity: u8) -> Color {
350    blender(backdrop, src, opacity, lighten_baseline)
351}
352
353fn lighten_baseline(backdrop: Color, src: Color, opacity: u8) -> Color {
354    blend_channel(backdrop, src, opacity, blend_lighten)
355}
356
357fn blend_lighten(b: i32, s: i32) -> u8 {
358    b.max(s) as u8
359}
360
361// --- darken ----------------------------------------------------------------
362fn darken(back: Color, front: Color, opacity: u8) -> Color {
363    blender(back, front, opacity, darken_baseline)
364}
365
366fn darken_baseline(back: Color, front: Color, opacity: u8) -> Color {
367    blend_channel(back, front, opacity, blend_darken)
368}
369
370fn blend_darken(b: i32, s: i32) -> u8 {
371    b.min(s) as u8
372}
373// --- overlay ----------------------------------------------------------------
374fn overlay(back: Color, front: Color, opacity: u8) -> Color {
375    blender(back, front, opacity, overlay_baseline)
376}
377
378fn overlay_baseline(backdrop: Color, src: Color, opacity: u8) -> Color {
379    blend_channel(backdrop, src, opacity, blend_overlay)
380}
381
382fn blend_overlay(b: i32, s: i32) -> u8 {
383    blend_hard_light(s, b)
384}
385fn blend_hard_light(b: i32, s: i32) -> u8 {
386    if s < 128 {
387        blend_multiply(b, s << 1)
388    } else {
389        blend_screen(b, (s << 1) - 255)
390    }
391}
392// --- screen ----------------------------------------------------------------
393fn screen(back: Color, front: Color, opacity: u8) -> Color {
394    blender(back, front, opacity, screen_baseline)
395}
396
397fn screen_baseline(back: Color, front: Color, opacity: u8) -> Color {
398    blend_channel(back, front, opacity, blend_screen)
399}
400
401// blend_screen(b, s, t)     ((b) + (s) - MUL_UN8((b), (s), (t)))
402fn blend_screen(a: i32, b: i32) -> u8 {
403    (a + b - mul8(a, b) as i32) as u8
404}
405// --- multiply ----------------------------------------------------------------
406fn multiply(back: Color, front: Color, opacity: u8) -> Color {
407    blender(back, front, opacity, multiply_baseline)
408}
409
410fn multiply_baseline(back: Color, front: Color, opacity: u8) -> Color {
411    blend_channel(back, front, opacity, blend_multiply)
412}
413
414fn blend_multiply(a: i32, b: i32) -> u8 {
415    mul8(a, b)
416}
417
418// --- Util --------------------------------------------------------------------
419
420fn blender<F>(back: Color, front: Color, opacity: u8, f: F) -> Color
421where
422    F: Fn(Color, Color, u8) -> Color,
423{
424    if back.a != 0 {
425        let norm = normal(back, front, opacity);
426        let blend = f(back, front, opacity);
427        let normal_to_blend_merge = merge(norm, blend, back.a);
428        let src_total_alpha = mul8(front.a_i32(), opacity as i32);
429        let composite_alpha = mul8(back.a_i32(), src_total_alpha as i32);
430        merge(normal_to_blend_merge, blend, composite_alpha)
431    } else {
432        normal(back, front, opacity)
433    }
434}
435
436fn blend_channel<F>(back: Color, front: Color, opacity: u8, f: F) -> Color
437where
438    F: Fn(i32, i32) -> u8,
439{
440    let r = f(back.r_i32(), front.r_i32());
441    let g = f(back.g_i32(), front.g_i32());
442    let b = f(back.b_i32(), front.b_i32());
443
444    normal(back, Color::new(r, g, b, front.a), opacity)
445}
446
447fn mul8(a: i32, b: i32) -> u8 {
448    let t = a * b + 0x80;
449    let r = ((t >> 8) + t) >> 8;
450    r as u8
451}
452
453fn div8(a: i32, b: i32) -> u8 {
454    let t = a * 0xff;
455    let r = (t + (b / 2)) / b;
456    r as u8
457}
458
459fn blend8(back: u8, src: u8, opacity: u8) -> u8 {
460    let src_x = src as i32;
461    let back_x = back as i32;
462    let a = src_x - back_x;
463    let b = opacity as i32;
464    let t = a * b + 0x80;
465    let r = ((t >> 8) + t) >> 8;
466    (back as i32 + r) as u8
467}
468
469fn normal(back: Color, front: Color, opacity: u8) -> Color {
470    if back.a == 0 {
471        let alpha = mul8(front.a_i32(), opacity as i32);
472        return Color::new(front.r, front.g, front.b, alpha);
473    } else if front.a == 0 {
474        return back;
475    }
476
477    let front_a = mul8(front.a_i32(), opacity as i32) as i32;
478    let res_a = front_a as i32 + back.a_i32() - mul8(back.a_i32(), front_a) as i32;
479
480    let res_r = back.r_i32() + ((front.r_i32() - back.r_i32()) * front_a) / res_a;
481    let res_g = back.g_i32() + ((front.g_i32() - back.g_i32()) * front_a) / res_a;
482    let res_b = back.b_i32() + ((front.b_i32() - back.b_i32()) * front_a) / res_a;
483
484    Color::from(&[res_r, res_g, res_b, res_a])
485}
486
487fn merge(back: Color, front: Color, opacity: u8) -> Color {
488    let res_r;
489    let res_g;
490    let res_b;
491
492    if back.a == 0 {
493        res_r = front.r;
494        res_g = front.g;
495        res_b = front.b;
496    } else if front.a == 0 {
497        res_r = back.r;
498        res_g = back.g;
499        res_b = back.b;
500    } else {
501        res_r = blend8(back.r, front.r, opacity);
502        res_g = blend8(back.g, front.g, opacity);
503        res_b = blend8(back.b, front.b, opacity);
504    }
505    let res_a = blend8(back.a, front.a, opacity);
506
507    if res_a == 0 {
508        Color::new(0, 0, 0, 0)
509    } else {
510        Color::new(res_r, res_g, res_b, res_a)
511    }
512}
513
514fn clip_color(mut r: f64, mut g: f64, mut b: f64) -> (f64, f64, f64) {
515    let lum = luminosity(r, g, b);
516    let min = r.min(g.min(b));
517    let max = r.max(g.max(b));
518
519    if min < 0.0 {
520        r = lum + (((r - lum) * lum) / (lum - min));
521        g = lum + (((g - lum) * lum) / (lum - min));
522        b = lum + (((b - lum) * lum) / (lum - min));
523    }
524
525    if max > 1.0 {
526        r = lum + (((r - lum) * (1.0 - lum)) / (max - lum));
527        g = lum + (((g - lum) * (1.0 - lum)) / (max - lum));
528        b = lum + (((b - lum) * (1.0 - lum)) / (max - lum));
529    }
530    (r, g, b)
531}
532
533fn set_luminocity(r: f64, g: f64, b: f64, lum: f64) -> (f64, f64, f64) {
534    let delta = lum - luminosity(r, g, b);
535    clip_color(r + delta, g + delta, b + delta)
536}
537
538fn saturation(r: f64, g: f64, b: f64) -> f64 {
539    r.max(g.max(b)) - r.min(g.min(b))
540}
541
542fn luminosity(r: f64, g: f64, b: f64) -> f64 {
543    0.3 * r + 0.59 * g + 0.11 * b
544}
545
546fn static_sort3(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
547    let (min0, mid0, max0) = ((r, 0), (g, 1), (b, 2));
548    // dbg!("--------");
549    // dbg!(min0, mid0, max0);
550    let (min1, mid1) = if min0.0 < mid0.0 {
551        (min0, mid0)
552    } else {
553        (mid0, min0)
554    };
555    // dbg!(min1, mid1);
556    let (min2, max1) = if min1.0 < max0.0 {
557        (min1, max0)
558    } else {
559        (max0, min1)
560    };
561    // dbg!(min2, max1);
562    let (mid2, max2) = if mid1.0 < max1.0 {
563        (mid1, max1)
564    } else {
565        (max1, mid1)
566    };
567    // dbg!(mid2, max2);
568    (min2.1, mid2.1, max2.1)
569}
570
571/// aseprite original
572/// https://github.com/aseprite/aseprite/blob/main/src/doc/blend_funcs.cpp#L371
573fn static_sort3_orig(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
574    // min = MIN(r, MIN(g, b));
575    // ((r) < (((g) < (b)) ? (g) : (b))) ? (r) : (((g) < (b)) ? (g) : (b));
576    // max = MAX(r, MAX(g, b));
577    // ((r) > (((g) > (b)) ? (g) : (b))) ? (r) : (((g) > (b)) ? (g) : (b))
578    // mid = ((r) > (g) ?
579    //          ((g) > (b) ?
580    //             (g) :
581    //             ((r) > (b) ?
582    //                (b) :
583    //                (r)
584    //             )
585    //          ) :
586    //          ((g) > (b) ?
587    //             ((b) > (r) ?
588    //                (b) :
589    //                (r)
590    //             ) :
591    //             (g)))
592
593    let min = if r < g.min(b) {
594        0 // r
595    } else if g < b {
596        1 // g
597    } else {
598        2 // b
599    };
600    let max = if r > g.max(b) {
601        0 // r
602    } else if g > b {
603        1 // g
604    } else {
605        2 // b
606    };
607    let mid = if r > g {
608        if g > b {
609            1 // g
610        } else if r > b {
611            2 // b
612        } else {
613            0 // r
614        }
615    } else if g > b {
616        if b > r {
617            2 // b
618        } else {
619            0 // r
620        }
621    } else {
622        1 // g
623    };
624    (min, mid, max)
625}
626
627const ASEPRITE_SATURATION_BUG_COMPATIBLE: bool = true;
628
629fn set_saturation(r: f64, g: f64, b: f64, sat: f64) -> (f64, f64, f64) {
630    let mut col = [r, g, b];
631
632    let (min, mid, max) = if ASEPRITE_SATURATION_BUG_COMPATIBLE {
633        static_sort3_orig(r, g, b)
634    } else {
635        static_sort3(r, g, b)
636    };
637    if col[max] > col[min] {
638        col[mid] = ((col[mid] - col[min]) * sat) / (col[max] - col[min]);
639        col[max] = sat;
640    } else {
641        col[mid] = 0.0;
642        col[max] = 0.0;
643    }
644    col[min] = 0.0;
645    (col[0], col[1], col[2])
646}