Skip to content

Commit c8139cb

Browse files
authored
Merge pull request #20 from aldanor/tests-and-stream-bench
Add stream tests for op-run edge case; add stream mode to bench
2 parents 3a1ce94 + c262afa commit c8139cb

File tree

2 files changed

+78
-34
lines changed

2 files changed

+78
-34
lines changed

bench/src/main.rs

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::cmp::Ordering;
22
use std::fs::{self, File};
3+
use std::io::Cursor;
34
use std::path::{Path, PathBuf};
45
use std::time::{Duration, Instant};
56

67
use anyhow::{bail, ensure, Context, Result};
78
use bytemuck::cast_slice;
89
use c_vec::CVec;
10+
use qoi::{Decoder, Encoder};
911
use structopt::StructOpt;
1012
use walkdir::{DirEntry, WalkDir};
1113

@@ -124,26 +126,45 @@ impl Image {
124126
trait Codec {
125127
type Output: AsRef<[u8]>;
126128

127-
fn name() -> &'static str;
128-
fn encode(img: &Image) -> Result<Self::Output>;
129-
fn decode(data: &[u8], img: &Image) -> Result<Self::Output>;
129+
fn name(&self) -> &'static str;
130+
fn encode(&self, img: &Image) -> Result<Self::Output>;
131+
fn decode(&self, data: &[u8], img: &Image) -> Result<Self::Output>;
130132
}
131133

132-
struct CodecQoiRust;
134+
struct CodecQoiRust {
135+
pub stream: bool,
136+
}
133137

134138
impl Codec for CodecQoiRust {
135139
type Output = Vec<u8>;
136140

137-
fn name() -> &'static str {
138-
"qoi-rust"
141+
fn name(&self) -> &'static str {
142+
if self.stream {
143+
"qoi-rust[stream]"
144+
} else {
145+
"qoi-rust"
146+
}
139147
}
140148

141-
fn encode(img: &Image) -> Result<Vec<u8>> {
142-
Ok(qoi::encode_to_vec(&img.data, img.width, img.height)?)
149+
fn encode(&self, img: &Image) -> Result<Vec<u8>> {
150+
if self.stream {
151+
let mut stream = Vec::new();
152+
let encoder = Encoder::new(&img.data, img.width, img.height)?;
153+
encoder.encode_to_stream(&mut stream)?;
154+
Ok(stream)
155+
} else {
156+
Ok(qoi::encode_to_vec(&img.data, img.width, img.height)?)
157+
}
143158
}
144159

145-
fn decode(data: &[u8], _img: &Image) -> Result<Vec<u8>> {
146-
Ok(qoi::decode_to_vec(data)?.1)
160+
fn decode(&self, data: &[u8], _img: &Image) -> Result<Vec<u8>> {
161+
if self.stream {
162+
let stream = Cursor::new(data);
163+
let mut decoder = Decoder::from_stream(stream)?;
164+
Ok(decoder.decode_to_vec()?)
165+
} else {
166+
Ok(qoi::decode_to_vec(data)?.1)
167+
}
147168
}
148169
}
149170

@@ -152,15 +173,15 @@ struct CodecQoiC;
152173
impl Codec for CodecQoiC {
153174
type Output = CVec<u8>;
154175

155-
fn name() -> &'static str {
176+
fn name(&self) -> &'static str {
156177
"qoi.h"
157178
}
158179

159-
fn encode(img: &Image) -> Result<CVec<u8>> {
180+
fn encode(&self, img: &Image) -> Result<CVec<u8>> {
160181
libqoi::qoi_encode(&img.data, img.width, img.height, img.channels)
161182
}
162183

163-
fn decode(data: &[u8], img: &Image) -> Result<CVec<u8>> {
184+
fn decode(&self, data: &[u8], img: &Image) -> Result<CVec<u8>> {
164185
Ok(libqoi::qoi_decode(data, img.channels)?.1)
165186
}
166187
}
@@ -209,33 +230,33 @@ impl ImageBench {
209230
Self { results: vec![], n_pixels: img.n_pixels(), n_bytes: img.n_bytes() }
210231
}
211232

212-
pub fn run<C: Codec>(&mut self, img: &Image, sec_allowed: f64) -> Result<()> {
213-
let (encoded, t_encode) = timeit(|| C::encode(img));
233+
pub fn run<C: Codec>(&mut self, codec: &C, img: &Image, sec_allowed: f64) -> Result<()> {
234+
let (encoded, t_encode) = timeit(|| codec.encode(img));
214235
let encoded = encoded?;
215-
let (decoded, t_decode) = timeit(|| C::decode(encoded.as_ref(), img));
236+
let (decoded, t_decode) = timeit(|| codec.decode(encoded.as_ref(), img));
216237
let decoded = decoded?;
217238
let roundtrip = decoded.as_ref() == img.data.as_slice();
218-
if C::name() == "qoi-rust" {
219-
assert!(roundtrip, "{}: decoded data doesn't roundtrip", C::name());
239+
if codec.name() == "qoi-rust" {
240+
assert!(roundtrip, "{}: decoded data doesn't roundtrip", codec.name());
220241
} else {
221-
ensure!(roundtrip, "{}: decoded data doesn't roundtrip", C::name());
242+
ensure!(roundtrip, "{}: decoded data doesn't roundtrip", codec.name());
222243
}
223244

224245
let n_encode = (sec_allowed / 2. / t_encode.as_secs_f64()).max(2.).ceil() as usize;
225246
let mut encode_tm = Vec::with_capacity(n_encode);
226247
for _ in 0..n_encode {
227-
encode_tm.push(timeit(|| C::encode(img)).1);
248+
encode_tm.push(timeit(|| codec.encode(img)).1);
228249
}
229250
let encode_sec = encode_tm.iter().map(Duration::as_secs_f64).collect();
230251

231252
let n_decode = (sec_allowed / 2. / t_decode.as_secs_f64()).max(2.).ceil() as usize;
232253
let mut decode_tm = Vec::with_capacity(n_decode);
233254
for _ in 0..n_decode {
234-
decode_tm.push(timeit(|| C::decode(encoded.as_ref(), img)).1);
255+
decode_tm.push(timeit(|| codec.decode(encoded.as_ref(), img)).1);
235256
}
236257
let decode_sec = decode_tm.iter().map(Duration::as_secs_f64).collect();
237258

238-
self.results.push(BenchResult::new(C::name(), decode_sec, encode_sec));
259+
self.results.push(BenchResult::new(codec.name(), decode_sec, encode_sec));
239260
Ok(())
240261
}
241262

@@ -362,7 +383,7 @@ impl BenchTotals {
362383
}
363384
}
364385

365-
fn bench_png(filename: &Path, seconds: f64, use_median: bool) -> Result<ImageBench> {
386+
fn bench_png(filename: &Path, seconds: f64, use_median: bool, stream: bool) -> Result<ImageBench> {
366387
let f = filename.to_string_lossy();
367388
let img = Image::read_png(filename).context(format!("error reading PNG file: {}", f))?;
368389
let size_png_kb = fs::metadata(filename)?.len() / 1024;
@@ -373,16 +394,18 @@ fn bench_png(filename: &Path, seconds: f64, use_median: bool) -> Result<ImageBen
373394
f, img.width, img.height, img.channels, size_png_kb, size_mb_raw, mpixels
374395
);
375396
let mut bench = ImageBench::new(&img);
376-
bench.run::<CodecQoiC>(&img, seconds)?;
377-
bench.run::<CodecQoiRust>(&img, seconds)?;
397+
bench.run(&CodecQoiC, &img, seconds)?;
398+
bench.run(&CodecQoiRust { stream }, &img, seconds)?;
378399
bench.report(use_median);
379400
Ok(bench)
380401
}
381402

382-
fn bench_suite(files: &[PathBuf], seconds: f64, use_median: bool, fancy: bool) -> Result<()> {
403+
fn bench_suite(
404+
files: &[PathBuf], seconds: f64, use_median: bool, fancy: bool, stream: bool,
405+
) -> Result<()> {
383406
let mut totals = BenchTotals::new();
384407
for file in files {
385-
match bench_png(file, seconds, use_median) {
408+
match bench_png(file, seconds, use_median, stream) {
386409
Ok(res) => totals.update(&res),
387410
Err(err) => eprintln!("{:?}", err),
388411
}
@@ -405,15 +428,18 @@ struct Args {
405428
#[structopt(short, long)]
406429
average: bool,
407430
/// Simple totals, no fancy tables.
408-
#[structopt(short, long)]
431+
#[structopt(long)]
409432
simple: bool,
433+
/// Use stream API for qoi-rust.
434+
#[structopt(long)]
435+
stream: bool,
410436
}
411437

412438
fn main() -> Result<()> {
413439
let args = <Args as StructOpt>::from_args();
414440
ensure!(!args.paths.is_empty(), "no input paths given");
415441
let files = find_pngs(&args.paths)?;
416442
ensure!(!files.is_empty(), "no PNG files found in given paths");
417-
bench_suite(&files, args.seconds, !args.average, !args.simple)?;
443+
bench_suite(&files, args.seconds, !args.average, !args.simple, args.stream)?;
418444
Ok(())
419445
}

tests/test_misc.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use qoi::{
2-
consts::{QOI_OP_RGB, QOI_OP_RUN, QOI_OP_INDEX},
2+
consts::{QOI_OP_INDEX, QOI_OP_RGB, QOI_OP_RUN},
33
decode_to_vec, Channels, ColorSpace, Header, Result,
44
};
55

@@ -17,8 +17,17 @@ fn test_start_with_qoi_op_run() -> Result<()> {
1717
qoi_data.extend([QOI_OP_RUN | 1, QOI_OP_RGB, 10, 20, 30]);
1818
qoi_data.extend([0; 7]);
1919
qoi_data.push(1);
20-
let (_, decoded) = decode_to_vec(&qoi_data)?;
21-
assert_eq!(decoded, vec![0, 0, 0, 255, 0, 0, 0, 255, 10, 20, 30, 255]);
20+
let expected = vec![0, 0, 0, 255, 0, 0, 0, 255, 10, 20, 30, 255];
21+
22+
assert_eq!(decode_to_vec(&qoi_data)?.1, expected);
23+
24+
#[cfg(feature = "std")]
25+
{
26+
let stream = std::io::Cursor::new(&qoi_data);
27+
let mut decoder = qoi::Decoder::from_stream(stream)?;
28+
assert_eq!(decoder.decode_to_vec()?, expected);
29+
}
30+
2231
Ok(())
2332
}
2433

@@ -29,8 +38,17 @@ fn test_start_with_qoi_op_run_and_use_index() -> Result<()> {
2938
qoi_data.extend([QOI_OP_RUN | 1, QOI_OP_RGB, 10, 20, 30, QOI_OP_INDEX | 53]);
3039
qoi_data.extend([0; 7]);
3140
qoi_data.push(1);
32-
let (_, decoded) = decode_to_vec(&qoi_data)?;
33-
assert_eq!(decoded, vec![0, 0, 0, 255, 0, 0, 0, 255, 10, 20, 30, 255, 0, 0, 0, 255]);
41+
let expected = vec![0, 0, 0, 255, 0, 0, 0, 255, 10, 20, 30, 255, 0, 0, 0, 255];
42+
43+
assert_eq!(decode_to_vec(&qoi_data)?.1, expected);
44+
45+
#[cfg(feature = "std")]
46+
{
47+
let stream = std::io::Cursor::new(&qoi_data);
48+
let mut decoder = qoi::Decoder::from_stream(stream)?;
49+
assert_eq!(decoder.decode_to_vec()?, expected);
50+
}
51+
3452
Ok(())
3553
}
3654

0 commit comments

Comments
 (0)