Initial commit with simplify function that RLE scales images.

This commit is contained in:
Bill Thiede 2020-06-25 18:23:04 -07:00
commit bb496e61a5
5 changed files with 552 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

334
Cargo.lock generated Normal file
View File

@ -0,0 +1,334 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bytemuck"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "color_quant"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
"maybe-uninit",
]
[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"lazy_static",
"maybe-uninit",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
dependencies = [
"cfg-if",
"crossbeam-utils",
"maybe-uninit",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "deflate"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "gif"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af"
dependencies = [
"color_quant",
"lzw",
]
[[package]]
name = "hermit-abi"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
dependencies = [
"libc",
]
[[package]]
name = "image"
version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4"
dependencies = [
"bytemuck",
"byteorder",
"gif",
"jpeg-decoder",
"num-iter",
"num-rational",
"num-traits",
"png",
"scoped_threadpool",
"tiff",
]
[[package]]
name = "jpeg-decoder"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704"
dependencies = [
"byteorder",
"rayon",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
"cfg-if",
]
[[package]]
name = "lzw"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memoffset"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "num-integer"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "perler"
version = "0.1.0"
dependencies = [
"image",
"lazy_static",
"log",
]
[[package]]
name = "png"
version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ccdd66f6fe4b2433b07e4728e9a013e43233120427046e93ceb709c3a439bf"
dependencies = [
"bitflags",
"crc32fast",
"deflate",
"miniz_oxide",
]
[[package]]
name = "rayon"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
dependencies = [
"crossbeam-deque",
"crossbeam-queue",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "tiff"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f"
dependencies = [
"byteorder",
"lzw",
"miniz_oxide",
]

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "perler"
version = "0.1.0"
authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.23.6"
log = "0.4.8"
[dev-dependencies]
lazy_static = "1.4.0"

202
src/lib.rs Normal file
View File

@ -0,0 +1,202 @@
use image::{DynamicImage, GenericImage, GenericImageView};
use log::debug;
pub fn simplify(input: &DynamicImage) -> DynamicImage {
let (w, h) = input.dimensions();
// The dimensions method returns the images width and height.
debug!("dimensions {}x{}", w, h);
let mut smallest_run = usize::MAX;
// Search for longest vertical runs
for y in 0..h {
let mut last_pix = input.get_pixel(0, y);
let mut run = 1;
for x in 1..w {
let pix = input.get_pixel(x, y);
debug!("xy {}x{} pix {:?} last pix {:?}", x, y, &pix.0, &last_pix.0);
if last_pix == pix {
run += 1;
} else {
smallest_run = smallest_run.min(run);
debug!(
"xy {}x{} pix {:?} last pix {:?} run {}",
x, y, &pix.0, &last_pix.0, &run
);
run = 1;
}
last_pix = pix;
}
smallest_run = smallest_run.min(run);
}
debug!("Smallest vertical run {}", smallest_run);
// Search for longest vertical runs
for x in 0..w {
let mut last_pix = input.get_pixel(x, 0);
let mut run = 1;
for y in 1..h {
let pix = input.get_pixel(x, y);
debug!("xy {}x{} pix {:?} last pix {:?}", x, y, &pix.0, &last_pix.0);
if last_pix == pix {
run += 1;
} else {
smallest_run = smallest_run.min(run);
debug!(
"xy {}x{} pix {:?} last pix {:?} run {}",
x, y, &pix.0, &last_pix.0, &run
);
run = 1;
}
last_pix = pix;
}
smallest_run = smallest_run.min(run);
}
debug!("Smallest run {}", smallest_run);
let scale = smallest_run as u32;
let s_w = w / scale;
let s_h = h / scale;
let mut out = DynamicImage::new_rgba8(s_w, s_h);
debug!("Creating image {}x{}", s_w, s_h);
for y in 0..s_h {
for x in 0..s_w {
let x_orig = x * scale;
let y_orig = y * scale;
let p = input.get_pixel(x_orig, y_orig);
debug!(
"getting {}x{} {:?} and storing in {}x{}",
x_orig, y_orig, p.0, x, y
);
out.put_pixel(x, y, p);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use image::{GenericImage, Rgba};
use lazy_static::lazy_static;
lazy_static! {
static ref WHITE: Rgba<u8> = [255, 255, 255, 255].into();
static ref BLACK: Rgba<u8> = [0, 0, 0, 255].into();
}
struct DebugImage<'r>(&'r DynamicImage);
use std::fmt;
impl<'r> fmt::Display for DebugImage<'r> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let img = &self.0;
let (w, h) = img.dimensions();
for y in 0..h {
let p = img.get_pixel(0, y);
write!(f, "[{:^3}", p.0[0])?;
for x in 1..w {
let p = img.get_pixel(x, y);
write!(f, " {:^3}", p.0[0])?;
}
write!(f, "]\n")?;
}
Ok(())
}
}
fn mk_horiz(w: u32, h: u32, scale: u32) -> DynamicImage {
let mut img = DynamicImage::new_rgba8(w * scale, h * scale);
for y in 0..h * scale {
for x in 0..w * scale {
if (y / scale) % 2 == 0 {
img.put_pixel(x, y, *WHITE);
} else {
img.put_pixel(x, y, *BLACK);
}
}
}
img
}
fn mk_vert(w: u32, h: u32, scale: u32) -> DynamicImage {
let mut img = DynamicImage::new_rgba8(w * scale, h * scale);
for y in 0..h * scale {
for x in 0..w * scale {
if (x / scale) % 2 == 0 {
img.put_pixel(x, y, *WHITE);
} else {
img.put_pixel(x, y, *BLACK);
}
}
}
img
}
fn mk_check(w: u32, h: u32, scale: u32) -> DynamicImage {
let mut img = DynamicImage::new_rgba8(w * scale, h * scale);
for y in 0..h * scale {
for x in 0..w * scale {
if (x / scale) % 2 == 0 || (y / scale) % 2 == 0 {
img.put_pixel(x, y, *WHITE);
} else {
img.put_pixel(x, y, *BLACK);
}
}
}
img
}
fn compare_images(lhs: DynamicImage, rhs: DynamicImage) {
let lhs = &lhs.to_rgba();
let rhs = &rhs.to_rgba();
let l_dim = lhs.dimensions();
let r_dim = rhs.dimensions();
assert_eq!(
l_dim, r_dim,
"image sizes don't match {:?} != {:?}",
l_dim, r_dim
);
// Based on https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio#Definition
let mut mse: [i64; 3] = [0, 0, 0];
let (w, _) = l_dim;
let w = w as usize;
for (c, (l, r)) in lhs.pixels().zip(rhs.pixels()).enumerate() {
let image::Rgba(l_pix) = l;
let image::Rgba(r_pix) = r;
{
for i in 0..3 {
let d = l_pix[i] as i64 - r_pix[i] as i64;
let d2 = d * d;
mse[i] += d2;
}
}
let x = c / w;
let y = c % w;
if l_pix != r_pix {
lhs.save("/tmp/lhs.png").expect("Failed to save lhs image");
rhs.save("/tmp/rhs.png").expect("Failed to save rhs image");
assert_eq!(l_pix, r_pix, "{:?} != {:?} @ {} x {} ", l_pix, r_pix, x, y);
}
}
}
#[test]
fn test_simplify() {
for (input, want) in vec![
(mk_horiz(3, 3, 5), mk_horiz(3, 3, 1)),
(mk_vert(3, 3, 5), mk_vert(3, 3, 1)),
(mk_check(3, 3, 5), mk_check(3, 3, 1)),
(mk_horiz(3, 9, 5), mk_horiz(3, 9, 1)),
(mk_vert(9, 3, 5), mk_vert(9, 3, 1)),
] {
let got = simplify(&input);
println!("input:\n{}", DebugImage(&input));
println!("want:\n{}", DebugImage(&want));
println!("got:\n{}", DebugImage(&got));
compare_images(want, got);
}
}
}

1
src/main.rs Normal file
View File

@ -0,0 +1 @@
fn main() {}