2021-12-30 10:19:02 +00:00
|
|
|
use std::cmp::Ordering;
|
2021-12-02 16:02:30 +00:00
|
|
|
use std::fs::{self, File};
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
|
|
|
|
use anyhow::{bail, ensure, Context, Result};
|
2022-01-05 14:10:42 +00:00
|
|
|
use bytemuck::cast_slice;
|
2022-01-05 14:13:15 +00:00
|
|
|
use c_vec::CVec;
|
2021-12-02 16:02:30 +00:00
|
|
|
use structopt::StructOpt;
|
|
|
|
use walkdir::{DirEntry, WalkDir};
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
2022-01-01 18:55:51 +00:00
|
|
|
out.sort_unstable();
|
2021-12-02 16:02:30 +00:00
|
|
|
Ok(out)
|
|
|
|
}
|
|
|
|
|
2022-01-05 14:10:42 +00:00
|
|
|
fn grayscale_to_rgb(buf: &[u8]) -> Vec<u8> {
|
|
|
|
let mut out = Vec::with_capacity(buf.len() * 3);
|
|
|
|
for &px in buf {
|
|
|
|
for _ in 0..3 {
|
|
|
|
out.push(px);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out
|
|
|
|
}
|
|
|
|
|
|
|
|
fn grayscale_alpha_to_rgba(buf: &[u8]) -> Vec<u8> {
|
|
|
|
let mut out = Vec::with_capacity(buf.len() * 4);
|
|
|
|
for &px in cast_slice::<_, [u8; 2]>(buf) {
|
|
|
|
for _ in 0..3 {
|
|
|
|
out.push(px[0]);
|
|
|
|
}
|
|
|
|
out.push(px[1])
|
|
|
|
}
|
|
|
|
out
|
|
|
|
}
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
#[derive(Clone)]
|
2021-12-02 16:02:30 +00:00
|
|
|
struct Image {
|
|
|
|
pub width: u32,
|
|
|
|
pub height: u32,
|
|
|
|
pub channels: u8,
|
|
|
|
pub data: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Image {
|
2022-01-05 14:10:42 +00:00
|
|
|
fn read_png(filename: &Path) -> Result<Self> {
|
|
|
|
let mut decoder = png::Decoder::new(File::open(filename)?);
|
|
|
|
let transformations = png::Transformations::normalize_to_color8();
|
|
|
|
decoder.set_transformations(transformations);
|
|
|
|
let mut reader = decoder.read_info()?;
|
|
|
|
let mut whole_buf = vec![0; reader.output_buffer_size()];
|
|
|
|
let info = reader.next_frame(&mut whole_buf)?;
|
|
|
|
let buf = &whole_buf[..info.buffer_size()];
|
|
|
|
ensure!(info.bit_depth == png::BitDepth::Eight, "invalid bit depth: {:?}", info.bit_depth);
|
|
|
|
let (channels, data) = match info.color_type {
|
|
|
|
png::ColorType::Grayscale => {
|
|
|
|
// png crate doesn't support GRAY_TO_RGB transformation yet
|
|
|
|
(3, grayscale_to_rgb(buf))
|
|
|
|
}
|
|
|
|
png::ColorType::GrayscaleAlpha => {
|
|
|
|
// same as above, but with alpha channel
|
|
|
|
(4, grayscale_alpha_to_rgba(buf))
|
|
|
|
}
|
|
|
|
color_type => {
|
|
|
|
let channels = color_type.samples();
|
|
|
|
ensure!(channels == 3 || channels == 4, "invalid channels: {}", channels);
|
|
|
|
(channels as u8, buf[..info.buffer_size()].to_vec())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(Self { width: info.width, height: info.height, channels, data })
|
|
|
|
}
|
|
|
|
|
2021-12-02 16:02:30 +00:00
|
|
|
pub const fn n_pixels(&self) -> usize {
|
|
|
|
(self.width as usize) * (self.height as usize)
|
|
|
|
}
|
2022-01-06 00:08:38 +00:00
|
|
|
|
|
|
|
pub const fn n_bytes(&self) -> usize {
|
|
|
|
self.n_pixels() * (self.channels as usize)
|
|
|
|
}
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
trait Codec {
|
2022-01-05 14:13:15 +00:00
|
|
|
type Output: AsRef<[u8]>;
|
2021-12-02 16:02:30 +00:00
|
|
|
|
2022-01-05 14:13:15 +00:00
|
|
|
fn name() -> &'static str;
|
|
|
|
fn encode(img: &Image) -> Result<Self::Output>;
|
|
|
|
fn decode(data: &[u8], img: &Image) -> Result<Self::Output>;
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct CodecQoiFast;
|
|
|
|
|
|
|
|
impl Codec for CodecQoiFast {
|
2022-01-05 14:13:15 +00:00
|
|
|
type Output = Vec<u8>;
|
|
|
|
|
2021-12-02 16:02:30 +00:00
|
|
|
fn name() -> &'static str {
|
|
|
|
"qoi-fast"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn encode(img: &Image) -> Result<Vec<u8>> {
|
2022-01-03 18:40:24 +00:00
|
|
|
Ok(qoi_fast::encode_to_vec(&img.data, img.width, img.height)?)
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
2022-01-01 21:02:08 +00:00
|
|
|
fn decode(data: &[u8], _img: &Image) -> Result<Vec<u8>> {
|
2022-01-03 18:40:24 +00:00
|
|
|
Ok(qoi_fast::decode_to_vec(data)?.1)
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct CodecQoiC;
|
|
|
|
|
|
|
|
impl Codec for CodecQoiC {
|
2022-01-05 14:13:15 +00:00
|
|
|
type Output = CVec<u8>;
|
|
|
|
|
2021-12-02 16:02:30 +00:00
|
|
|
fn name() -> &'static str {
|
2022-01-05 14:16:41 +00:00
|
|
|
"qoi.h"
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
2022-01-05 14:13:15 +00:00
|
|
|
fn encode(img: &Image) -> Result<CVec<u8>> {
|
|
|
|
libqoi::qoi_encode(&img.data, img.width, img.height, img.channels)
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
2022-01-05 14:13:15 +00:00
|
|
|
fn decode(data: &[u8], img: &Image) -> Result<CVec<u8>> {
|
|
|
|
Ok(libqoi::qoi_decode(data, img.channels)?.1)
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
#[derive(Clone)]
|
2021-12-02 16:02:30 +00:00
|
|
|
struct BenchResult {
|
|
|
|
pub codec: String,
|
|
|
|
pub decode_sec: Vec<f64>,
|
2021-12-30 10:19:02 +00:00
|
|
|
pub encode_sec: Vec<f64>,
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
impl BenchResult {
|
|
|
|
pub fn new(codec: impl AsRef<str>, mut decode_sec: Vec<f64>, mut encode_sec: Vec<f64>) -> Self {
|
2021-12-31 10:37:56 +00:00
|
|
|
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));
|
2021-12-30 10:19:02 +00:00
|
|
|
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)]
|
2021-12-02 16:02:30 +00:00
|
|
|
struct ImageBench {
|
|
|
|
results: Vec<BenchResult>,
|
2021-12-30 10:19:02 +00:00
|
|
|
n_pixels: usize,
|
2022-01-06 00:08:38 +00:00
|
|
|
n_bytes: usize,
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ImageBench {
|
2021-12-30 10:19:02 +00:00
|
|
|
pub fn new(img: &Image) -> Self {
|
2022-01-06 00:08:38 +00:00
|
|
|
Self { results: vec![], n_pixels: img.n_pixels(), n_bytes: img.n_bytes() }
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
pub fn run<C: Codec>(&mut self, img: &Image, sec_allowed: f64) -> Result<()> {
|
|
|
|
let (encoded, t_encode) = timeit(|| C::encode(img));
|
2021-12-02 16:02:30 +00:00
|
|
|
let encoded = encoded?;
|
2022-01-05 14:13:15 +00:00
|
|
|
let (decoded, t_decode) = timeit(|| C::decode(encoded.as_ref(), img));
|
2021-12-02 16:02:30 +00:00
|
|
|
let decoded = decoded?;
|
2022-01-05 14:13:15 +00:00
|
|
|
let roundtrip = decoded.as_ref() == img.data.as_slice();
|
2022-01-03 14:43:28 +00:00
|
|
|
if C::name() == "qoi-fast" {
|
|
|
|
assert!(roundtrip, "{}: decoded data doesn't roundtrip", C::name());
|
|
|
|
} else {
|
|
|
|
ensure!(roundtrip, "{}: decoded data doesn't roundtrip", C::name());
|
|
|
|
}
|
2021-12-02 16:02:30 +00:00
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
let n_encode = (sec_allowed / 2. / t_encode.as_secs_f64()).max(2.).ceil() as usize;
|
2021-12-02 16:02:30 +00:00
|
|
|
let mut encode_tm = Vec::with_capacity(n_encode);
|
|
|
|
for _ in 0..n_encode {
|
2022-01-05 14:13:15 +00:00
|
|
|
encode_tm.push(timeit(|| C::encode(img)).1);
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
let encode_sec = encode_tm.iter().map(Duration::as_secs_f64).collect();
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
let n_decode = (sec_allowed / 2. / t_decode.as_secs_f64()).max(2.).ceil() as usize;
|
2021-12-02 16:02:30 +00:00
|
|
|
let mut decode_tm = Vec::with_capacity(n_decode);
|
|
|
|
for _ in 0..n_decode {
|
2022-01-05 14:13:15 +00:00
|
|
|
decode_tm.push(timeit(|| C::decode(encoded.as_ref(), img)).1);
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
let decode_sec = decode_tm.iter().map(Duration::as_secs_f64).collect();
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
self.results.push(BenchResult::new(C::name(), decode_sec, encode_sec));
|
2021-12-02 16:02:30 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn report(&self, use_median: bool) {
|
2022-01-06 00:08:38 +00:00
|
|
|
let (w_name, w_col) = (9, 13);
|
2022-01-05 14:16:41 +00:00
|
|
|
print!("{:<w$}", "", w = w_name);
|
2021-12-02 16:02:30 +00:00
|
|
|
print!("{:>w$}", "decode:ms", w = w_col);
|
|
|
|
print!("{:>w$}", "encode:ms", w = w_col);
|
2022-01-06 00:08:38 +00:00
|
|
|
print!("{:>w$}", "decode:Mp/s", w = w_col);
|
|
|
|
print!("{:>w$}", "encode:Mp/s", w = w_col);
|
|
|
|
print!("{:>w$}", "decode:MB/s", w = w_col);
|
|
|
|
print!("{:>w$}", "encode:MB/s", w = w_col);
|
2021-12-02 16:02:30 +00:00
|
|
|
println!();
|
|
|
|
for r in &self.results {
|
2021-12-30 10:19:02 +00:00
|
|
|
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;
|
2021-12-02 16:02:30 +00:00
|
|
|
let (decode_mpps, encode_mpps) = (mpixels / decode_sec, mpixels / encode_sec);
|
2022-01-06 00:08:38 +00:00
|
|
|
let mbytes = self.n_bytes as f64 / 1024. / 1024.;
|
|
|
|
let (decode_mbps, encode_mbps) = (mbytes / decode_sec, mbytes / encode_sec);
|
2021-12-02 16:02:30 +00:00
|
|
|
|
|
|
|
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);
|
2022-01-06 00:08:38 +00:00
|
|
|
print!("{:>w$.1}", decode_mbps, w = w_col);
|
|
|
|
print!("{:>w$.1}", encode_mbps, w = w_col);
|
2021-12-02 16:02:30 +00:00
|
|
|
println!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-30 10:19:02 +00:00
|
|
|
#[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())
|
|
|
|
}
|
|
|
|
|
2022-01-06 00:08:38 +00:00
|
|
|
pub fn report(&self, use_median: bool, fancy: bool) {
|
2021-12-30 10:19:02 +00:00
|
|
|
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();
|
2022-01-06 00:08:38 +00:00
|
|
|
let (mut total_decode_sec, mut total_encode_sec, mut n_pixels_total, mut n_bytes_total) =
|
|
|
|
(vec![0.; n_codecs], vec![0.; n_codecs], 0, 0);
|
2021-12-30 10:19:02 +00:00
|
|
|
for r in &self.results {
|
2022-01-06 00:08:38 +00:00
|
|
|
n_pixels_total += r.n_pixels;
|
|
|
|
n_bytes_total += r.n_bytes;
|
2021-12-30 10:19:02 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2022-01-06 00:08:38 +00:00
|
|
|
let mpixels = n_pixels_total as f64 / 1e6;
|
|
|
|
let mbytes = n_bytes_total as f64 / 1024. / 1024.;
|
2021-12-30 10:19:02 +00:00
|
|
|
|
|
|
|
println!("---");
|
|
|
|
println!(
|
2022-01-06 00:08:38 +00:00
|
|
|
"Overall results: ({} images, {:.2} MB raw, {:.2} MP):",
|
2021-12-30 10:19:02 +00:00
|
|
|
self.results.len(),
|
2022-01-06 00:08:38 +00:00
|
|
|
mbytes,
|
|
|
|
mpixels
|
2021-12-30 10:19:02 +00:00
|
|
|
);
|
2022-01-06 00:08:38 +00:00
|
|
|
if fancy {
|
|
|
|
let (w_header, w_col) = (14, 12);
|
|
|
|
let n = n_codecs;
|
|
|
|
let print_sep = |s| print!("{}{:->w$}", s, "", w = w_header + n * w_col);
|
|
|
|
print_sep("");
|
|
|
|
print!("\n{:<w$}", "", w = w_header);
|
|
|
|
(0..n).for_each(|i| print!("{:>w$}", codec_names[i], w = w_col));
|
|
|
|
print_sep("\n");
|
|
|
|
// print!("\n{:<w$}", " ms", w = w_header);
|
|
|
|
// (0..n).for_each(|i| print!("{:>w$.2}", total_decode_sec[i] * 1e3, w = w_col));
|
|
|
|
print!("\n{:<w$}", "decode Mp/s", w = w_header);
|
|
|
|
(0..n).for_each(|i| print!("{:>w$.1}", mpixels / total_decode_sec[i], w = w_col));
|
|
|
|
print!("\n{:<w$}", " MB/s", w = w_header);
|
|
|
|
(0..n).for_each(|i| print!("{:>w$.1}", mbytes / total_decode_sec[i], w = w_col));
|
|
|
|
print_sep("\n");
|
|
|
|
// print!("\n{:<w$}", " ms", w = w_header);
|
|
|
|
// (0..n).for_each(|i| print!("{:>w$.2}", total_encode_sec[i] * 1e3, w = w_col));
|
|
|
|
print!("\n{:<w$}", "encode Mp/s", w = w_header);
|
|
|
|
(0..n).for_each(|i| print!("{:>w$.1}", mpixels / total_encode_sec[i], w = w_col));
|
|
|
|
print!("\n{:<w$}", " MB/s", w = w_header);
|
|
|
|
(0..n).for_each(|i| print!("{:>w$.1}", mbytes / total_encode_sec[i], w = w_col));
|
|
|
|
print_sep("\n");
|
2021-12-30 10:19:02 +00:00
|
|
|
println!();
|
2022-01-06 00:08:38 +00:00
|
|
|
} else {
|
|
|
|
let (w_name, w_col) = (9, 13);
|
|
|
|
println!("---");
|
|
|
|
print!("{:<w$}", "", 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);
|
|
|
|
print!("{:>w$}", "decode:MB/s", w = w_col);
|
|
|
|
print!("{:>w$}", "encode:MB/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 (decode_mpps, encode_mpps) = (mpixels / decode_sec, mpixels / encode_sec);
|
|
|
|
let (decode_mbps, encode_mbps) = (mbytes / decode_sec, mbytes / 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);
|
|
|
|
print!("{:>w$.1}", decode_mbps, w = w_col);
|
|
|
|
print!("{:>w$.1}", encode_mbps, w = w_col);
|
|
|
|
println!();
|
|
|
|
}
|
2021-12-30 10:19:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn bench_png(filename: &Path, seconds: f64, use_median: bool) -> Result<ImageBench> {
|
2021-12-02 16:02:30 +00:00
|
|
|
let f = filename.to_string_lossy();
|
2022-01-05 14:10:42 +00:00
|
|
|
let img = Image::read_png(filename).context(format!("error reading PNG file: {}", f))?;
|
2022-01-06 00:08:38 +00:00
|
|
|
let size_png_kb = fs::metadata(filename)?.len() / 1024;
|
|
|
|
let size_mb_raw = img.n_bytes() as f64 / 1024. / 1024.;
|
2021-12-02 16:02:30 +00:00
|
|
|
let mpixels = img.n_pixels() as f64 / 1e6;
|
|
|
|
println!(
|
2022-01-06 00:08:38 +00:00
|
|
|
"{} ({}x{}:{}, {} KB png, {:.2} MB raw, {:.2} MP)",
|
|
|
|
f, img.width, img.height, img.channels, size_png_kb, size_mb_raw, mpixels
|
2021-12-02 16:02:30 +00:00
|
|
|
);
|
2021-12-30 10:19:02 +00:00
|
|
|
let mut bench = ImageBench::new(&img);
|
|
|
|
bench.run::<CodecQoiC>(&img, seconds)?;
|
|
|
|
bench.run::<CodecQoiFast>(&img, seconds)?;
|
|
|
|
bench.report(use_median);
|
|
|
|
Ok(bench)
|
|
|
|
}
|
|
|
|
|
2022-01-06 00:08:38 +00:00
|
|
|
fn bench_suite(files: &[PathBuf], seconds: f64, use_median: bool, fancy: bool) -> Result<()> {
|
2021-12-30 10:19:02 +00:00
|
|
|
let mut totals = BenchTotals::new();
|
|
|
|
for file in files {
|
2022-01-02 15:58:54 +00:00
|
|
|
match bench_png(file, seconds, use_median) {
|
|
|
|
Ok(res) => totals.update(&res),
|
2022-01-05 14:16:41 +00:00
|
|
|
Err(err) => eprintln!("{:?}", err),
|
2022-01-02 15:58:54 +00:00
|
|
|
}
|
2021-12-30 10:19:02 +00:00
|
|
|
}
|
2022-01-03 12:18:50 +00:00
|
|
|
if totals.results.len() > 1 {
|
2022-01-06 00:08:38 +00:00
|
|
|
totals.report(use_median, fancy);
|
2022-01-03 12:18:50 +00:00
|
|
|
}
|
2021-12-02 16:02:30 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, StructOpt)]
|
|
|
|
struct Args {
|
2021-12-30 10:19:02 +00:00
|
|
|
/// Files or directories containing png images.
|
2021-12-02 16:02:30 +00:00
|
|
|
#[structopt(parse(from_os_str))]
|
|
|
|
paths: Vec<PathBuf>,
|
2021-12-30 10:19:02 +00:00
|
|
|
/// 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,
|
2022-01-06 00:08:38 +00:00
|
|
|
/// Simple totals, no fancy tables.
|
|
|
|
#[structopt(short, long)]
|
|
|
|
simple: bool,
|
2021-12-02 16:02:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2022-01-06 00:08:38 +00:00
|
|
|
bench_suite(&files, args.seconds, !args.average, !args.simple)?;
|
2021-12-02 16:02:30 +00:00
|
|
|
Ok(())
|
|
|
|
}
|