Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ jobs:
rustfmt:
runs-on: ubuntu-latest
continue-on-error: false
if: false
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
Expand Down
71 changes: 42 additions & 29 deletions benches/decode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use criterion::{black_box, BenchmarkId, BenchmarkGroup, Criterion, Throughput, measurement::Measurement};
use criterion::{
black_box, measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
};
use gif::Decoder;

fn read_image(image: &[u8]) -> Option<Vec<u8>> {
Expand Down Expand Up @@ -42,38 +44,49 @@ fn main() {
let mut c = Criterion::default().configure_from_args();
let mut group = c.benchmark_group("gif");

run_bench_def(&mut group, BenchDef {
data: include_bytes!("note.gif"),
id: "note.gif",
sample_size: 100,
});
run_bench_def(
&mut group,
BenchDef {
data: include_bytes!("note.gif"),
id: "note.gif",
sample_size: 100,
},
);

run_bench_def(&mut group, BenchDef {
data: include_bytes!("photo.gif"),
id: "photo.gif",
sample_size: 20,
});
run_bench_def(
&mut group,
BenchDef {
data: include_bytes!("photo.gif"),
id: "photo.gif",
sample_size: 20,
},
);

run_bench_def(&mut group, BenchDef {
data: include_bytes!("../tests/samples/sample_1.gif"),
id: "sample_1.gif",
sample_size: 100,
});
run_bench_def(
&mut group,
BenchDef {
data: include_bytes!("../tests/samples/sample_1.gif"),
id: "sample_1.gif",
sample_size: 100,
},
);

run_bench_def(&mut group, BenchDef {
data: include_bytes!("../tests/samples/sample_big.gif"),
id: "sample_big.gif",
sample_size: 20,
});
run_bench_def(
&mut group,
BenchDef {
data: include_bytes!("../tests/samples/sample_big.gif"),
id: "sample_big.gif",
sample_size: 20,
},
);

group
.bench_with_input(
"extract-metadata-note",
include_bytes!("note.gif"),
|b, input| {
b.iter(|| read_metadata(input));
}
);
group.bench_with_input(
"extract-metadata-note",
include_bytes!("note.gif"),
|b, input| {
b.iter(|| read_metadata(input));
},
);

group.finish();

Expand Down
22 changes: 11 additions & 11 deletions benches/rgb_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,20 @@ fn main() {
group
.sample_size(50)
.throughput(Throughput::Bytes(size as u64))
.bench_function(path.file_name().unwrap().to_str().unwrap(),
|b| {
match info.color_type {
png::ColorType::Rgb => b.iter(|| {
Frame::from_rgb_speed(w, h, &mut buf[..size], 30)
}),
png::ColorType::Rgba => b.iter(|| {
Frame::from_rgba_speed(w, h, &mut buf[..size], 30)
}),
.bench_function(
path.file_name().unwrap().to_str().unwrap(),
|b| match info.color_type {
png::ColorType::Rgb => {
b.iter(|| Frame::from_rgb_speed(w, h, &mut buf[..size], 30))
}
png::ColorType::Rgba => {
b.iter(|| Frame::from_rgba_speed(w, h, &mut buf[..size], 30))
}
c => {
println!("Image has wrong color type: {c:?}");
}
}
});
},
);

// actually write the image as a singe frame gif... while MSE can be used
// for quality check, it might not be as good as visual inspection
Expand Down
11 changes: 6 additions & 5 deletions examples/check.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use std::{env, fs, process};

fn main() {
let file = env::args().nth(1)
.unwrap_or_else(|| explain_usage());
let file = fs::File::open(file)
.expect("failed to open input file");
let file = env::args().nth(1).unwrap_or_else(|| explain_usage());
let file = fs::File::open(file).expect("failed to open input file");
let mut reader = {
let mut options = gif::DecodeOptions::new();
options.allow_unknown_blocks(true);
Expand All @@ -28,7 +26,10 @@ fn main() {
dispose: {:?}\n \
needs_input: {:?}",
frame.delay,
frame.width, frame.height, frame.left, frame.top,
frame.width,
frame.height,
frame.left,
frame.top,
frame.dispose,
frame.needs_user_input
);
Expand Down
40 changes: 27 additions & 13 deletions examples/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let (send, recv) = std::sync::mpsc::channel();

decoder.into_iter().enumerate().par_bridge().try_for_each(move |(frame_number, frame)| {
let mut frame = frame?;
FrameDecoder::new(DecodeOptions::new())
.decode_lzw_encoded_frame(&mut frame)
.unwrap();
// frame is now pixels
frame.make_lzw_pre_encoded();
// frame is now LZW again, re-encoded
send.send((frame_number, frame)).unwrap();
Ok::<_, gif::DecodingError>(())
})?;
decoder
.into_iter()
.enumerate()
.par_bridge()
.try_for_each(move |(frame_number, frame)| {
let mut frame = frame?;
FrameDecoder::new(DecodeOptions::new())
.decode_lzw_encoded_frame(&mut frame)
.unwrap();
// frame is now pixels
frame.make_lzw_pre_encoded();
// frame is now LZW again, re-encoded
send.send((frame_number, frame)).unwrap();
Ok::<_, gif::DecodingError>(())
})?;

// Decoding and encoding can happen in parallel, but writing to the GIF file is sequential
let mut next_frame_number = 0;
Expand All @@ -59,7 +63,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// frames can arrive in any order, since they're processed in parallel,
// so they have to be stored in a queue
frames_to_process.push((frame_number, frame));
while let Some(index) = frames_to_process.iter().position(|&(num, _)| num == next_frame_number) {
while let Some(index) = frames_to_process
.iter()
.position(|&(num, _)| num == next_frame_number)
{
let frame = frames_to_process.remove(index).1;
encoder.write_lzw_pre_encoded_frame(&frame)?;
next_frame_number += 1;
Expand All @@ -70,6 +77,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let seconds = start.elapsed().as_millis() as f64 / 1000.;
let rate = (input_size / 1024 / 1024) as f64 / seconds;

eprintln!("Finished in {seconds:0.2}s, {rate:0.0}MiB/s {}", if cfg!(debug_assertions) { ". Run with --release for more speed." } else { "" });
eprintln!(
"Finished in {seconds:0.2}s, {rate:0.0}MiB/s {}",
if cfg!(debug_assertions) {
". Run with --release for more speed."
} else {
""
}
);
Ok(())
}
63 changes: 51 additions & 12 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ impl Frame<'static> {
#[track_caller]
pub fn from_rgba_speed(width: u16, height: u16, pixels: &mut [u8], speed: i32) -> Self {
assert_eq!(width as usize * height as usize * 4, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame");
assert!(speed >= 1 && speed <= 30, "speed needs to be in the range [1, 30]");
assert!(
speed >= 1 && speed <= 30,
"speed needs to be in the range [1, 30]"
);
let mut transparent = None;
for pix in pixels.chunks_exact_mut(4) {
if pix[3] != 0 {
Expand All @@ -233,7 +236,12 @@ impl Frame<'static> {
return Frame {
width,
height,
buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| nq.index_of(pix) as u8).collect()),
buffer: Cow::Owned(
pixels
.chunks_exact(4)
.map(|pix| nq.index_of(pix) as u8)
.collect(),
),
palette: Some(nq.color_map_rgb()),
transparent: transparent.map(|t| nq.index_of(&t) as u8),
..Frame::default()
Expand All @@ -244,11 +252,19 @@ impl Frame<'static> {
// Palette size <= 256 elements, we can build an exact palette.
let mut colors_vec: Vec<(u8, u8, u8, u8)> = colors.into_iter().collect();
colors_vec.sort_unstable();
let palette = colors_vec.iter().flat_map(|&(r, g, b, _a)| [r, g, b]).collect();
let colors_lookup: HashMap<(u8, u8, u8, u8), u8> = colors_vec.into_iter().zip(0..=255).collect();
let palette = colors_vec
.iter()
.flat_map(|&(r, g, b, _a)| [r, g, b])
.collect();
let colors_lookup: HashMap<(u8, u8, u8, u8), u8> =
colors_vec.into_iter().zip(0..=255).collect();

let index_of = | pixel: &[u8] |
colors_lookup.get(&(pixel[0], pixel[1], pixel[2], pixel[3])).copied().unwrap_or(0);
let index_of = |pixel: &[u8]| {
colors_lookup
.get(&(pixel[0], pixel[1], pixel[2], pixel[3]))
.copied()
.unwrap_or(0)
};

Frame {
width,
Expand All @@ -266,11 +282,24 @@ impl Frame<'static> {
/// * If the length of pixels does not equal `width * height`.
/// * If the length of palette > `256 * 3`.
#[track_caller]
pub fn from_palette_pixels(width: u16, height: u16, pixels: impl Into<Vec<u8>>, palette: impl Into<Vec<u8>>, transparent: Option<u8>) -> Self {
pub fn from_palette_pixels(
width: u16,
height: u16,
pixels: impl Into<Vec<u8>>,
palette: impl Into<Vec<u8>>,
transparent: Option<u8>,
) -> Self {
let pixels = pixels.into();
let palette = palette.into();
assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame");
assert!(palette.len() <= 256*3, "Too many palette values to create a GIF Frame");
assert_eq!(
width as usize * height as usize,
pixels.len(),
"Too many or too little pixels for the given width and height to create a GIF Frame"
);
assert!(
palette.len() <= 256 * 3,
"Too many palette values to create a GIF Frame"
);

Frame {
width,
Expand All @@ -287,9 +316,18 @@ impl Frame<'static> {
/// # Panics:
/// * If the length of pixels does not equal `width * height`.
#[track_caller]
pub fn from_indexed_pixels(width: u16, height: u16, pixels: impl Into<Vec<u8>>, transparent: Option<u8>) -> Self {
pub fn from_indexed_pixels(
width: u16,
height: u16,
pixels: impl Into<Vec<u8>>,
transparent: Option<u8>,
) -> Self {
let pixels = pixels.into();
assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame");
assert_eq!(
width as usize * height as usize,
pixels.len(),
"Too many or too little pixels for the given width and height to create a GIF Frame"
);

Frame {
width,
Expand Down Expand Up @@ -338,7 +376,8 @@ impl Frame<'static> {
pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Self {
assert_eq!(width as usize * height as usize * 3, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame");
let mut vec: Vec<u8> = Vec::new();
vec.try_reserve_exact(pixels.len() + width as usize * height as usize).expect("OOM");
vec.try_reserve_exact(pixels.len() + width as usize * height as usize)
.expect("OOM");
for v in pixels.chunks_exact(3) {
vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF]);
}
Expand Down
Loading
Loading