#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Image {
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub has_alpha: bool,
pub data: Vec<u16>,
}
impl Image {
pub fn filled(width: u32, height: u32, bit_depth: u8, rgba: [u16; 4]) -> Self {
let data = rgba.repeat(width as usize * height as usize);
Self {
width,
height,
bit_depth,
has_alpha: false,
data,
}
}
pub fn max_value(&self) -> u16 {
((1u32 << self.bit_depth) - 1) as u16
}
pub fn to_rgba8(&self) -> Vec<u8> {
let max = u32::from(self.max_value());
self.data
.iter()
.map(|&v| ((u32::from(v) * 255 + max / 2) / max) as u8)
.collect()
}
pub fn to_rgb8(&self) -> Vec<u8> {
self.to_rgba8()
.chunks_exact(4)
.flat_map(|p| [p[0], p[1], p[2]])
.collect()
}
fn pixel(&self, x: u32, y: u32) -> [u16; 4] {
let i = (y as usize * self.width as usize + x as usize) * 4;
[
self.data[i],
self.data[i + 1],
self.data[i + 2],
self.data[i + 3],
]
}
fn remap(&self, width: u32, height: u32, src: impl Fn(u32, u32) -> (u32, u32)) -> Self {
const TILE: u32 = 32;
let mut data = vec![0u16; width as usize * height as usize * 4];
for ty in (0..height).step_by(TILE as usize) {
for tx in (0..width).step_by(TILE as usize) {
for y in ty..(ty + TILE).min(height) {
for x in tx..(tx + TILE).min(width) {
let (sx, sy) = src(x, y);
let i = (sy as usize * self.width as usize + sx as usize) * 4;
let o = (y as usize * width as usize + x as usize) * 4;
data[o..o + 4].copy_from_slice(&self.data[i..i + 4]);
}
}
}
}
Self {
width,
height,
data,
..*self
}
}
pub fn crop(&self, x: u32, y: u32, w: u32, h: u32) -> Self {
let x = x.min(self.width);
let y = y.min(self.height);
let w = w.min(self.width - x);
let h = h.min(self.height - y);
self.remap(w, h, |px, py| (px + x, py + y))
}
pub fn rotate_ccw(&self, quarters: u8) -> Self {
let (w, h) = (self.width, self.height);
match quarters % 4 {
1 => self.remap(h, w, |x, y| (w - 1 - y, x)),
2 => self.remap(w, h, |x, y| (w - 1 - x, h - 1 - y)),
3 => self.remap(h, w, |x, y| (y, h - 1 - x)),
_ => self.clone(),
}
}
pub fn mirror(&self, top_bottom: bool) -> Self {
let (w, h) = (self.width, self.height);
if top_bottom {
self.remap(w, h, |x, y| (x, h - 1 - y))
} else {
self.remap(w, h, |x, y| (w - 1 - x, y))
}
}
pub fn draw(&mut self, src: &Image, x0: i64, y0: i64) {
let max = u32::from(src.max_value());
let dst_max = u32::from(self.max_value());
let same_depth = src.bit_depth == self.bit_depth;
let shift_to = move |v: u16| -> u16 {
if same_depth {
v
} else {
((u32::from(v) * dst_max + max / 2) / max) as u16
}
};
for sy in 0..src.height {
let y = y0 + i64::from(sy);
if y < 0 || y >= i64::from(self.height) {
continue;
}
for sx in 0..src.width {
let x = x0 + i64::from(sx);
if x < 0 || x >= i64::from(self.width) {
continue;
}
let s = src.pixel(sx, sy);
let i = (y as usize * self.width as usize + x as usize) * 4;
let a = u32::from(s[3]);
for (d, &sc) in self.data[i..i + 3].iter_mut().zip(&s[..3]) {
let (fg, bg) = (u32::from(shift_to(sc)), u32::from(*d));
*d = ((fg * a + bg * (max - a) + max / 2) / max) as u16;
}
if src.has_alpha {
let ab = u32::from(self.data[i + 3]);
let out_a = a + ab * (max - a) / max;
self.data[i + 3] = shift_to(out_a.min(max) as u16);
self.has_alpha = true;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn numbered(w: u32, h: u32) -> Image {
let data = (0..w * h).flat_map(|i| [i as u16, 0, 0, 255]).collect();
Image {
width: w,
height: h,
bit_depth: 8,
has_alpha: false,
data,
}
}
fn reds(img: &Image) -> Vec<u16> {
img.data.chunks(4).map(|p| p[0]).collect()
}
#[test]
fn rotations_and_mirrors() {
let img = numbered(3, 2);
assert_eq!(reds(&img.rotate_ccw(1)), vec![2, 5, 1, 4, 0, 3]);
assert_eq!(reds(&img.rotate_ccw(2)), vec![5, 4, 3, 2, 1, 0]);
assert_eq!(reds(&img.rotate_ccw(3)), vec![3, 0, 4, 1, 5, 2]);
assert_eq!(reds(&img.mirror(false)), vec![2, 1, 0, 5, 4, 3]);
assert_eq!(reds(&img.mirror(true)), vec![3, 4, 5, 0, 1, 2]);
assert_eq!(reds(&img.crop(1, 1, 5, 5)), vec![4, 5]);
}
#[test]
fn draw_with_alpha() {
let mut canvas = Image::filled(2, 1, 8, [0, 0, 0, 255]);
let mut src = Image::filled(1, 1, 8, [255, 255, 255, 128]);
src.has_alpha = true;
canvas.draw(&src, 1, 0);
assert_eq!(&canvas.data[4..7], &[128, 128, 128]);
assert_eq!(&canvas.data[0..3], &[0, 0, 0]);
canvas.draw(&src, -5, 0); }
}