Skip to content

Commit

Permalink
Iterator interface
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed Jan 11, 2024
1 parent 9f942c7 commit 4fc7f34
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 2 deletions.
46 changes: 45 additions & 1 deletion src/reader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ pub struct DecodeOptions {
allow_unknown_blocks: bool,
}

impl Default for DecodeOptions {
fn default() -> Self {
Self::new()
}
}

impl DecodeOptions {
/// Creates a new decoder builder
#[must_use]
Expand Down Expand Up @@ -345,6 +351,8 @@ impl<R> Decoder<R> where R: Read {
///
/// Do not call `Self::next_frame_info` beforehand.
/// Deinterlaces the result.
///
/// You can also call `.into_iter()` on the decoder to use it as a regular iterator.
pub fn read_next_frame(&mut self) -> Result<Option<&Frame<'static>>, DecodingError> {
if let Some(frame) = self.next_frame_info()? {
let (width, height) = (frame.width, frame.height);
Expand Down Expand Up @@ -397,6 +405,14 @@ impl<R> Decoder<R> where R: Read {
}
}

/// This is private for iterator's use
fn take_current_frame(&mut self) -> Option<Frame<'static>> {
if self.current_frame.buffer.is_empty() {
return None;
}
Some(self.current_frame.take())
}

/// Reads the data of the current frame into a pre-allocated buffer.
///
/// `Self::next_frame_info` needs to be called beforehand.
Expand Down Expand Up @@ -516,7 +532,6 @@ impl<R> Decoder<R> where R: Read {

/// Returns the color palette relevant for the frame that has been decoded
pub fn palette(&self) -> Result<&[u8], DecodingError> {
// TODO prevent planic
Ok(match self.current_frame.palette {
Some(ref table) => table,
None => self.global_palette.as_ref().ok_or(DecodingError::format(
Expand Down Expand Up @@ -559,6 +574,35 @@ impl<R> Decoder<R> where R: Read {
}
}

impl<R: Read> IntoIterator for Decoder<R> {
type Item = Result<Frame<'static>, DecodingError>;
type IntoIter = DecoderIter<R>;

#[inline]
fn into_iter(self) -> Self::IntoIter {
DecoderIter {
inner: self,
}
}
}

/// Use `decoder.into_iter()` to iterate over the frames
pub struct DecoderIter<R: Read> {
inner: Decoder<R>,
}

impl<R: Read> Iterator for DecoderIter<R> {
type Item = Result<Frame<'static>, DecodingError>;

fn next(&mut self) -> Option<Self::Item> {
match self.inner.read_next_frame() {
Ok(Some(_)) => self.inner.take_current_frame().map(Ok),
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}
}

struct InterlaceIterator {
len: usize,
next: usize,
Expand Down
44 changes: 43 additions & 1 deletion tests/decode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use gif::{DecodeOptions, DisposalMethod, Encoder, Frame};
use gif::{Decoder, DecodeOptions, DisposalMethod, Encoder, Frame};

#[test]
fn frame_consistency_is_configurable() {
Expand Down Expand Up @@ -83,6 +83,48 @@ fn check_for_end_code_is_configurable() {
}
}

#[test]
fn check_rebuild_without_reencode1() {
rebuild_without_reencode(include_bytes!("samples/gifplayer-muybridge.gif"));
}

#[test]
fn check_rebuild_without_reencode2() {
rebuild_without_reencode(include_bytes!("samples/interlaced.gif"));
}

fn rebuild_without_reencode(image: &[u8]) {
let mut options = DecodeOptions::new();
options.skip_frame_decoding(true);
let mut decoder = options.read_info(&image[..]).unwrap();

let mut encoder = Encoder::new(Vec::new(), decoder.width(), decoder.height(), decoder.global_palette().unwrap_or_default()).unwrap();

let mut num_frames = 0;
while let Some(frame) = decoder.read_next_frame().unwrap() {
num_frames += 1;
encoder.write_lzw_pre_encoded_frame(&frame).unwrap();
}

let gif = encoder.into_inner().unwrap();
let rebuilt = Decoder::new(&gif[..]).unwrap();
let orig = Decoder::new(&image[..]).unwrap();

for (orig, rebuilt) in orig.into_iter().zip(rebuilt) {
num_frames -= 1;
let orig = orig.unwrap();
let rebuilt = rebuilt.unwrap();
assert_eq!(orig.delay, rebuilt.delay);
assert_eq!(orig.transparent, rebuilt.transparent);
assert_eq!(orig.palette, rebuilt.palette);
assert_eq!(orig.width, rebuilt.width);
assert_eq!(orig.height, rebuilt.height);
assert_eq!(orig.interlaced, rebuilt.interlaced);
assert_eq!(orig.buffer, rebuilt.buffer);
}
assert_eq!(num_frames, 0);
}

#[test]
fn check_skip_frame_data() {
let image: &[u8] = include_bytes!("samples/moon_impact.gif");
Expand Down
6 changes: 6 additions & 0 deletions tests/roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ fn encode_roundtrip() {
round_trip_from_image(ORIGINAL);
}

#[test]
fn encode_roundtrip2() {
const ORIGINAL: &[u8] = include_bytes!("samples/interlaced.gif");
round_trip_from_image(ORIGINAL);
}

fn round_trip_from_image(original: &[u8]) {
let (width, height, global_palette, repeat);
let frames: Vec<_> = {
Expand Down

0 comments on commit 4fc7f34

Please sign in to comment.