Big batch of renames: strip qoi prefix everywhere
This commit is contained in:
parent
cb10dd3629
commit
328cfac40d
13 changed files with 73 additions and 72 deletions
|
|
@ -25,7 +25,7 @@ reference = [] # follows reference encoder implementation precisely, but may be
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["qoi-bench"]
|
members = ["bench"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
|
||||||
20
bench/Cargo.toml
Normal file
20
bench/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "qoi-bench"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
authors = ["Ivan Smirnov <rust@ivan.smirnov.ie>"]
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# internal
|
||||||
|
qoi-fast = { path = ".." }
|
||||||
|
# external
|
||||||
|
anyhow = "1.0"
|
||||||
|
libc = "0.2"
|
||||||
|
png = "0.17"
|
||||||
|
structopt = "0.3"
|
||||||
|
walkdir = "2.3"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0"
|
||||||
18
bench/build.rs
Normal file
18
bench/build.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
|
let out_src = out_dir.join("qoi.c");
|
||||||
|
fs::write(&out_src, "#include \"qoi.h\"\n").unwrap();
|
||||||
|
|
||||||
|
cc::Build::new()
|
||||||
|
.file(&out_src)
|
||||||
|
.include("../ext/qoi")
|
||||||
|
.define("QOI_NO_STDIO", None)
|
||||||
|
.define("QOI_IMPLEMENTATION", None)
|
||||||
|
.flag("-Wno-unsequenced")
|
||||||
|
.opt_level(3)
|
||||||
|
.compile("qoi");
|
||||||
|
}
|
||||||
413
bench/src/main.rs
Normal file
413
bench/src/main.rs
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::ptr;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use anyhow::{bail, ensure, Context, Result};
|
||||||
|
use libc::{c_int, c_void};
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
struct qoi_desc {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
channels: u8,
|
||||||
|
colorspace: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn qoi_encode(data: *const c_void, desc: *const qoi_desc, out_len: *mut c_int) -> *mut c_void;
|
||||||
|
fn qoi_decode(
|
||||||
|
data: *const c_void, size: c_int, desc: *mut qoi_desc, channels: c_int,
|
||||||
|
) -> *mut c_void;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn black_box<T>(dummy: T) -> T {
|
||||||
|
unsafe {
|
||||||
|
let ret = core::ptr::read_volatile(&dummy);
|
||||||
|
core::mem::forget(dummy);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeit<T>(func: impl Fn() -> T) -> (T, Duration) {
|
||||||
|
let t0 = Instant::now();
|
||||||
|
let out = func();
|
||||||
|
let t1 = Instant::now();
|
||||||
|
(black_box(out), t1 - t0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mean(v: &[f64]) -> f64 {
|
||||||
|
v.iter().sum::<f64>() / v.len() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_pngs(paths: &[PathBuf]) -> Result<Vec<PathBuf>> {
|
||||||
|
let is_png_file = |path: &PathBuf| {
|
||||||
|
path.is_file()
|
||||||
|
&& path.extension().unwrap_or_default().to_string_lossy().to_ascii_lowercase() == "png"
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut out = vec![];
|
||||||
|
for path in paths {
|
||||||
|
if is_png_file(path) {
|
||||||
|
out.push(path.clone());
|
||||||
|
} else if path.is_dir() {
|
||||||
|
out.extend(
|
||||||
|
WalkDir::new(path)
|
||||||
|
.follow_links(true)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.map(DirEntry::into_path)
|
||||||
|
.filter(is_png_file),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
bail!("path doesn't exist: {}", path.to_string_lossy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.sort_unstable();
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Image {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub channels: u8,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub const fn n_pixels(&self) -> usize {
|
||||||
|
(self.width as usize) * (self.height as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_png(filename: &Path) -> Result<Image> {
|
||||||
|
let decoder = png::Decoder::new(File::open(filename)?);
|
||||||
|
let mut reader = decoder.read_info()?;
|
||||||
|
let mut buf = vec![0; reader.output_buffer_size()];
|
||||||
|
let info = reader.next_frame(&mut buf)?;
|
||||||
|
let bytes = &buf[..info.buffer_size()];
|
||||||
|
let channels = info.color_type.samples() as u8;
|
||||||
|
ensure!(channels == 3 || channels == 4, "invalid channels: {}", channels);
|
||||||
|
Ok(Image { width: info.width, height: info.height, channels, data: bytes.to_vec() })
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Codec {
|
||||||
|
fn name() -> &'static str;
|
||||||
|
|
||||||
|
fn encode(img: &Image) -> Result<Vec<u8>>;
|
||||||
|
|
||||||
|
fn encode_bench(img: &Image) -> Result<()> {
|
||||||
|
let _ = black_box(Self::encode(img)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(data: &[u8], img: &Image) -> Result<Vec<u8>>;
|
||||||
|
|
||||||
|
fn decode_bench(data: &[u8], img: &Image) -> Result<()> {
|
||||||
|
let _ = black_box(Self::decode(data, img)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CodecQoiFast;
|
||||||
|
|
||||||
|
impl Codec for CodecQoiFast {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"qoi-fast"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(img: &Image) -> Result<Vec<u8>> {
|
||||||
|
Ok(qoi_fast::encode_to_vec(&img.data, img.width, img.height)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(data: &[u8], _img: &Image) -> Result<Vec<u8>> {
|
||||||
|
Ok(qoi_fast::decode_to_vec(data)?.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CodecQoiC;
|
||||||
|
|
||||||
|
impl CodecQoiC {
|
||||||
|
unsafe fn qoi_encode(img: &Image) -> Result<(*mut u8, usize)> {
|
||||||
|
let desc = qoi_desc {
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
channels: img.channels,
|
||||||
|
colorspace: 0,
|
||||||
|
};
|
||||||
|
let mut out_len: c_int = 0;
|
||||||
|
let ptr =
|
||||||
|
qoi_encode(img.data.as_ptr() as *const _, &desc as *const _, &mut out_len as *mut _);
|
||||||
|
ensure!(!ptr.is_null(), "error encoding with qoi-c");
|
||||||
|
Ok((ptr as _, out_len as _))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn qoi_decode(data: &[u8], img: &Image) -> Result<(*mut u8, qoi_desc)> {
|
||||||
|
let mut desc = qoi_desc::default();
|
||||||
|
let ptr =
|
||||||
|
qoi_decode(data.as_ptr() as _, data.len() as _, &mut desc as *mut _, img.channels as _);
|
||||||
|
ensure!(!ptr.is_null(), "error decoding with qoi-c");
|
||||||
|
Ok((ptr as _, desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Codec for CodecQoiC {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"qoi-c"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(img: &Image) -> Result<Vec<u8>> {
|
||||||
|
unsafe {
|
||||||
|
let (ptr, len) = Self::qoi_encode(img)?;
|
||||||
|
let mut vec = vec![0; len];
|
||||||
|
ptr::copy_nonoverlapping(ptr, vec.as_mut_ptr(), len);
|
||||||
|
libc::free(ptr as _);
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_bench(img: &Image) -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let (ptr, _) = Self::qoi_encode(img)?;
|
||||||
|
libc::free(ptr as _);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(data: &[u8], img: &Image) -> Result<Vec<u8>> {
|
||||||
|
unsafe {
|
||||||
|
let (ptr, desc) = Self::qoi_decode(data, img)?;
|
||||||
|
let len = desc.width as usize * desc.height as usize * desc.channels as usize;
|
||||||
|
let mut vec = vec![0; len];
|
||||||
|
ptr::copy_nonoverlapping(ptr, vec.as_mut_ptr(), len);
|
||||||
|
libc::free(ptr as _);
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_bench(data: &[u8], img: &Image) -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let (ptr, _) = Self::qoi_decode(data, img)?;
|
||||||
|
libc::free(ptr as _);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct BenchResult {
|
||||||
|
pub codec: String,
|
||||||
|
pub decode_sec: Vec<f64>,
|
||||||
|
pub encode_sec: Vec<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BenchResult {
|
||||||
|
pub fn new(codec: impl AsRef<str>, mut decode_sec: Vec<f64>, mut encode_sec: Vec<f64>) -> Self {
|
||||||
|
decode_sec.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||||
|
encode_sec.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||||
|
let codec = codec.as_ref().into();
|
||||||
|
Self { codec, decode_sec, encode_sec }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average_decode_sec(&self, use_median: bool) -> f64 {
|
||||||
|
if use_median {
|
||||||
|
self.decode_sec[self.decode_sec.len() / 2]
|
||||||
|
} else {
|
||||||
|
mean(&self.decode_sec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average_encode_sec(&self, use_median: bool) -> f64 {
|
||||||
|
if use_median {
|
||||||
|
self.encode_sec[self.encode_sec.len() / 2]
|
||||||
|
} else {
|
||||||
|
mean(&self.encode_sec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ImageBench {
|
||||||
|
results: Vec<BenchResult>,
|
||||||
|
n_pixels: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageBench {
|
||||||
|
pub fn new(img: &Image) -> Self {
|
||||||
|
Self { results: vec![], n_pixels: img.n_pixels() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<C: Codec>(&mut self, img: &Image, sec_allowed: f64) -> Result<()> {
|
||||||
|
let (encoded, t_encode) = timeit(|| C::encode(img));
|
||||||
|
let encoded = encoded?;
|
||||||
|
let (decoded, t_decode) = timeit(|| C::decode(&encoded, img));
|
||||||
|
let decoded = decoded?;
|
||||||
|
let roundtrip = decoded.as_slice() == img.data.as_slice();
|
||||||
|
if C::name() == "qoi-fast" {
|
||||||
|
assert!(roundtrip, "{}: decoded data doesn't roundtrip", C::name());
|
||||||
|
} else {
|
||||||
|
ensure!(roundtrip, "{}: decoded data doesn't roundtrip", C::name());
|
||||||
|
}
|
||||||
|
|
||||||
|
let n_encode = (sec_allowed / 2. / t_encode.as_secs_f64()).max(2.).ceil() as usize;
|
||||||
|
let mut encode_tm = Vec::with_capacity(n_encode);
|
||||||
|
for _ in 0..n_encode {
|
||||||
|
encode_tm.push(timeit(|| C::encode_bench(img)).1);
|
||||||
|
}
|
||||||
|
let encode_sec = encode_tm.iter().map(Duration::as_secs_f64).collect();
|
||||||
|
|
||||||
|
let n_decode = (sec_allowed / 2. / t_decode.as_secs_f64()).max(2.).ceil() as usize;
|
||||||
|
let mut decode_tm = Vec::with_capacity(n_decode);
|
||||||
|
for _ in 0..n_decode {
|
||||||
|
decode_tm.push(timeit(|| C::decode_bench(&encoded, img)).1);
|
||||||
|
}
|
||||||
|
let decode_sec = decode_tm.iter().map(Duration::as_secs_f64).collect();
|
||||||
|
|
||||||
|
self.results.push(BenchResult::new(C::name(), decode_sec, encode_sec));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report(&self, use_median: bool) {
|
||||||
|
let (w_name, w_col) = (11, 13);
|
||||||
|
print!("{:<w$}", "codec", w = w_name);
|
||||||
|
print!("{:>w$}", "decode:ms", w = w_col);
|
||||||
|
print!("{:>w$}", "encode:ms", w = w_col);
|
||||||
|
print!("{:>w$}", "decode:mp/s", w = w_col);
|
||||||
|
print!("{:>w$}", "encode:mp/s", w = w_col);
|
||||||
|
println!();
|
||||||
|
for r in &self.results {
|
||||||
|
let decode_sec = r.average_decode_sec(use_median);
|
||||||
|
let encode_sec = r.average_encode_sec(use_median);
|
||||||
|
let mpixels = self.n_pixels as f64 / 1e6;
|
||||||
|
let (decode_mpps, encode_mpps) = (mpixels / decode_sec, mpixels / encode_sec);
|
||||||
|
|
||||||
|
print!("{:<w$}", r.codec, w = w_name);
|
||||||
|
print!("{:>w$.2}", decode_sec * 1e3, w = w_col);
|
||||||
|
print!("{:>w$.2}", encode_sec * 1e3, w = w_col);
|
||||||
|
print!("{:>w$.1}", decode_mpps, w = w_col);
|
||||||
|
print!("{:>w$.1}", encode_mpps, w = w_col);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct BenchTotals {
|
||||||
|
results: Vec<ImageBench>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BenchTotals {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, b: &ImageBench) {
|
||||||
|
self.results.push(b.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report(&self, use_median: bool) {
|
||||||
|
if self.results.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let codec_names: Vec<_> = self.results[0].results.iter().map(|r| r.codec.clone()).collect();
|
||||||
|
let n_codecs = codec_names.len();
|
||||||
|
let (mut total_decode_sec, mut total_encode_sec, mut total_size) =
|
||||||
|
(vec![0.; n_codecs], vec![0.; n_codecs], 0);
|
||||||
|
for r in &self.results {
|
||||||
|
total_size += r.n_pixels;
|
||||||
|
for i in 0..n_codecs {
|
||||||
|
// sum of medians is not the median of sums, but w/e, good enough here
|
||||||
|
total_decode_sec[i] += r.results[i].average_decode_sec(use_median);
|
||||||
|
total_encode_sec[i] += r.results[i].average_encode_sec(use_median);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (w_name, w_col) = (11, 13);
|
||||||
|
println!("---");
|
||||||
|
println!(
|
||||||
|
"Overall results: ({} images, {:.1} MB):",
|
||||||
|
self.results.len(),
|
||||||
|
total_size as f64 / 1024. / 1024.
|
||||||
|
);
|
||||||
|
println!("---");
|
||||||
|
print!("{:<w$}", "codec", w = w_name);
|
||||||
|
print!("{:>w$}", "decode:ms", w = w_col);
|
||||||
|
print!("{:>w$}", "encode:ms", w = w_col);
|
||||||
|
print!("{:>w$}", "decode:mp/s", w = w_col);
|
||||||
|
print!("{:>w$}", "encode:mp/s", w = w_col);
|
||||||
|
println!();
|
||||||
|
for (i, codec_name) in codec_names.iter().enumerate() {
|
||||||
|
let decode_sec = total_decode_sec[i];
|
||||||
|
let encode_sec = total_encode_sec[i];
|
||||||
|
let mpixels = total_size as f64 / 1e6;
|
||||||
|
let (decode_mpps, encode_mpps) = (mpixels / decode_sec, mpixels / encode_sec);
|
||||||
|
|
||||||
|
print!("{:<w$}", codec_name, w = w_name);
|
||||||
|
print!("{:>w$.2}", decode_sec * 1e3, w = w_col);
|
||||||
|
print!("{:>w$.2}", encode_sec * 1e3, w = w_col);
|
||||||
|
print!("{:>w$.1}", decode_mpps, w = w_col);
|
||||||
|
print!("{:>w$.1}", encode_mpps, w = w_col);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_png(filename: &Path, seconds: f64, use_median: bool) -> Result<ImageBench> {
|
||||||
|
let f = filename.to_string_lossy();
|
||||||
|
let img = read_png(filename).context(format!("error reading PNG file: {}", f))?;
|
||||||
|
let size_kb = fs::metadata(filename)?.len() / 1024;
|
||||||
|
let mpixels = img.n_pixels() as f64 / 1e6;
|
||||||
|
println!(
|
||||||
|
"{} ({}x{}:{}, {} KB, {:.1}MP)",
|
||||||
|
f, img.width, img.height, img.channels, size_kb, mpixels
|
||||||
|
);
|
||||||
|
let mut bench = ImageBench::new(&img);
|
||||||
|
bench.run::<CodecQoiC>(&img, seconds)?;
|
||||||
|
bench.run::<CodecQoiFast>(&img, seconds)?;
|
||||||
|
bench.report(use_median);
|
||||||
|
Ok(bench)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_suite(files: &[PathBuf], seconds: f64, use_median: bool) -> Result<()> {
|
||||||
|
let mut totals = BenchTotals::new();
|
||||||
|
for file in files {
|
||||||
|
match bench_png(file, seconds, use_median) {
|
||||||
|
Ok(res) => totals.update(&res),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error processing `{}`: {}", file.display(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if totals.results.len() > 1 {
|
||||||
|
totals.report(use_median);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
struct Args {
|
||||||
|
/// Files or directories containing png images.
|
||||||
|
#[structopt(parse(from_os_str))]
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
/// Number of seconds allocated for each image/codec.
|
||||||
|
#[structopt(short, long, default_value = "1")]
|
||||||
|
seconds: f64,
|
||||||
|
/// Use average (mean) instead of the median.
|
||||||
|
#[structopt(short, long)]
|
||||||
|
average: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let args = <Args as StructOpt>::from_args();
|
||||||
|
ensure!(!args.paths.is_empty(), "no input paths given");
|
||||||
|
let files = find_pngs(&args.paths)?;
|
||||||
|
ensure!(!files.is_empty(), "no PNG files found in given paths");
|
||||||
|
bench_suite(&files, args.seconds, !args.average)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
1
bench/src/qoi.c
Normal file
1
bench/src/qoi.c
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "qoi.h"
|
||||||
|
|
@ -19,13 +19,13 @@ libfuzzer-sys = "0.4"
|
||||||
members = ["."]
|
members = ["."]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "qoi_encode"
|
name = "encode"
|
||||||
path = "fuzz_targets/qoi_encode.rs"
|
path = "fuzz_targets/encode.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "qoi_decode"
|
name = "decode"
|
||||||
path = "fuzz_targets/qoi_decode.rs"
|
path = "fuzz_targets/decode.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
|
||||||
42
fuzz/fuzz_targets/decode.rs
Normal file
42
fuzz/fuzz_targets/decode.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use qoi_fast::{decode_header, decode_to_vec, Channels, ColorSpace, Header};
|
||||||
|
|
||||||
|
fuzz_target!(|input: (u16, u16, bool, &[u8])| {
|
||||||
|
let (w, h, is_4, data) = input;
|
||||||
|
let (w, h) = (1 + w % 260, 1 + h % 260);
|
||||||
|
let channels = if is_4 { 4 } else { 3 };
|
||||||
|
|
||||||
|
let mut vec = vec![
|
||||||
|
b'q',
|
||||||
|
b'o',
|
||||||
|
b'i',
|
||||||
|
b'f',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(w >> 8) as u8,
|
||||||
|
(w & 0xff) as u8,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(h >> 8) as u8,
|
||||||
|
(h & 0xff) as u8,
|
||||||
|
channels,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
vec.extend(&*data);
|
||||||
|
vec.extend(&[0, 0, 0, 0, 0, 0, 0, 1]);
|
||||||
|
|
||||||
|
let header_expected = Header {
|
||||||
|
width: w as u32,
|
||||||
|
height: h as u32,
|
||||||
|
channels: Channels::try_from(channels).unwrap(),
|
||||||
|
colorspace: ColorSpace::try_from(0).unwrap(),
|
||||||
|
};
|
||||||
|
assert_eq!(decode_header(&vec).unwrap(), header_expected);
|
||||||
|
|
||||||
|
if let Ok((header, out)) = decode_to_vec(&vec) {
|
||||||
|
assert_eq!(header, header_expected);
|
||||||
|
assert_eq!(out.len(), header.n_bytes());
|
||||||
|
}
|
||||||
|
});
|
||||||
25
fuzz/fuzz_targets/encode.rs
Normal file
25
fuzz/fuzz_targets/encode.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use qoi_fast::{encode_size_limit, encode_to_vec};
|
||||||
|
|
||||||
|
fuzz_target!(|input: (bool, u8, &[u8])| {
|
||||||
|
let (is_4, w_frac, data) = input;
|
||||||
|
let channels = if is_4 { 4 } else { 3 };
|
||||||
|
let size = data.len();
|
||||||
|
let n_pixels = size / channels as usize;
|
||||||
|
let (w, h) = if n_pixels == 0 {
|
||||||
|
(0, 0)
|
||||||
|
} else {
|
||||||
|
let w = ((n_pixels * (1 + w_frac as usize)) / 256).max(1);
|
||||||
|
let h = n_pixels / w;
|
||||||
|
(w, h)
|
||||||
|
};
|
||||||
|
let out = encode_to_vec(&data[..(w * h * channels as usize)], w as u32, h as u32);
|
||||||
|
if w * h != 0 {
|
||||||
|
let out = out.unwrap();
|
||||||
|
assert!(out.len() <= encode_size_limit(w as u32, h as u32, channels));
|
||||||
|
} else {
|
||||||
|
assert!(out.is_err());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -22,9 +22,7 @@ const QOI_OP_DIFF_END: u8 = QOI_OP_DIFF | 0x3f;
|
||||||
const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f;
|
const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn qoi_decode_impl_slice<const N: usize, const RGBA: bool>(
|
fn decode_impl_slice<const N: usize, const RGBA: bool>(data: &[u8], out: &mut [u8]) -> Result<usize>
|
||||||
data: &[u8], out: &mut [u8],
|
|
||||||
) -> Result<usize>
|
|
||||||
where
|
where
|
||||||
Pixel<N>: SupportedChannels,
|
Pixel<N>: SupportedChannels,
|
||||||
[u8; N]: Pod,
|
[u8; N]: Pod,
|
||||||
|
|
@ -92,14 +90,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn qoi_decode_impl_slice_all(
|
fn decode_impl_slice_all(
|
||||||
data: &[u8], out: &mut [u8], channels: u8, src_channels: u8,
|
data: &[u8], out: &mut [u8], channels: u8, src_channels: u8,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
match (channels, src_channels) {
|
match (channels, src_channels) {
|
||||||
(3, 3) => qoi_decode_impl_slice::<3, false>(data, out),
|
(3, 3) => decode_impl_slice::<3, false>(data, out),
|
||||||
(3, 4) => qoi_decode_impl_slice::<3, true>(data, out),
|
(3, 4) => decode_impl_slice::<3, true>(data, out),
|
||||||
(4, 3) => qoi_decode_impl_slice::<4, false>(data, out),
|
(4, 3) => decode_impl_slice::<4, false>(data, out),
|
||||||
(4, 4) => qoi_decode_impl_slice::<4, true>(data, out),
|
(4, 4) => decode_impl_slice::<4, true>(data, out),
|
||||||
_ => {
|
_ => {
|
||||||
cold();
|
cold();
|
||||||
Err(Error::InvalidChannels { channels })
|
Err(Error::InvalidChannels { channels })
|
||||||
|
|
@ -108,33 +106,33 @@ fn qoi_decode_impl_slice_all(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> {
|
pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> {
|
||||||
let mut decoder = QoiDecoder::new(&data)?;
|
let mut decoder = Decoder::new(&data)?;
|
||||||
decoder.decode_to_buf(buf)?;
|
decoder.decode_to_buf(buf)?;
|
||||||
Ok(*decoder.header())
|
Ok(*decoder.header())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> {
|
pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> {
|
||||||
let mut decoder = QoiDecoder::new(&data)?;
|
let mut decoder = Decoder::new(&data)?;
|
||||||
let out = decoder.decode_to_vec()?;
|
let out = decoder.decode_to_vec()?;
|
||||||
Ok((*decoder.header(), out))
|
Ok((*decoder.header(), out))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
|
pub fn decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
|
||||||
Header::decode(data)
|
Header::decode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QoiDecoder<'a> {
|
pub struct Decoder<'a> {
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
header: Header,
|
header: Header,
|
||||||
channels: Channels,
|
channels: Channels,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> QoiDecoder<'a> {
|
impl<'a> Decoder<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> {
|
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> {
|
||||||
let data = data.as_ref();
|
let data = data.as_ref();
|
||||||
|
|
@ -171,7 +169,7 @@ impl<'a> QoiDecoder<'a> {
|
||||||
if unlikely(buf.len() < size) {
|
if unlikely(buf.len() < size) {
|
||||||
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
|
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
|
||||||
}
|
}
|
||||||
let n_read = qoi_decode_impl_slice_all(
|
let n_read = decode_impl_slice_all(
|
||||||
self.data,
|
self.data,
|
||||||
buf,
|
buf,
|
||||||
self.channels.as_u8(),
|
self.channels.as_u8(),
|
||||||
|
|
@ -191,7 +189,7 @@ impl<'a> QoiDecoder<'a> {
|
||||||
|
|
||||||
#[cfg(any(feature = "std"))]
|
#[cfg(any(feature = "std"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn qoi_decode_impl_stream<R: Read, const N: usize, const RGBA: bool>(
|
fn decode_impl_stream<R: Read, const N: usize, const RGBA: bool>(
|
||||||
data: &mut R, out: &mut [u8],
|
data: &mut R, out: &mut [u8],
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
|
|
@ -261,14 +259,14 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn qoi_decode_impl_stream_all<R: Read>(
|
fn decode_impl_stream_all<R: Read>(
|
||||||
data: &mut R, out: &mut [u8], channels: u8, src_channels: u8,
|
data: &mut R, out: &mut [u8], channels: u8, src_channels: u8,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match (channels, src_channels) {
|
match (channels, src_channels) {
|
||||||
(3, 3) => qoi_decode_impl_stream::<_, 3, false>(data, out),
|
(3, 3) => decode_impl_stream::<_, 3, false>(data, out),
|
||||||
(3, 4) => qoi_decode_impl_stream::<_, 3, true>(data, out),
|
(3, 4) => decode_impl_stream::<_, 3, true>(data, out),
|
||||||
(4, 3) => qoi_decode_impl_stream::<_, 4, false>(data, out),
|
(4, 3) => decode_impl_stream::<_, 4, false>(data, out),
|
||||||
(4, 4) => qoi_decode_impl_stream::<_, 4, true>(data, out),
|
(4, 4) => decode_impl_stream::<_, 4, true>(data, out),
|
||||||
_ => {
|
_ => {
|
||||||
cold();
|
cold();
|
||||||
Err(Error::InvalidChannels { channels })
|
Err(Error::InvalidChannels { channels })
|
||||||
|
|
@ -277,14 +275,14 @@ fn qoi_decode_impl_stream_all<R: Read>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub struct QoiStreamDecoder<R> {
|
pub struct StreamDecoder<R> {
|
||||||
reader: R,
|
reader: R,
|
||||||
header: Header,
|
header: Header,
|
||||||
channels: Channels,
|
channels: Channels,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl<R: Read> QoiStreamDecoder<R> {
|
impl<R: Read> StreamDecoder<R> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(mut reader: R) -> Result<Self> {
|
pub fn new(mut reader: R) -> Result<Self> {
|
||||||
let mut b = [0; QOI_HEADER_SIZE];
|
let mut b = [0; QOI_HEADER_SIZE];
|
||||||
|
|
@ -325,7 +323,7 @@ impl<R: Read> QoiStreamDecoder<R> {
|
||||||
if unlikely(buf.len() < size) {
|
if unlikely(buf.len() < size) {
|
||||||
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
|
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
|
||||||
}
|
}
|
||||||
qoi_decode_impl_stream_all(
|
decode_impl_stream_all(
|
||||||
&mut self.reader,
|
&mut self.reader,
|
||||||
buf,
|
buf,
|
||||||
self.channels.as_u8(),
|
self.channels.as_u8(),
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use crate::utils::GenericWriter;
|
||||||
use crate::utils::{unlikely, BytesMut, Writer};
|
use crate::utils::{unlikely, BytesMut, Writer};
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation, unused_assignments)]
|
#[allow(clippy::cast_possible_truncation, unused_assignments)]
|
||||||
fn qoi_encode_impl<W: Writer, const N: usize>(mut buf: W, data: &[u8]) -> Result<usize>
|
fn encode_impl<W: Writer, const N: usize>(mut buf: W, data: &[u8]) -> Result<usize>
|
||||||
where
|
where
|
||||||
Pixel<N>: SupportedChannels,
|
Pixel<N>: SupportedChannels,
|
||||||
{
|
{
|
||||||
|
|
@ -70,15 +70,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn qoi_encode_impl_all<W: Writer>(out: W, data: &[u8], channels: Channels) -> Result<usize> {
|
fn encode_impl_all<W: Writer>(out: W, data: &[u8], channels: Channels) -> Result<usize> {
|
||||||
match channels {
|
match channels {
|
||||||
Channels::Rgb => qoi_encode_impl::<_, 3>(out, data),
|
Channels::Rgb => encode_impl::<_, 3>(out, data),
|
||||||
Channels::Rgba => qoi_encode_impl::<_, 4>(out, data),
|
Channels::Rgba => encode_impl::<_, 4>(out, data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encoded_size_limit(width: u32, height: u32, channels: impl Into<u8>) -> usize {
|
pub fn encode_size_limit(width: u32, height: u32, channels: impl Into<u8>) -> usize {
|
||||||
let (width, height) = (width as usize, height as usize);
|
let (width, height) = (width as usize, height as usize);
|
||||||
let n_pixels = width.saturating_mul(height);
|
let n_pixels = width.saturating_mul(height);
|
||||||
QOI_HEADER_SIZE
|
QOI_HEADER_SIZE
|
||||||
|
|
@ -88,24 +88,24 @@ pub fn encoded_size_limit(width: u32, height: u32, channels: impl Into<u8>) -> u
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_encode_to_buf(
|
pub fn encode_to_buf(
|
||||||
buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
|
buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
QoiEncoder::new(&data, width, height)?.encode_to_buf(buf)
|
Encoder::new(&data, width, height)?.encode_to_buf(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
|
pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
|
||||||
QoiEncoder::new(&data, width, height)?.encode_to_vec()
|
Encoder::new(&data, width, height)?.encode_to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct QoiEncoder<'a> {
|
pub struct Encoder<'a> {
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
header: Header,
|
header: Header,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> QoiEncoder<'a> {
|
impl<'a> Encoder<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
|
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
|
||||||
|
|
@ -138,27 +138,27 @@ impl<'a> QoiEncoder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encoded_size_limit(&self) -> usize {
|
pub fn encode_size_limit(&self) -> usize {
|
||||||
self.header.encoded_size_limit()
|
self.header.encode_size_limit()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
|
pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
|
||||||
let buf = buf.as_mut();
|
let buf = buf.as_mut();
|
||||||
let size_required = self.encoded_size_limit();
|
let size_required = self.encode_size_limit();
|
||||||
if unlikely(buf.len() < size_required) {
|
if unlikely(buf.len() < size_required) {
|
||||||
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
|
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
|
||||||
}
|
}
|
||||||
let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); // can't panic
|
let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); // can't panic
|
||||||
head.copy_from_slice(&self.header.encode());
|
head.copy_from_slice(&self.header.encode());
|
||||||
let n_written = qoi_encode_impl_all(BytesMut::new(tail), self.data, self.header.channels)?;
|
let n_written = encode_impl_all(BytesMut::new(tail), self.data, self.header.channels)?;
|
||||||
Ok(QOI_HEADER_SIZE + n_written)
|
Ok(QOI_HEADER_SIZE + n_written)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
|
pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
|
||||||
let mut out = vec![0_u8; self.encoded_size_limit()];
|
let mut out = vec![0_u8; self.encode_size_limit()];
|
||||||
let size = self.encode_to_buf(&mut out)?;
|
let size = self.encode_to_buf(&mut out)?;
|
||||||
out.truncate(size);
|
out.truncate(size);
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -169,7 +169,7 @@ impl<'a> QoiEncoder<'a> {
|
||||||
pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {
|
pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {
|
||||||
writer.write_all(&self.header.encode())?;
|
writer.write_all(&self.header.encode())?;
|
||||||
let n_written =
|
let n_written =
|
||||||
qoi_encode_impl_all(GenericWriter::new(writer), self.data, self.header.channels)?;
|
encode_impl_all(GenericWriter::new(writer), self.data, self.header.channels)?;
|
||||||
Ok(n_written + QOI_HEADER_SIZE)
|
Ok(n_written + QOI_HEADER_SIZE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use core::convert::TryInto;
|
||||||
use bytemuck::cast_slice;
|
use bytemuck::cast_slice;
|
||||||
|
|
||||||
use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX};
|
use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX};
|
||||||
use crate::encoded_size_limit;
|
use crate::encode_size_limit;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::types::{Channels, ColorSpace};
|
use crate::types::{Channels, ColorSpace};
|
||||||
use crate::utils::unlikely;
|
use crate::utils::unlikely;
|
||||||
|
|
@ -112,7 +112,7 @@ impl Header {
|
||||||
///
|
///
|
||||||
/// This comes useful when pre-allocating a buffer to encode the image into.
|
/// This comes useful when pre-allocating a buffer to encode the image into.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encoded_size_limit(&self) -> usize {
|
pub fn encode_size_limit(&self) -> usize {
|
||||||
encoded_size_limit(self.width, self.height, self.channels)
|
encode_size_limit(self.width, self.height, self.channels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -26,14 +26,14 @@ mod utils;
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||||
pub use crate::decode::qoi_decode_to_vec;
|
pub use crate::decode::decode_to_vec;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use crate::decode::QoiStreamDecoder;
|
pub use crate::decode::StreamDecoder;
|
||||||
pub use crate::decode::{qoi_decode_header, qoi_decode_to_buf, QoiDecoder};
|
pub use crate::decode::{decode_header, decode_to_buf, Decoder};
|
||||||
|
|
||||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||||
pub use crate::encode::qoi_encode_to_vec;
|
pub use crate::encode::encode_to_vec;
|
||||||
pub use crate::encode::{encoded_size_limit, qoi_encode_to_buf, QoiEncoder};
|
pub use crate::encode::{encode_size_limit, encode_to_buf, Encoder};
|
||||||
pub use crate::error::{Error, Result};
|
pub use crate::error::{Error, Result};
|
||||||
pub use crate::header::Header;
|
pub use crate::header::Header;
|
||||||
pub use crate::types::{Channels, ColorSpace};
|
pub use crate::types::{Channels, ColorSpace};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use anyhow::{bail, Result};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use qoi_fast::{qoi_decode_to_vec, qoi_encode_to_vec};
|
use qoi_fast::{decode_to_vec, encode_to_vec};
|
||||||
|
|
||||||
fn find_qoi_png_pairs(root: impl AsRef<Path>) -> Vec<(PathBuf, PathBuf)> {
|
fn find_qoi_png_pairs(root: impl AsRef<Path>) -> Vec<(PathBuf, PathBuf)> {
|
||||||
let root = root.as_ref();
|
let root = root.as_ref();
|
||||||
|
|
@ -96,7 +96,7 @@ fn test_reference_images() -> Result<()> {
|
||||||
let png_name = png_path.file_name().unwrap_or_default().to_string_lossy();
|
let png_name = png_path.file_name().unwrap_or_default().to_string_lossy();
|
||||||
let img = Image::from_png(png_path)?;
|
let img = Image::from_png(png_path)?;
|
||||||
println!("{} {} {} {}", png_name, img.width, img.height, img.channels);
|
println!("{} {} {} {}", png_name, img.width, img.height, img.channels);
|
||||||
let encoded = qoi_encode_to_vec(&img.data, img.width, img.height)?;
|
let encoded = encode_to_vec(&img.data, img.width, img.height)?;
|
||||||
let expected = fs::read(qoi_path)?;
|
let expected = fs::read(qoi_path)?;
|
||||||
assert_eq!(encoded.len(), expected.len()); // this should match regardless
|
assert_eq!(encoded.len(), expected.len()); // this should match regardless
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
|
|
@ -104,8 +104,8 @@ fn test_reference_images() -> Result<()> {
|
||||||
compare_slices(&png_name, "encoding", &encoded, &expected)?;
|
compare_slices(&png_name, "encoding", &encoded, &expected)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (_header1, decoded1) = qoi_decode_to_vec(&encoded)?;
|
let (_header1, decoded1) = decode_to_vec(&encoded)?;
|
||||||
let (_header2, decoded2) = qoi_decode_to_vec(&expected)?;
|
let (_header2, decoded2) = decode_to_vec(&expected)?;
|
||||||
compare_slices(&png_name, "decoding [1]", &decoded1, &img.data)?;
|
compare_slices(&png_name, "decoding [1]", &decoded1, &img.data)?;
|
||||||
compare_slices(&png_name, "decoding [2]", &decoded2, &img.data)?;
|
compare_slices(&png_name, "decoding [2]", &decoded2, &img.data)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue