use crate::graphics::Scanline;
use crate::{alpha_compose, clamp};
use crate::{Rgba, RgbaImage};
use log::error;
pub fn average_color(img: &RgbaImage) -> Rgba<u8> {
let (w, h) = img.dimensions();
let mut r = 0;
let mut g = 0;
let mut b = 0;
for x in 0..w {
for y in 0..h {
let pixel: &Rgba<u8> = img.get_pixel(x as u32, y as u32);
let data = pixel.0;
r += data[0] as u32;
g += data[1] as u32;
b += data[2] as u32;
}
}
r /= w * h;
g /= w * h;
b /= w * h;
Rgba([r as u8, g as u8, b as u8, 255])
}
pub fn compute_color(
origin_img: &RgbaImage,
current_img: &RgbaImage,
lines: &Vec<Scanline>,
alpha: u8,
) -> Rgba<u8> {
let mut rsum = 0;
let mut gsum = 0;
let mut bsum = 0;
let mut count = 0;
let a: i32 = 0x101 * 255 / alpha as i32;
for line in lines {
for x in line.x1..=line.x2 {
let mut pixel: &Rgba<u8> = origin_img.get_pixel(x as u32, line.y as u32);
let mut data = pixel.0;
let or = data[0] as i32;
let og = data[1] as i32;
let ob = data[2] as i32;
pixel = current_img.get_pixel(x as u32, line.y as u32);
data = pixel.0;
let cr = data[0] as i32;
let cg = data[1] as i32;
let cb = data[2] as i32;
rsum += ((or - cr) * a + cr * 0x101) as i64;
gsum += ((og - cg) * a + cg * 0x101) as i64;
bsum += ((ob - cb) * a + cb * 0x101) as i64;
count += 1;
}
}
assert_eq!(count > 0, true);
let r = clamp((rsum / count) as i32 >> 8, 0, 255) as u8;
let g = clamp((gsum / count) as i32 >> 8, 0, 255) as u8;
let b = clamp((bsum / count) as i32 >> 8, 0, 255) as u8;
Rgba([r, g, b, alpha])
}
pub fn diff_partial(
origin_img: &RgbaImage,
before_img: &RgbaImage,
after_img: &RgbaImage,
lines: &Vec<Scanline>,
score: f64,
) -> f64 {
let (w, h) = origin_img.dimensions();
let mut total: f64 = (score * 255.0) * (score * 255.0) * (w * h * 4) as f64;
for line in lines {
for x in line.x1..=line.x2 {
let mut pixel: &Rgba<u8> = origin_img.get_pixel(x as u32, line.y as u32);
let mut data = pixel.0;
let or = data[0] as i32;
let og = data[1] as i32;
let ob = data[2] as i32;
let oa = data[3] as i32;
pixel = before_img.get_pixel(x as u32, line.y as u32);
data = pixel.0;
let br = data[0] as i32;
let bg = data[1] as i32;
let bb = data[2] as i32;
let ba = data[3] as i32;
pixel = after_img.get_pixel(x as u32, line.y as u32);
data = pixel.0;
let ar = data[0] as i32;
let ag = data[1] as i32;
let ab = data[2] as i32;
let aa = data[3] as i32;
let dr1 = or - br;
let dg1 = og - bg;
let db1 = ob - bb;
let da1 = oa - ba;
let dr2 = or - ar;
let dg2 = og - ag;
let db2 = ob - ab;
let da2 = oa - aa;
total += (dr2 * dr2 + dg2 * dg2 + db2 * db2 + da2 * da2) as f64;
total -= (dr1 * dr1 + dg1 * dg1 + db1 * db1 + da1 * da1) as f64;
}
}
(total / (w * h * 4) as f64).sqrt() / 255.0
}
pub fn diff_full(origin_img: &RgbaImage, current_img: &RgbaImage) -> f64 {
let mut total = 0.0;
let (w, h) = origin_img.dimensions();
for x in 0..w {
for y in 0..h {
let mut pixel: &Rgba<u8> = origin_img.get_pixel(x as u32, y as u32);
let mut data = pixel.0;
let or = data[0] as i32;
let og = data[1] as i32;
let ob = data[2] as i32;
let oa = data[3] as i32;
pixel = current_img.get_pixel(x as u32, y as u32);
data = pixel.0;
let cr = data[0] as i32;
let cg = data[1] as i32;
let cb = data[2] as i32;
let ca = data[3] as i32;
let dr = or - cr;
let dg = og - cg;
let db = ob - cb;
let da = oa - ca;
total += (dr * dr + dg * dg + db * db + da * da) as f64;
}
}
(total / (w * h * 4) as f64).sqrt() / 255.0
}
pub fn diff_partial_with_color(
origin_img: &RgbaImage,
before_img: &RgbaImage,
lines: &Vec<Scanline>,
score: f64,
color: Rgba<u8>,
) -> f64 {
if lines.is_empty() {
return score;
}
let (w, h) = origin_img.dimensions();
let mut total: u64 = ((score * 255.0) * (score * 255.0) * (w * h * 4) as f64) as u64;
for line in lines {
for x in line.x1..=line.x2 {
let mut pixel: &Rgba<u8> = origin_img.get_pixel(x as u32, line.y as u32);
let mut data = pixel.0;
let or = data[0] as i32;
let og = data[1] as i32;
let ob = data[2] as i32;
let oa = data[3] as i32;
pixel = before_img.get_pixel(x as u32, line.y as u32);
let composed_color = alpha_compose(pixel, &color);
data = pixel.0;
let br = data[0] as i32;
let bg = data[1] as i32;
let bb = data[2] as i32;
let ba = data[3] as i32;
let ar = composed_color[0] as i32;
let ag = composed_color[1] as i32;
let ab = composed_color[2] as i32;
let aa = composed_color[3] as i32;
let dr1 = or - br;
let dg1 = og - bg;
let db1 = ob - bb;
let da1 = oa - ba;
let dr2 = or - ar;
let dg2 = og - ag;
let db2 = ob - ab;
let da2 = oa - aa;
total += (dr2 * dr2 + dg2 * dg2 + db2 * db2 + da2 * da2) as u64;
total -= (dr1 * dr1 + dg1 * dg1 + db1 * db1 + da1 * da1) as u64;
}
}
((total as f64) / (w * h * 4) as f64).sqrt() / 255.0
}
pub fn parse_hex_color(hex: &str) -> Option<Rgba<u8>> {
let h = hex.trim_start_matches("#");
let mut r = 0;
let mut g = 0;
let mut b = 0;
let mut a = 255;
match h.len() {
0 => {
return None;
}
3 => {
r = u8::from_str_radix(h.get(0..1).unwrap(), 16).unwrap();
r = r << 4 | r;
g = u8::from_str_radix(h.get(1..2).unwrap(), 16).unwrap();
g = g << 4 | g;
b = u8::from_str_radix(h.get(2..3).unwrap(), 16).unwrap();
b = b << 4 | b;
}
4 => {
r = u8::from_str_radix(h.get(0..1).unwrap(), 16).unwrap();
r = r << 4 | r;
g = u8::from_str_radix(h.get(1..2).unwrap(), 16).unwrap();
g = g << 4 | g;
b = u8::from_str_radix(h.get(2..3).unwrap(), 16).unwrap();
b = b << 4 | b;
a = u8::from_str_radix(h.get(3..4).unwrap(), 16).unwrap();
a = a << 4 | a;
}
6 => {
r = u8::from_str_radix(h.get(0..2).unwrap(), 16).unwrap();
g = u8::from_str_radix(h.get(2..4).unwrap(), 16).unwrap();
b = u8::from_str_radix(h.get(4..6).unwrap(), 16).unwrap();
}
8 => {
r = u8::from_str_radix(h.get(0..2).unwrap(), 16).unwrap();
g = u8::from_str_radix(h.get(2..4).unwrap(), 16).unwrap();
b = u8::from_str_radix(h.get(4..6).unwrap(), 16).unwrap();
a = u8::from_str_radix(h.get(6..8).unwrap(), 16).unwrap();
}
_ => {
error!("invalid hex color: {}", hex);
unreachable!();
}
}
Some(Rgba([r, g, b, a]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_color() {
let width = 100;
let height = 100;
let mut img = image::ImageBuffer::new(width, height);
let current_img = image::ImageBuffer::new(width, height);
let color = Rgba([255, 0, 0, 255]);
let mut lines = Vec::new();
for y in 0..height {
for x in 0..width {
let pixel: &mut Rgba<u8> = img.get_pixel_mut(x as u32, y as u32);
pixel.0 = color.0;
}
lines.push(Scanline {
y,
x1: 0,
x2: width - 1,
});
}
let c = compute_color(&img, ¤t_img, &lines, 255);
assert_eq!(c, color);
}
#[test]
fn test_diff_partial() {
let width = 100;
let height = 100;
let mut img = image::ImageBuffer::new(width, height);
let before_img = image::ImageBuffer::new(width, height);
let color = Rgba([255, 0, 0, 255]);
let mut lines = Vec::new();
for y in 0..height {
for x in 0..width {
let pixel: &mut Rgba<u8> = img.get_pixel_mut(x as u32, y as u32);
pixel.0 = color.0;
}
lines.push(Scanline {
y,
x1: 0,
x2: width - 1,
});
}
let score = diff_partial(&img, &before_img, &before_img, &lines, 0.0);
assert_eq!(score, 0.0);
}
#[test]
fn test_diff_full() {
let width = 100;
let height = 100;
let mut img = image::ImageBuffer::new(width, height);
let current_img = image::ImageBuffer::new(width, height);
let color = Rgba([255, 0, 0, 255]);
let mut lines = Vec::new();
for y in 0..height {
for x in 0..width {
let pixel: &mut Rgba<u8> = img.get_pixel_mut(x as u32, y as u32);
pixel.0 = color.0;
}
lines.push(Scanline {
y,
x1: 0,
x2: width - 1,
});
}
let score1 = diff_full(&img, ¤t_img);
let score2 = diff_full(&img, &img);
assert_eq!(score1 > 0.0, true);
assert_eq!(score2, 0.0);
}
#[test]
fn test_parse_hex_color() {
let color1 = "#fff";
let color2 = "#ffff";
let color3 = "#ffffff";
let color4 = "#ffffffff";
let result = Some(Rgba([255, 255, 255, 255]));
assert_eq!(parse_hex_color(color1), result);
assert_eq!(parse_hex_color(color2), result);
assert_eq!(parse_hex_color(color3), result);
assert_eq!(parse_hex_color(color4), result);
}
}