Skip to content

Commit 94c8f9b

Browse files
authored
Merge pull request #218 from image-rs/ext
StreamingDecoder streaming ext data
2 parents 18d3956 + 6f8127d commit 94c8f9b

File tree

4 files changed

+209
-157
lines changed

4 files changed

+209
-157
lines changed

src/reader/converter.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,26 @@ pub(crate) type FillBufferCallback<'a> =
2828

2929
/// Deinterlaces and expands to RGBA if needed
3030
pub(crate) struct PixelConverter {
31-
memory_limit: MemoryLimit,
3231
color_output: ColorOutput,
3332
buffer: Vec<u8>,
3433
global_palette: Option<Vec<u8>>,
3534
}
3635

3736
impl PixelConverter {
38-
pub(crate) const fn new(color_output: ColorOutput, memory_limit: MemoryLimit) -> Self {
37+
pub(crate) const fn new(color_output: ColorOutput) -> Self {
3938
Self {
40-
memory_limit,
4139
color_output,
4240
buffer: Vec::new(),
4341
global_palette: None,
4442
}
4543
}
4644

47-
pub(crate) fn check_buffer_size(&self, frame: &Frame<'_>) -> Result<usize, DecodingError> {
48-
let pixel_bytes = self
49-
.memory_limit
45+
pub(crate) fn check_buffer_size(
46+
&self,
47+
frame: &Frame<'_>,
48+
memory_limit: &MemoryLimit,
49+
) -> Result<usize, DecodingError> {
50+
let pixel_bytes = memory_limit
5051
.buffer_size(self.color_output, frame.width, frame.height)
5152
.ok_or_else(|| DecodingError::OutOfMemory)?;
5253

@@ -63,8 +64,9 @@ impl PixelConverter {
6364
&mut self,
6465
frame: &mut Frame<'_>,
6566
data_callback: FillBufferCallback<'_>,
67+
memory_limit: &MemoryLimit,
6668
) -> Result<(), DecodingError> {
67-
let pixel_bytes = self.check_buffer_size(frame)?;
69+
let pixel_bytes = self.check_buffer_size(frame, memory_limit)?;
6870
let mut vec = match mem::replace(&mut frame.buffer, Cow::Borrowed(&[])) {
6971
// reuse buffer if possible without reallocating
7072
Cow::Owned(mut vec) if vec.capacity() >= pixel_bytes => {

src/reader/decoder.rs

Lines changed: 47 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,11 @@ use std::io;
1313
use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame};
1414
use crate::reader::DecodeOptions;
1515
use crate::MemoryLimit;
16-
use crate::Repeat;
1716

1817
use weezl::{decode::Decoder as LzwDecoder, BitOrder, LzwError, LzwStatus};
1918

2019
/// GIF palettes are RGB
2120
pub const PLTE_CHANNELS: usize = 3;
22-
/// Headers for supported extensions.
23-
const EXT_NAME_NETSCAPE: &[u8] = b"NETSCAPE2.0\x01";
24-
const EXT_NAME_XMP: &[u8] = b"XMP DataXMP";
25-
const EXT_NAME_ICC: &[u8] = b"ICCRGBG1012";
2621

2722
/// An error returned in the case of the image not being formatted properly.
2823
#[derive(Debug)]
@@ -50,6 +45,8 @@ impl error::Error for DecodingFormatError {
5045
pub enum DecodingError {
5146
/// Failed to internally allocate a buffer of sufficient size.
5247
OutOfMemory,
48+
/// Allocation exceeded set memory limit
49+
MemoryLimit,
5350
/// Expected a decoder but none found.
5451
DecoderNotFound,
5552
/// Expected an end-code, but none found.
@@ -78,6 +75,7 @@ impl fmt::Display for DecodingError {
7875
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
7976
match *self {
8077
Self::OutOfMemory => fmt.write_str("Out of Memory"),
78+
Self::MemoryLimit => fmt.write_str("Memory limit reached"),
8179
Self::DecoderNotFound => fmt.write_str("Decoder Not Found"),
8280
Self::EndCodeNotFound => fmt.write_str("End-Code Not Found"),
8381
Self::UnexpectedEof => fmt.write_str("Unexpected End of File"),
@@ -93,6 +91,7 @@ impl error::Error for DecodingError {
9391
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
9492
match *self {
9593
Self::OutOfMemory => None,
94+
Self::MemoryLimit => None,
9695
Self::DecoderNotFound => None,
9796
Self::EndCodeNotFound => None,
9897
Self::UnexpectedEof => None,
@@ -146,28 +145,21 @@ pub enum Decoded {
146145
GlobalPalette(Box<[u8]>),
147146
/// Index of the background color in the global palette.
148147
BackgroundColor(u8),
149-
/// Loop count is known
150-
Repetitions(Repeat),
151148
/// Palette and optional `Application` extension have been parsed,
152149
/// reached frame data.
153150
HeaderEnd,
154151
/// The start of a block.
155152
/// `BlockStart(Block::Trailer)` is the very last decode event
156153
BlockStart(Block),
157-
/// Decoded a sub-block. More sub-block are available.
154+
/// Decoded a sub-block.
158155
///
159-
/// Indicates the label of the extension which might be unknown. A label of `0` is used when
160-
/// the sub block does not belong to an extension.
161-
///
162-
/// Call `last_ext()` to get the data
163-
SubBlockFinished(AnyExtension),
164-
/// Decoded the last (or only) sub-block of a block.
165-
///
166-
/// Indicates the label of the extension which might be unknown. A label of `0` is used when
167-
/// the sub block does not belong to an extension.
168-
///
169-
/// Call `last_ext()` to get the data
170-
BlockFinished(AnyExtension),
156+
/// Call `last_ext_sub_block()` to get the sub-block data. It won't be available after this event.
157+
SubBlock {
158+
/// An ext label of `0` is used when the sub block does not belong to an extension.
159+
ext: AnyExtension,
160+
/// if true, then no more sub-blocks are available in this block.
161+
is_last: bool,
162+
},
171163
/// Decoded all information of the next frame, except the image data.
172164
///
173165
/// The returned frame does **not** contain any owned image data.
@@ -192,9 +184,11 @@ enum State {
192184
BlockStart(u8),
193185
BlockEnd,
194186
ExtensionBlockStart,
187+
/// Resets ext.data
188+
ExtensionDataSubBlockStart(usize),
195189
/// Collects data in ext.data
196-
ExtensionDataBlock(usize),
197-
ApplicationExtension,
190+
ExtensionDataSubBlock(usize),
191+
ExtensionBlockEnd,
198192
LocalPalette(usize),
199193
LzwInit(u8),
200194
/// Decompresses LZW
@@ -212,6 +206,7 @@ use super::converter::PixelConverter;
212206
pub struct FrameDecoder {
213207
lzw_reader: LzwReader,
214208
pixel_converter: PixelConverter,
209+
memory_limit: MemoryLimit,
215210
}
216211

217212
impl FrameDecoder {
@@ -221,7 +216,8 @@ impl FrameDecoder {
221216
pub fn new(options: DecodeOptions) -> Self {
222217
Self {
223218
lzw_reader: LzwReader::new(options.check_for_end_code),
224-
pixel_converter: PixelConverter::new(options.color_output, options.memory_limit),
219+
pixel_converter: PixelConverter::new(options.color_output),
220+
memory_limit: options.memory_limit.clone(),
225221
}
226222
}
227223

@@ -236,7 +232,9 @@ impl FrameDecoder {
236232
/// If you get an error about invalid min code size, the buffer was probably pixels, not compressed data.
237233
#[inline]
238234
pub fn decode_lzw_encoded_frame(&mut self, frame: &mut Frame<'_>) -> Result<(), DecodingError> {
239-
let pixel_bytes = self.pixel_converter.check_buffer_size(frame)?;
235+
let pixel_bytes = self
236+
.pixel_converter
237+
.check_buffer_size(frame, &self.memory_limit)?;
240238
let mut vec = vec![0; pixel_bytes];
241239
self.decode_lzw_encoded_frame_into_buffer(frame, &mut vec)?;
242240
frame.buffer = Cow::Owned(vec);
@@ -382,10 +380,6 @@ pub struct StreamingDecoder {
382380
current: Option<Frame<'static>>,
383381
/// Needs to emit `HeaderEnd` once
384382
header_end_reached: bool,
385-
/// XMP metadata bytes.
386-
xmp_metadata: Option<Vec<u8>>,
387-
/// ICC profile bytes.
388-
icc_profile: Option<Vec<u8>>,
389383
}
390384

391385
/// One version number of the GIF standard.
@@ -400,8 +394,6 @@ pub enum Version {
400394
struct ExtensionData {
401395
id: AnyExtension,
402396
data: Vec<u8>,
403-
sub_block_lens: Vec<u8>,
404-
is_block_end: bool,
405397
}
406398

407399
/// Destination to write to for `StreamingDecoder::update`
@@ -429,9 +421,7 @@ impl OutputBuffer<'_> {
429421
OutputBuffer::Vec(vec) => {
430422
let vec: &mut Vec<u8> = vec;
431423
let len = buf.len();
432-
memory_limit.check_size(vec.len() + len)?;
433-
vec.try_reserve(len)
434-
.map_err(|_| DecodingError::OutOfMemory)?;
424+
memory_limit.try_reserve(vec, len)?;
435425
if vec.capacity() - vec.len() >= len {
436426
vec.extend_from_slice(buf);
437427
}
@@ -470,13 +460,9 @@ impl StreamingDecoder {
470460
ext: ExtensionData {
471461
id: AnyExtension(0),
472462
data: Vec::with_capacity(256), // 0xFF + 1 byte length
473-
sub_block_lens: Vec::new(),
474-
is_block_end: true,
475463
},
476464
current: None,
477465
header_end_reached: false,
478-
xmp_metadata: None,
479-
icc_profile: None,
480466
}
481467
}
482468

@@ -503,10 +489,11 @@ impl StreamingDecoder {
503489
Ok((len - buf.len(), Decoded::Nothing))
504490
}
505491

506-
/// Returns the data of the last extension that has been decoded.
492+
/// Data of the last extension sub block that has been decoded.
493+
/// You need to concatenate all subblocks together to get the overall block content.
507494
#[must_use]
508-
pub fn last_ext(&self) -> (AnyExtension, &[u8], bool) {
509-
(self.ext.id, &self.ext.data, self.ext.is_block_end)
495+
pub fn last_ext_sub_block(&mut self) -> &[u8] {
496+
&self.ext.data
510497
}
511498

512499
/// Current frame info as a mutable ref.
@@ -543,18 +530,6 @@ impl StreamingDecoder {
543530
self.height
544531
}
545532

546-
/// XMP metadata stored in the image.
547-
#[must_use]
548-
pub fn xmp_metadata(&self) -> Option<&[u8]> {
549-
self.xmp_metadata.as_deref()
550-
}
551-
552-
/// ICC profile stored in the image.
553-
#[must_use]
554-
pub fn icc_profile(&self) -> Option<&[u8]> {
555-
self.icc_profile.as_deref()
556-
}
557-
558533
/// The version number of the GIF standard used in this image.
559534
///
560535
/// We suppose a minimum of `V87a` compatibility. This value will be reported until we have
@@ -739,6 +714,13 @@ impl StreamingDecoder {
739714
}
740715
}
741716
}
717+
ExtensionBlockStart => {
718+
goto!(ExtensionDataSubBlockStart(b as usize), emit Decoded::BlockStart(Block::Extension))
719+
}
720+
ExtensionBlockEnd => {
721+
self.ext.data.clear();
722+
goto!(0, BlockEnd)
723+
}
742724
BlockEnd => {
743725
if b == Block::Trailer as u8 {
744726
// can't consume yet, because the trailer is not a real block,
@@ -748,49 +730,27 @@ impl StreamingDecoder {
748730
goto!(BlockStart(b))
749731
}
750732
}
751-
ExtensionBlockStart => {
733+
ExtensionDataSubBlockStart(sub_block_len) => {
752734
self.ext.data.clear();
753-
self.ext.sub_block_lens.clear();
754-
self.ext.is_block_end = false;
755-
self.ext.sub_block_lens.push(b);
756-
goto!(ExtensionDataBlock(b as usize), emit Decoded::BlockStart(Block::Extension))
735+
goto!(0, ExtensionDataSubBlock(sub_block_len))
757736
}
758-
ExtensionDataBlock(left) => {
737+
ExtensionDataSubBlock(left) => {
759738
if left > 0 {
760739
let n = cmp::min(left, buf.len());
761-
self.memory_limit.check_size(self.ext.data.len() + n)?;
762-
self.ext
763-
.data
764-
.try_reserve(n)
765-
.map_err(|_| DecodingError::OutOfMemory)?;
740+
let needs_to_grow =
741+
n > self.ext.data.capacity().wrapping_sub(self.ext.data.len());
742+
if needs_to_grow {
743+
return Err(DecodingError::OutOfMemory);
744+
}
766745
self.ext.data.extend_from_slice(&buf[..n]);
767-
goto!(n, ExtensionDataBlock(left - n))
746+
goto!(n, ExtensionDataSubBlock(left - n))
768747
} else if b == 0 {
769-
self.ext.is_block_end = true;
770-
match self.ext.id.into_known() {
771-
Some(Extension::Application) => {
772-
goto!(0, ApplicationExtension, emit Decoded::BlockFinished(self.ext.id))
773-
}
774-
Some(Extension::Control) => {
775-
self.read_control_extension()?;
776-
goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id))
777-
}
778-
_ => {
779-
goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id))
780-
}
748+
if self.ext.id.into_known() == Some(Extension::Control) {
749+
self.read_control_extension()?;
781750
}
751+
goto!(ExtensionBlockEnd, emit Decoded::SubBlock { ext: self.ext.id, is_last: true })
782752
} else {
783-
self.ext.sub_block_lens.push(b);
784-
self.ext.is_block_end = false;
785-
goto!(ExtensionDataBlock(b as usize), emit Decoded::SubBlockFinished(self.ext.id))
786-
}
787-
}
788-
ApplicationExtension => {
789-
debug_assert_eq!(0, b);
790-
if let Some(decoded) = self.read_application_extension() {
791-
goto!(BlockEnd, emit decoded)
792-
} else {
793-
goto!(BlockEnd)
753+
goto!(ExtensionDataSubBlockStart(b as usize), emit Decoded::SubBlock { ext: self.ext.id, is_last: false })
794754
}
795755
}
796756
LocalPalette(left) => {
@@ -897,48 +857,6 @@ impl StreamingDecoder {
897857
Ok(())
898858
}
899859

900-
fn read_application_extension(&mut self) -> Option<Decoded> {
901-
if let Some(&[first, second]) = self.ext.data.strip_prefix(EXT_NAME_NETSCAPE) {
902-
let repeat = u16::from(first) | u16::from(second) << 8;
903-
return Some(Decoded::Repetitions(if repeat == 0 {
904-
Repeat::Infinite
905-
} else {
906-
Repeat::Finite(repeat)
907-
}));
908-
} else if let Some(mut rest) = self.ext.data.strip_prefix(EXT_NAME_XMP) {
909-
let (&id_len, data_lens) = self.ext.sub_block_lens.split_first()?;
910-
if id_len as usize != EXT_NAME_XMP.len() {
911-
return None;
912-
}
913-
// XMP is not written as a valid "pascal-string", so we need to stitch together
914-
// the text from our collected sublock-lengths.
915-
let mut xmp_metadata = Vec::with_capacity(data_lens.len() + self.ext.data.len());
916-
for &len in data_lens {
917-
xmp_metadata.push(len);
918-
let (sub_block, tail) = rest.split_at(len as usize);
919-
xmp_metadata.extend_from_slice(sub_block);
920-
rest = tail;
921-
}
922-
923-
// XMP adds a "ramp" of 257 bytes to the end of the metadata to let the "pascal-strings"
924-
// parser converge to the null byte. The ramp looks like "0x01, 0xff, .., 0x01, 0x00".
925-
// For convenience and to allow consumers to not be bothered with this implementation detail,
926-
// we cut the ramp.
927-
const RAMP_SIZE: usize = 257;
928-
if xmp_metadata.len() >= RAMP_SIZE
929-
&& xmp_metadata.ends_with(&[0x03, 0x02, 0x01, 0x00])
930-
&& xmp_metadata[xmp_metadata.len() - RAMP_SIZE..].starts_with(&[0x01, 0x0ff])
931-
{
932-
xmp_metadata.truncate(xmp_metadata.len() - RAMP_SIZE);
933-
}
934-
935-
self.xmp_metadata = Some(xmp_metadata);
936-
} else if let Some(rest) = self.ext.data.strip_prefix(EXT_NAME_ICC) {
937-
self.icc_profile = Some(rest.to_vec());
938-
}
939-
None
940-
}
941-
942860
fn add_frame(&mut self) {
943861
if self.current.is_none() {
944862
self.current = Some(Frame::default());

0 commit comments

Comments
 (0)