1use crate::binary::blend_mode::BlendMode;
2
3#[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
134fn 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
150fn 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
167fn 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
182fn 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
197fn 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
208fn 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
220fn 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
229fn blend_exclusion(b: i32, s: i32) -> u8 {
231 let t = mul8(b, s) as i32;
232 (b + s - 2 * t) as u8
233}
234fn 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}
246fn 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}
264fn 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 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}
297fn 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
306fn 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
327fn 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 div8(b, s)
345 }
346}
347
348fn 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
361fn 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}
373fn 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}
392fn 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
401fn blend_screen(a: i32, b: i32) -> u8 {
403 (a + b - mul8(a, b) as i32) as u8
404}
405fn 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
418fn 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 let (min1, mid1) = if min0.0 < mid0.0 {
551 (min0, mid0)
552 } else {
553 (mid0, min0)
554 };
555 let (min2, max1) = if min1.0 < max0.0 {
557 (min1, max0)
558 } else {
559 (max0, min1)
560 };
561 let (mid2, max2) = if mid1.0 < max1.0 {
563 (mid1, max1)
564 } else {
565 (max1, mid1)
566 };
567 (min2.1, mid2.1, max2.1)
569}
570
571fn static_sort3_orig(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
574 let min = if r < g.min(b) {
594 0 } else if g < b {
596 1 } else {
598 2 };
600 let max = if r > g.max(b) {
601 0 } else if g > b {
603 1 } else {
605 2 };
607 let mid = if r > g {
608 if g > b {
609 1 } else if r > b {
611 2 } else {
613 0 }
615 } else if g > b {
616 if b > r {
617 2 } else {
619 0 }
621 } else {
622 1 };
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}