diff --git a/Cargo.lock b/Cargo.lock index 3851729..891be45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,6 +744,7 @@ dependencies = [ name = "qoi2" version = "0.1.0" dependencies = [ + "anyhow", "bytemuck", "imageproc", "itertools 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 9775555..7077817 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.86" bytemuck = "1.16.1" imageproc = "0.25.0" itertools = "0.13.0" diff --git a/src/encoder.rs b/src/encoder.rs index b5d42af..045a4aa 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,11 +1,12 @@ use std::mem::discriminant; + use itertools::Itertools; use crate::common::*; +// @TODO replace Options with custom Errors impl Ops { - // @TODO don't alloc here, get a mut reference and extend here - fn format (self) -> Vec { + fn write_encoded (self, buf: &mut Vec) { // cast_diff let cd = |diff: i8| cast_with_bias(diff, 2); // cast_luma_green @@ -14,13 +15,13 @@ impl Ops { let clrb = |diff: i8| cast_with_bias(diff, 8); match self { - Ops::Nop => vec![], - Ops::Rgb([r, g, b]) => vec![254, r, g, b], - Ops::Rgba([r, g, b, a]) => vec![255, r, g, b, a], - Ops::Index(idx) => vec![idx as u8], - Ops::Run(len) => vec![(3 << 6) | (len - 1)], - Ops::Diff([dr, dg, db]) => vec![(1 << 6) | ((cd(dr) << 4) | (cd(dg) << 2) | cd(db)) ], - Ops::Luma([dr, dg, db]) => vec![(2 << 6) | clg(dg), (clrb(dr) << 4) | clrb(db)], + Ops::Nop => (), + Ops::Rgb([r, g, b]) => buf.extend_from_slice(&[254, r, g, b]), + Ops::Rgba([r, g, b, a]) => buf.extend_from_slice(&[255, r, g, b, a]), + Ops::Index(idx) => buf.push(idx as u8), + Ops::Run(len) => buf.push((3 << 6) | (len - 1)), + Ops::Diff([dr, dg, db]) => buf.push((1 << 6) | ((cd(dr) << 4) | (cd(dg) << 2) | cd(db))), + Ops::Luma([dr, dg, db]) => buf.extend_from_slice(&[(2 << 6) | clg(dg), (clrb(dr) << 4) | clrb(db)]), } } } @@ -57,8 +58,9 @@ fn encode_body (header: Header, data: &[u8]) -> Option> { Ops::Run(run) if run < 62 => cur = Ops::Run(run + 1), // if run is full, commit it to out vec, and start a new one Ops::Run(run) if run >= 62 => { + cur = Ops::Run(62); // clamp for safety; + cur.write_encoded(&mut out); cur = Ops::Run(1); - out.extend_from_slice(&Ops::Run(62).format()); }, // otherwise (default case, when encountering a match for the first time) start a run _ => cur = Ops::Run(1), @@ -66,19 +68,19 @@ fn encode_body (header: Header, data: &[u8]) -> Option> { } else { // if current Op is Run (meaning we need to and it since there's no match) if discriminant(&cur) == discriminant(&Ops::Run(0)) { - out.extend_from_slice(&cur.format()); + cur.write_encoded(&mut out); } let hash = hash(rgba); // INDEX -> DIFF -> LUMA -> RGB(A) if known[hash] == rgba { cur = Ops::Index(hash); - } else if let Some(ops) = diff_ops([r, g, b, a], [last[0], last[1], last[2], last[3]]) { + } else if let Some(ops) = diff_ops([r, g, b, a], last) { cur = ops; } else { cur = header.channels.ops_color(&rgba[..header.channels.num()])?; } - out.extend_from_slice(&cur.format()); + cur.write_encoded(&mut out); last = rgba; known[hash] = rgba; } @@ -86,7 +88,7 @@ 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()); + cur.write_encoded(&mut out); } Some(out)