use std::usize;
use image::Rgba;
pub type Color8 = Rgba<u8>;
#[allow(dead_code)]
pub(crate) fn merge(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let [back_r, back_g, back_b, back_a] = backdrop.0;
let [src_r, src_g, src_b, src_a] = src.0;
let res_r;
let res_g;
let res_b;
if back_a == 0 {
res_r = src_r;
res_g = src_g;
res_b = src_b;
} else if src_a == 0 {
res_r = back_r;
res_g = back_g;
res_b = back_b;
} else {
res_r = blend8(back_r, src_r, opacity);
res_g = blend8(back_g, src_g, opacity);
res_b = blend8(back_b, src_b, opacity);
}
let res_a = blend8(back_a, src_a, opacity);
if res_a == 0 {
Rgba([0, 0, 0, 0])
} else {
Rgba([res_r, res_g, res_b, res_a])
}
}
pub(crate) fn normal(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (back_r, back_g, back_b, back_a) = as_rgba_i32(backdrop);
let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
if back_a == 0 {
let alpha = mul_un8(src_a, opacity as i32) as i32;
return from_rgba_i32(src_r, src_g, src_b, alpha);
} else if src_a == 0 {
return backdrop;
}
let src_a = mul_un8(src_a, opacity as i32) as i32;
let res_a = src_a + back_a - mul_un8(back_a, src_a) as i32;
let res_r = back_r + ((src_r - back_r) * src_a) / res_a;
let res_g = back_g + ((src_g - back_g) * src_a) / res_a;
let res_b = back_b + ((src_b - back_b) * src_a) / res_a;
from_rgba_i32(res_r, res_g, res_b, res_a)
}
fn blender<F>(backdrop: Color8, src: Color8, opacity: u8, f: F) -> Color8
where
F: Fn(Color8, Color8, u8) -> Color8,
{
if backdrop[3] != 0 {
let norm = normal(backdrop, src, opacity);
let blend = f(backdrop, src, opacity);
let back_alpha = backdrop[3];
let normal_to_blend_merge = merge(norm, blend, back_alpha);
let src_total_alpha = mul_un8(src[3] as i32, opacity as i32);
let composite_alpha = mul_un8(back_alpha as i32, src_total_alpha as i32);
merge(normal_to_blend_merge, blend, composite_alpha)
} else {
normal(backdrop, src, opacity)
}
}
fn blend_channel<F>(backdrop: Color8, src: Color8, opacity: u8, f: F) -> Color8
where
F: Fn(i32, i32) -> u8,
{
let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
let (src_r, src_g, src_b, _) = as_rgba_i32(src);
let r = f(back_r, src_r);
let g = f(back_g, src_g);
let b = f(back_b, src_b);
let src = Rgba([r, g, b, src[3]]);
normal(backdrop, src, opacity)
}
pub(crate) fn multiply(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, multiply_baseline)
}
fn multiply_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_multiply)
}
fn blend_multiply(a: i32, b: i32) -> u8 {
mul_un8(a, b)
}
pub(crate) fn screen(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, screen_baseline)
}
fn screen_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_screen)
}
fn blend_screen(a: i32, b: i32) -> u8 {
(a + b - mul_un8(a, b) as i32) as u8
}
pub(crate) fn overlay(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, overlay_baseline)
}
fn overlay_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_overlay)
}
fn blend_overlay(b: i32, s: i32) -> u8 {
blend_hard_light(s, b)
}
pub(crate) fn darken(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, darken_baseline)
}
fn darken_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_darken)
}
fn blend_darken(b: i32, s: i32) -> u8 {
b.min(s) as u8
}
pub(crate) fn lighten(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, lighten_baseline)
}
fn lighten_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_lighten)
}
fn blend_lighten(b: i32, s: i32) -> u8 {
b.max(s) as u8
}
pub(crate) fn color_dodge(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, color_dodge_baseline)
}
fn color_dodge_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_color_dodge)
}
fn blend_color_dodge(b: i32, s: i32) -> u8 {
if b == 0 {
return 0;
}
let s = 255 - s;
if b >= s {
255
} else {
div_un8(b, s)
}
}
pub(crate) fn color_burn(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, color_burn_baseline)
}
fn color_burn_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_color_burn)
}
fn blend_color_burn(b: i32, s: i32) -> u8 {
if b == 255 {
return 255;
}
let b = 255 - b;
if b >= s {
0
} else {
255 - div_un8(b, s)
}
}
pub(crate) fn hard_light(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, hard_light_baseline)
}
fn hard_light_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_hard_light)
}
fn blend_hard_light(b: i32, s: i32) -> u8 {
if s < 128 {
blend_multiply(b, s << 1)
} else {
blend_screen(b, (s << 1) - 255)
}
}
pub(crate) fn soft_light(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, soft_light_baseline)
}
fn soft_light_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
let r = blend_soft_light(back_r, src_r);
let g = blend_soft_light(back_g, src_g);
let b = blend_soft_light(back_b, src_b);
let src = from_rgba_i32(r, g, b, src_a);
normal(backdrop, src, opacity)
}
fn blend_soft_light(b: i32, s: i32) -> i32 {
let b: f64 = b as f64 / 255.0;
let s: f64 = s as f64 / 255.0;
let d = if b <= 0.25 {
((16.0 * b - 12.0) * b + 4.0) * b
} else {
b.sqrt()
};
let r = if s <= 0.5 {
b - (1.0 - 2.0 * s) * b * (1.0 - b)
} else {
b + (2.0 * s - 1.0) * (d - b)
};
(r * 255.0 + 0.5) as u32 as i32
}
pub(crate) fn divide(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, divide_baseline)
}
fn divide_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_divide)
}
fn blend_divide(b: i32, s: i32) -> u8 {
if b == 0 {
0
} else if b >= s {
255
} else {
div_un8(b, s)
}
}
pub(crate) fn difference(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, difference_baseline)
}
fn difference_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_difference)
}
fn blend_difference(b: i32, s: i32) -> u8 {
(b - s).unsigned_abs() as u8
}
pub(crate) fn exclusion(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, exclusion_baseline)
}
fn exclusion_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blend_channel(backdrop, src, opacity, blend_exclusion)
}
fn blend_exclusion(b: i32, s: i32) -> u8 {
let t = mul_un8(b, s) as i32;
(b + s - 2 * t) as u8
}
pub(crate) fn addition(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, addition_baseline)
}
fn addition_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
let r = back_r + src_r;
let g = back_g + src_g;
let b = back_b + src_b;
let src = from_rgba_i32(r.min(255), g.min(255), b.min(255), src_a);
normal(backdrop, src, opacity)
}
pub(crate) fn subtract(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, subtract_baseline)
}
fn subtract_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (back_r, back_g, back_b, _) = as_rgba_i32(backdrop);
let (src_r, src_g, src_b, src_a) = as_rgba_i32(src);
let r = back_r - src_r;
let g = back_g - src_g;
let b = back_b - src_b;
let src = from_rgba_i32(r.max(0), g.max(0), b.max(0), src_a);
normal(backdrop, src, opacity)
}
pub(crate) fn hsl_hue(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, hsl_hue_baseline)
}
fn hsl_hue_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (r, g, b) = as_rgb_f64(backdrop);
let sat = saturation(r, g, b);
let lum = luminosity(r, g, b);
let (r, g, b) = as_rgb_f64(src);
let (r, g, b) = set_saturation(r, g, b, sat);
let (r, g, b) = set_luminocity(r, g, b, lum);
let src = from_rgb_f64(r, g, b, src[3]);
normal(backdrop, src, opacity)
}
pub(crate) fn hsl_saturation(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, hsl_saturation_baseline)
}
fn hsl_saturation_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (r, g, b) = as_rgb_f64(src);
let sat = saturation(r, g, b);
let (r, g, b) = as_rgb_f64(backdrop);
let lum = luminosity(r, g, b);
let (r, g, b) = set_saturation(r, g, b, sat);
let (r, g, b) = set_luminocity(r, g, b, lum);
let src = from_rgb_f64(r, g, b, src[3]);
normal(backdrop, src, opacity)
}
pub(crate) fn hsl_color(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, hsl_color_baseline)
}
fn hsl_color_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (r, g, b) = as_rgb_f64(backdrop);
let lum = luminosity(r, g, b);
let (r, g, b) = as_rgb_f64(src);
let (r, g, b) = set_luminocity(r, g, b, lum);
let src = from_rgb_f64(r, g, b, src[3]);
normal(backdrop, src, opacity)
}
pub(crate) fn hsl_luminosity(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
blender(backdrop, src, opacity, hsl_luminosity_baseline)
}
fn hsl_luminosity_baseline(backdrop: Color8, src: Color8, opacity: u8) -> Color8 {
let (r, g, b) = as_rgb_f64(src);
let lum = luminosity(r, g, b);
let (r, g, b) = as_rgb_f64(backdrop);
let (r, g, b) = set_luminocity(r, g, b, lum);
let src = from_rgb_f64(r, g, b, src[3]);
normal(backdrop, src, opacity)
}
fn saturation(r: f64, g: f64, b: f64) -> f64 {
r.max(g.max(b)) - r.min(g.min(b))
}
fn luminosity(r: f64, g: f64, b: f64) -> f64 {
0.3 * r + 0.59 * g + 0.11 * b
}
fn set_luminocity(r: f64, g: f64, b: f64, lum: f64) -> (f64, f64, f64) {
let delta = lum - luminosity(r, g, b);
clip_color(r + delta, g + delta, b + delta)
}
fn clip_color(mut r: f64, mut g: f64, mut b: f64) -> (f64, f64, f64) {
let lum = luminosity(r, g, b);
let min = r.min(g.min(b));
let max = r.max(g.max(b));
if min < 0.0 {
r = lum + (((r - lum) * lum) / (lum - min));
g = lum + (((g - lum) * lum) / (lum - min));
b = lum + (((b - lum) * lum) / (lum - min));
}
if max > 1.0 {
r = lum + (((r - lum) * (1.0 - lum)) / (max - lum));
g = lum + (((g - lum) * (1.0 - lum)) / (max - lum));
b = lum + (((b - lum) * (1.0 - lum)) / (max - lum));
}
(r, g, b)
}
fn static_sort3(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
let (min0, mid0, max0) = ((r, 0), (g, 1), (b, 2));
let (min1, mid1) = if min0.0 < mid0.0 {
(min0, mid0)
} else {
(mid0, min0)
};
let (min2, max1) = if min1.0 < max0.0 {
(min1, max0)
} else {
(max0, min1)
};
let (mid2, max2) = if mid1.0 < max1.0 {
(mid1, max1)
} else {
(max1, mid1)
};
(min2.1, mid2.1, max2.1)
}
#[cfg(test)]
fn static_sort3_spec(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
let mut inp = [(r, 0), (g, 1), (b, 2)];
inp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
let res: Vec<usize> = inp.iter().map(|p| p.1).collect();
(res[0], res[1], res[2])
}
#[test]
fn test_static_sort3() {
let (r, g, b) = (2.0, 3.0, 4.0);
assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
let (r, g, b) = (2.0, 4.0, 3.0);
assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
let (r, g, b) = (3.0, 2.0, 4.0);
assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
let (r, g, b) = (3.0, 4.0, 2.0);
assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
let (r, g, b) = (4.0, 2.0, 3.0);
assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
let (r, g, b) = (4.0, 3.0, 2.0);
assert_eq!(static_sort3(r, g, b), static_sort3_spec(r, g, b));
}
fn static_sort3_orig(r: f64, g: f64, b: f64) -> (usize, usize, usize) {
let min = if r < g.min(b) {
0 } else if g < b {
1 } else {
2 };
let max = if r > g.max(b) {
0 } else if g > b {
1 } else {
2 };
let mid = if r > g {
if g > b {
1 } else if r > b {
2 } else {
0 }
} else if g > b {
if b > r {
2 } else {
0 }
} else {
1 };
(min, mid, max)
}
const ASEPRITE_SATURATION_BUG_COMPATIBLE: bool = true;
fn set_saturation(r: f64, g: f64, b: f64, sat: f64) -> (f64, f64, f64) {
let mut col = [r, g, b];
let (min, mid, max) = if ASEPRITE_SATURATION_BUG_COMPATIBLE {
static_sort3_orig(r, g, b)
} else {
static_sort3(r, g, b)
};
if col[max] > col[min] {
col[mid] = ((col[mid] - col[min]) * sat) / (col[max] - col[min]);
col[max] = sat;
} else {
col[mid] = 0.0;
col[max] = 0.0;
}
col[min] = 0.0;
(col[0], col[1], col[2])
}
#[test]
fn test_set_saturation() {
if ASEPRITE_SATURATION_BUG_COMPATIBLE {
return;
}
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];
for r in steps.iter().cloned() {
for g in steps.iter().cloned() {
for b in steps.iter().cloned() {
for sat in steps.iter().cloned() {
let sat0 = saturation(r, g, b);
println!(
"* x = ({:.3}, {:.3}, {:.3}); x.sat() = {:.5}",
r, g, b, sat0
);
let (r1, g1, b1) = set_saturation(r, g, b, sat);
let sat1 = saturation(r1, g1, b1);
println!(
" y = x.set_sat({:.5}); y = ({:.3}, {:.3}, {:.3}), y.sat() = {:.5}",
sat, r1, g1, b1, sat1
);
if !(r == g && g == b) && (sat1 - sat).abs() > 0.00001 {
panic!(
"set_saturation({:.3}, {:.3}, {:.3}, {:.3}) => ({:.3}, {:.3}, {:.3}) => sat: {:.5} (input sat: {:.5})",
r, g, b, sat, r1, g1, b1, sat1, sat0
);
}
}
}
}
}
}
fn as_rgba_i32(color: Color8) -> (i32, i32, i32, i32) {
let [r, g, b, a] = color.0;
(r as i32, g as i32, b as i32, a as i32)
}
fn as_rgb_f64(color: Color8) -> (f64, f64, f64) {
let r = color[0] as f64 / 255.0;
let g = color[1] as f64 / 255.0;
let b = color[2] as f64 / 255.0;
(r, g, b)
}
fn from_rgba_i32(r: i32, g: i32, b: i32, a: i32) -> Color8 {
debug_assert!((0..=255).contains(&r));
debug_assert!((0..=255).contains(&g));
debug_assert!((0..=255).contains(&b));
debug_assert!((0..=255).contains(&a));
Rgba([r as u8, g as u8, b as u8, a as u8])
}
fn from_rgb_f64(r: f64, g: f64, b: f64, a: u8) -> Color8 {
from_rgba_i32(
(r * 255.0) as i32,
(g * 255.0) as i32,
(b * 255.0) as i32,
a as i32,
)
}
fn blend8(back: u8, src: u8, opacity: u8) -> u8 {
let src_x = src as i32;
let back_x = back as i32;
let a = src_x - back_x;
let b = opacity as i32;
let t = a * b + 0x80;
let r = ((t >> 8) + t) >> 8;
(back as i32 + r) as u8
}
#[test]
fn test_blend8() {
assert_eq!(blend8(80, 50, 0), 80);
assert_eq!(blend8(80, 50, 128), 65);
assert_eq!(blend8(80, 50, 255), 50);
assert_eq!(blend8(80, 150, 128), 80 + (70 / 2));
assert_eq!(blend8(80, 150, 51), 80 + (70 / 5));
assert_eq!(blend8(80, 150, 36), 80 + (70 / 7));
}
#[test]
fn test_normal() {
let back = Rgba([0, 205, 249, 255]);
let front = Rgba([237, 118, 20, 255]);
let res = normal(back, front, 128);
assert_eq!(Rgba([118, 162, 135, 255]), res);
}
pub(crate) fn mul_un8(a: i32, b: i32) -> u8 {
let t = a * b + 0x80;
let r = ((t >> 8) + t) >> 8;
r as u8
}
fn div_un8(a: i32, b: i32) -> u8 {
let t = a * 0xff;
let r = (t + (b / 2)) / b;
r as u8
}