diff --git a/out/dice.qoi b/out/dice.qoi index 9f6317c..ce84029 100644 Binary files a/out/dice.qoi and b/out/dice.qoi differ diff --git a/out/kodim23.qoi b/out/kodim23.qoi new file mode 100644 index 0000000..078918d Binary files /dev/null and b/out/kodim23.qoi differ diff --git a/src/common.rs b/src/common.rs index edc0863..a287501 100644 --- a/src/common.rs +++ b/src/common.rs @@ -74,25 +74,44 @@ pub fn hash ([r, g, b, a]: [u8; 4]) -> usize { (r as usize * 3 + g as usize * 5 + b as usize * 7 + a as usize * 11) % 64 } -pub fn calc_diff ([r0, g0, b0]: [u8; 3], [r1, g1, b1]: [u8; 3]) -> [i16; 3] { - [idiff!(r0, r1), idiff!(g0, g1), idiff!(b0, b1)] +pub fn calc_diff ([r0, g0, b0, _]: [i16; 4], [r1, g1, b1, _]: [i16; 4]) -> [i16; 3] { + [diff(r0, r1), diff(g0, g1), diff(b0, b1)] } -pub fn diff_ops (left: [u8; 3], right: [u8; 3]) -> Option { +pub fn diff_ops (left: [u8; 4], right: [u8; 4]) -> Option { + if left[3] != right[3] { return None; } + let left: [i16; 4] = left.map(i16::from); + let right: [i16; 4] = right.map(i16::from); + let diff = calc_diff(left, right); + if diff.iter().all(|d| (-2..2).contains(d)) { Some(Ops::Diff(diff.map(|e| e as i8))) - } else if (-32..32).contains(&diff[1]) && (-8..8).contains(&(diff[0] - diff[1])) && (-8..8).contains(&(diff[2] - diff[1])) { - Some(Ops::Luma([(diff[0] - diff[1]) as i8, diff[1] as i8, (diff[2] - diff[1]) as i8])) + } else if let Some(luma) = new_luma(diff) { + Some(luma) } else { None } } +pub fn new_luma ([r, g, b]: [i16; 3]) -> Option { + let drdg = r - g; + let dbdg = b - g; + if (-32..32).contains(&g) && (-8..8).contains(&drdg) && (-8..8).contains(&dbdg) { + return Some(Ops::Luma([drdg as i8, g as i8, dbdg as i8])); + } + return None; +} + +fn diff (lhs: i16, rhs: i16) -> i16 { + let mut diff = lhs - rhs; + if diff.abs() > 128 { + diff = 256 - rhs + lhs + 1 * match diff.is_negative() { + true => 1, + false => -1 + }; + } + diff +} + pub fn cast_with_bias (num: i8, bias: i8) -> u8 { (num + bias) as u8 } - -pub macro idiff { - ($t1: expr, $t2: expr) => { - $t1 as i16 - $t2 as i16 - } -} diff --git a/src/encoder.rs b/src/encoder.rs index 7330596..e929e6b 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,7 +1,5 @@ use std::mem::discriminant; - use itertools::Itertools; - use crate::common::*; @@ -9,11 +7,11 @@ impl Ops { // @TODO don't alloc here, get a mut reference and extend here fn format (self) -> Vec { // cast_diff - let cd = |diff: i8| cast_with_bias(diff, -2); + let cd = |diff: i8| cast_with_bias(diff, 2); // cast_luma_green - let clg = |diff: i8| cast_with_bias(diff, -32); + let clg = |diff: i8| cast_with_bias(diff, 32); // cast_luma_red_blue - let clrb = |diff: i8| cast_with_bias(diff, -8); + let clrb = |diff: i8| cast_with_bias(diff, 8); match self { Ops::Nop => vec![], @@ -45,6 +43,7 @@ fn encode_body (header: Header, data: &[u8]) -> Option> { let mut last = [0u8, 0, 0, 255]; let mut out = vec![]; let mut cur = Ops::Nop; + let mut counter = 0; for mut chunk in &data.iter().chunks(header.channels.num()) { let (&r, &g, &b) = chunk.next_tuple()?; @@ -75,7 +74,7 @@ fn encode_body (header: Header, data: &[u8]) -> Option> { // INDEX -> DIFF -> LUMA -> RGB(A) if known[hash] == rgba { cur = Ops::Index(hash); - } else if let Some(ops) = diff_ops([last[0], last[1], last[2]], [r, g, b]) { + } else if let Some(ops) = diff_ops([r, g, b, a], [last[0], last[1], last[2], last[3]]) { cur = ops; } else { cur = header.channels.ops_color(&rgba[..header.channels.num()])?; @@ -86,6 +85,11 @@ fn encode_body (header: Header, data: &[u8]) -> Option> { } } + // if an image buffer ends with a run, flush it as well + if discriminant(&cur) == discriminant(&Ops::Run(0)) { + out.extend_from_slice(&cur.format()); + } + Some(out) } @@ -129,4 +133,17 @@ mod test { let _write = std::fs::write("out/1kb.qoi", &encoded); } + + #[test] + fn kodim23 () { + let file = imageproc::image::io::Reader::open("in/kodim23.png").unwrap().decode().unwrap(); + let file = file.into_rgb8().into_raw(); + let header = Header { width: 768, height: 512, channels: Channels::RGB, colorspace: Colorspace::Srgb }; + + + let inst = Instant::now(); + let encoded = encode_full(header, &file).unwrap(); + println!("kodim23 encoding done in {} ms", inst.elapsed().as_millis()); + let _write = std::fs::write("out/kodim23.qoi", &encoded); + } }