Skip to main content

tiff_reader/
filters.rs

1//! Compression filter pipeline for TIFF strip/tile decompression.
2
3#[cfg(any(feature = "jpeg", feature = "zstd"))]
4use std::io::Cursor;
5use std::io::Read;
6#[cfg(feature = "jpeg")]
7use std::panic::{self, AssertUnwindSafe};
8
9use crate::error::{Error, Result};
10use crate::header::ByteOrder;
11use tiff_core::{Compression, Predictor};
12
13/// Decompress a strip or tile according to the TIFF compression scheme.
14pub fn decompress(
15    compression: u16,
16    data: &[u8],
17    index: usize,
18    _jpeg_tables: Option<&[u8]>,
19    decoded_len_limit: usize,
20) -> Result<Vec<u8>> {
21    match Compression::from_code(compression) {
22        Some(Compression::None) => {
23            if data.len() > decoded_len_limit {
24                return Err(decoded_block_too_large(
25                    index,
26                    "uncompressed",
27                    decoded_len_limit,
28                ));
29            }
30            Ok(data.to_vec())
31        }
32        Some(Compression::Deflate | Compression::DeflateOld) => {
33            decompress_deflate(data, index, decoded_len_limit)
34        }
35        Some(Compression::Lzw) => decompress_lzw(data, index, decoded_len_limit),
36        Some(Compression::PackBits) => decompress_packbits(data, index, decoded_len_limit),
37        Some(Compression::Lerc) => Err(Error::UnsupportedCompression(compression)),
38        #[cfg(feature = "jpeg")]
39        Some(Compression::OldJpeg) => Err(Error::UnsupportedCompression(compression)),
40        #[cfg(feature = "jpeg")]
41        Some(Compression::Jpeg) => decompress_jpeg(data, index, _jpeg_tables, decoded_len_limit),
42        #[cfg(not(feature = "jpeg"))]
43        Some(Compression::OldJpeg | Compression::Jpeg) => {
44            Err(Error::UnsupportedCompression(compression))
45        }
46        #[cfg(feature = "zstd")]
47        Some(Compression::Zstd) => decompress_zstd(data, index, decoded_len_limit),
48        #[cfg(not(feature = "zstd"))]
49        Some(Compression::Zstd) => Err(Error::UnsupportedCompression(compression)),
50        None => Err(Error::UnsupportedCompression(compression)),
51    }
52}
53
54/// Normalize row bytes into native-endian decoded samples and reverse any TIFF predictor.
55pub fn fix_endianness_and_predict(
56    row: &mut [u8],
57    bit_depth: u16,
58    samples: u16,
59    byte_order: ByteOrder,
60    predictor: u16,
61) -> Result<()> {
62    match Predictor::from_code(predictor) {
63        Some(Predictor::None) => {
64            fix_endianness(row, byte_order, bit_depth);
65            Ok(())
66        }
67        Some(Predictor::Horizontal) => {
68            fix_endianness(row, byte_order, bit_depth);
69            reverse_horizontal_predictor(row, bit_depth, samples);
70            Ok(())
71        }
72        Some(Predictor::FloatingPoint) => match bit_depth {
73            16 => {
74                let mut encoded = row.to_vec();
75                predict_f16(&mut encoded, row, samples);
76                Ok(())
77            }
78            32 => {
79                let mut encoded = row.to_vec();
80                predict_f32(&mut encoded, row, samples);
81                Ok(())
82            }
83            64 => {
84                let mut encoded = row.to_vec();
85                predict_f64(&mut encoded, row, samples);
86                Ok(())
87            }
88            _ => Err(Error::UnsupportedPredictor(3)),
89        },
90        None => Err(Error::UnsupportedPredictor(predictor)),
91    }
92}
93
94fn decompress_deflate(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
95    use flate2::read::ZlibDecoder;
96
97    let decoder = ZlibDecoder::new(data);
98    read_bounded_to_end(decoder, index, "deflate", decoded_len_limit)
99}
100
101fn decompress_lzw(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
102    use weezl::decode::Configuration;
103    use weezl::{BitOrder, LzwStatus};
104
105    let mut decoder = Configuration::with_tiff_size_switch(BitOrder::Msb, 8)
106        .with_yield_on_full_buffer(true)
107        .build();
108    let probe_limit = decoded_len_probe_limit(index, "LZW", decoded_len_limit)?;
109    let mut out = Vec::with_capacity(decoded_len_limit.min(8192));
110    let mut input_offset = 0usize;
111    let mut scratch = [0u8; 8192];
112
113    loop {
114        let remaining = probe_limit.saturating_sub(out.len());
115        if remaining == 0 {
116            return Err(decoded_block_too_large(index, "LZW", decoded_len_limit));
117        }
118
119        let output_len = remaining.min(scratch.len());
120        let result = decoder.decode_bytes(&data[input_offset..], &mut scratch[..output_len]);
121        input_offset += result.consumed_in;
122        out.extend_from_slice(&scratch[..result.consumed_out]);
123        if out.len() > decoded_len_limit {
124            return Err(decoded_block_too_large(index, "LZW", decoded_len_limit));
125        }
126
127        match result.status {
128            Err(e) => {
129                return Err(Error::DecompressionFailed {
130                    index,
131                    reason: format!("LZW: {e}"),
132                })
133            }
134            Ok(LzwStatus::Done) => return Ok(out),
135            Ok(LzwStatus::Ok) => {
136                if result.consumed_in == 0 && result.consumed_out == 0 {
137                    return Err(Error::DecompressionFailed {
138                        index,
139                        reason: "LZW: decoder made no progress".into(),
140                    });
141                }
142            }
143            Ok(LzwStatus::NoProgress) => {
144                if result.consumed_out == output_len {
145                    continue;
146                }
147                return Err(Error::DecompressionFailed {
148                    index,
149                    reason: "LZW: stream ended before end marker".into(),
150                });
151            }
152        }
153    }
154}
155
156fn decompress_packbits(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
157    let probe_limit = decoded_len_probe_limit(index, "PackBits", decoded_len_limit)?;
158    let mut out = Vec::new();
159    let mut cursor = 0usize;
160
161    while cursor < data.len() {
162        let header = data[cursor] as i8;
163        cursor += 1;
164
165        if header >= 0 {
166            let count = header as usize + 1;
167            let end = cursor + count;
168            if end > data.len() {
169                return Err(Error::DecompressionFailed {
170                    index,
171                    reason: "PackBits literal run is truncated".into(),
172                });
173            }
174            append_bounded_bytes(
175                &mut out,
176                &data[cursor..end],
177                index,
178                "PackBits",
179                decoded_len_limit,
180                probe_limit,
181            )?;
182            cursor = end;
183        } else if header != -128 {
184            if cursor >= data.len() {
185                return Err(Error::DecompressionFailed {
186                    index,
187                    reason: "PackBits repeat run is truncated".into(),
188                });
189            }
190            let count = (1i16 - header as i16) as usize;
191            let byte = data[cursor];
192            cursor += 1;
193            append_bounded_repeat(
194                &mut out,
195                byte,
196                count,
197                index,
198                "PackBits",
199                decoded_len_limit,
200                probe_limit,
201            )?;
202        }
203    }
204
205    Ok(out)
206}
207
208#[cfg(feature = "jpeg")]
209fn decompress_jpeg(
210    data: &[u8],
211    index: usize,
212    jpeg_tables: Option<&[u8]>,
213    decoded_len_limit: usize,
214) -> Result<Vec<u8>> {
215    let stream = merge_jpeg_stream(jpeg_tables, data);
216    panic::catch_unwind(AssertUnwindSafe(|| {
217        let mut decoder = jpeg_decoder::Decoder::new(Cursor::new(stream));
218        decoder.set_max_decoding_buffer_size(decoded_len_limit);
219        decoder.read_info()?;
220        validate_jpeg_metadata_budget(&decoder, decoded_len_limit)?;
221        decoder.decode()
222    }))
223    .map_err(|payload| Error::DecompressionFailed {
224        index,
225        reason: format!(
226            "JPEG decoder panicked: {}",
227            panic_payload_message(payload.as_ref())
228        ),
229    })?
230    .map_err(|e| Error::DecompressionFailed {
231        index,
232        reason: format!("JPEG: {e}"),
233    })
234}
235
236#[cfg(feature = "jpeg")]
237fn validate_jpeg_metadata_budget<R: std::io::Read>(
238    decoder: &jpeg_decoder::Decoder<R>,
239    decoded_len_limit: usize,
240) -> std::result::Result<(), jpeg_decoder::Error> {
241    let info = decoder.info().ok_or_else(|| {
242        jpeg_decoder::Error::Format("JPEG metadata missing after read_info".into())
243    })?;
244    let decoded_len = usize::from(info.width)
245        .checked_mul(usize::from(info.height))
246        .and_then(|pixels| pixels.checked_mul(info.pixel_format.pixel_bytes()))
247        .ok_or_else(|| jpeg_decoder::Error::Format("JPEG decoded size overflow".into()))?;
248    if decoded_len > decoded_len_limit {
249        return Err(jpeg_decoder::Error::Format(format!(
250            "JPEG decoded size {decoded_len} exceeds TIFF block budget {decoded_len_limit}"
251        )));
252    }
253    Ok(())
254}
255
256#[cfg(feature = "zstd")]
257fn decompress_zstd(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
258    let decoder = zstd::stream::read::Decoder::new(Cursor::new(data)).map_err(|e| {
259        Error::DecompressionFailed {
260            index,
261            reason: format!("ZSTD: {e}"),
262        }
263    })?;
264    read_bounded_to_end(decoder, index, "ZSTD", decoded_len_limit)
265}
266
267fn read_bounded_to_end<R: Read>(
268    reader: R,
269    index: usize,
270    codec: &'static str,
271    decoded_len_limit: usize,
272) -> Result<Vec<u8>> {
273    let probe_limit = decoded_len_probe_limit(index, codec, decoded_len_limit)?;
274    let mut reader = reader.take(probe_limit as u64);
275    let mut out = Vec::with_capacity(decoded_len_limit.min(8192));
276    let mut scratch = [0u8; 8192];
277
278    loop {
279        let remaining = probe_limit.saturating_sub(out.len());
280        if remaining == 0 {
281            break;
282        }
283        let read_len = remaining.min(scratch.len());
284        let bytes_read =
285            reader
286                .read(&mut scratch[..read_len])
287                .map_err(|e| Error::DecompressionFailed {
288                    index,
289                    reason: format!("{codec}: {e}"),
290                })?;
291        if bytes_read == 0 {
292            break;
293        }
294        out.extend_from_slice(&scratch[..bytes_read]);
295        if out.len() > decoded_len_limit {
296            return Err(decoded_block_too_large(index, codec, decoded_len_limit));
297        }
298    }
299
300    Ok(out)
301}
302
303fn decoded_len_probe_limit(
304    index: usize,
305    codec: &'static str,
306    decoded_len_limit: usize,
307) -> Result<usize> {
308    decoded_len_limit
309        .checked_add(1)
310        .ok_or_else(|| Error::DecompressionFailed {
311            index,
312            reason: format!("{codec}: TIFF block budget is too large to probe safely"),
313        })
314}
315
316fn decoded_block_too_large(index: usize, codec: &'static str, decoded_len_limit: usize) -> Error {
317    Error::DecompressionFailed {
318        index,
319        reason: format!("{codec}: decoded block exceeds TIFF block budget {decoded_len_limit}"),
320    }
321}
322
323fn append_bounded_bytes(
324    out: &mut Vec<u8>,
325    bytes: &[u8],
326    index: usize,
327    codec: &'static str,
328    decoded_len_limit: usize,
329    probe_limit: usize,
330) -> Result<()> {
331    let remaining = probe_limit.saturating_sub(out.len());
332    let copy_len = bytes.len().min(remaining);
333    out.extend_from_slice(&bytes[..copy_len]);
334    if copy_len < bytes.len() || out.len() > decoded_len_limit {
335        return Err(decoded_block_too_large(index, codec, decoded_len_limit));
336    }
337    Ok(())
338}
339
340fn append_bounded_repeat(
341    out: &mut Vec<u8>,
342    byte: u8,
343    count: usize,
344    index: usize,
345    codec: &'static str,
346    decoded_len_limit: usize,
347    probe_limit: usize,
348) -> Result<()> {
349    let remaining = probe_limit.saturating_sub(out.len());
350    let copy_len = count.min(remaining);
351    out.resize(out.len() + copy_len, byte);
352    if copy_len < count || out.len() > decoded_len_limit {
353        return Err(decoded_block_too_large(index, codec, decoded_len_limit));
354    }
355    Ok(())
356}
357
358#[cfg(feature = "jpeg")]
359fn merge_jpeg_stream(jpeg_tables: Option<&[u8]>, scan_data: &[u8]) -> Vec<u8> {
360    if jpeg_tables.is_none() {
361        return scan_data.to_vec();
362    }
363
364    let tables = jpeg_tables.unwrap_or_default();
365    let table_body = match tables.strip_suffix(&[0xff, 0xd9]) {
366        Some(without_eoi) => without_eoi,
367        None => tables,
368    };
369    let scan_body = match scan_data.strip_prefix(&[0xff, 0xd8]) {
370        Some(without_soi) => without_soi,
371        None => scan_data,
372    };
373
374    let mut merged = Vec::with_capacity(table_body.len() + scan_body.len() + 2);
375    if table_body.starts_with(&[0xff, 0xd8]) {
376        merged.extend_from_slice(table_body);
377    } else {
378        merged.extend_from_slice(&[0xff, 0xd8]);
379        merged.extend_from_slice(table_body);
380    }
381    merged.extend_from_slice(scan_body);
382    if !merged.ends_with(&[0xff, 0xd9]) {
383        merged.extend_from_slice(&[0xff, 0xd9]);
384    }
385    merged
386}
387
388#[cfg(feature = "jpeg")]
389fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
390    if let Some(message) = payload.downcast_ref::<&'static str>() {
391        (*message).to_string()
392    } else if let Some(message) = payload.downcast_ref::<String>() {
393        message.clone()
394    } else {
395        "unknown panic payload".into()
396    }
397}
398
399fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u16) {
400    let host_is_little_endian = cfg!(target_endian = "little");
401    let data_is_little_endian = matches!(byte_order, ByteOrder::LittleEndian);
402    if host_is_little_endian == data_is_little_endian {
403        return;
404    }
405
406    let chunk = match bit_depth {
407        0..=8 => 1,
408        9..=16 => 2,
409        17..=32 => 4,
410        _ => 8,
411    };
412    if chunk == 1 {
413        return;
414    }
415
416    for value in buf.chunks_exact_mut(chunk) {
417        value.reverse();
418    }
419}
420
421fn reverse_horizontal_predictor(buf: &mut [u8], bit_depth: u16, samples: u16) {
422    let bytes_per_value = match bit_depth {
423        0..=8 => 1,
424        9..=16 => 2,
425        17..=32 => 4,
426        _ => 8,
427    };
428    let lookback = usize::from(samples) * bytes_per_value;
429
430    match bytes_per_value {
431        1 => {
432            for index in lookback..buf.len() {
433                buf[index] = buf[index].wrapping_add(buf[index - lookback]);
434            }
435        }
436        2 => {
437            for index in (lookback..buf.len()).step_by(2) {
438                let current = u16::from_ne_bytes(buf[index..index + 2].try_into().unwrap());
439                let previous = u16::from_ne_bytes(
440                    buf[index - lookback..index - lookback + 2]
441                        .try_into()
442                        .unwrap(),
443                );
444                buf[index..index + 2]
445                    .copy_from_slice(&current.wrapping_add(previous).to_ne_bytes());
446            }
447        }
448        4 => {
449            for index in (lookback..buf.len()).step_by(4) {
450                let current = u32::from_ne_bytes(buf[index..index + 4].try_into().unwrap());
451                let previous = u32::from_ne_bytes(
452                    buf[index - lookback..index - lookback + 4]
453                        .try_into()
454                        .unwrap(),
455                );
456                buf[index..index + 4]
457                    .copy_from_slice(&current.wrapping_add(previous).to_ne_bytes());
458            }
459        }
460        _ => {
461            for index in (lookback..buf.len()).step_by(8) {
462                let current = u64::from_ne_bytes(buf[index..index + 8].try_into().unwrap());
463                let previous = u64::from_ne_bytes(
464                    buf[index - lookback..index - lookback + 8]
465                        .try_into()
466                        .unwrap(),
467                );
468                buf[index..index + 8]
469                    .copy_from_slice(&current.wrapping_add(previous).to_ne_bytes());
470            }
471        }
472    }
473}
474
475fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
476    let samples = usize::from(samples);
477    for i in samples..input.len() {
478        input[i] = input[i].wrapping_add(input[i - samples]);
479    }
480    for (i, chunk) in output.chunks_mut(2).enumerate() {
481        chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([
482            input[i],
483            input[input.len() / 2 + i],
484        ])));
485    }
486}
487
488fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
489    let samples = usize::from(samples);
490    for i in samples..input.len() {
491        input[i] = input[i].wrapping_add(input[i - samples]);
492    }
493    for (i, chunk) in output.chunks_mut(4).enumerate() {
494        chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
495            input[i],
496            input[input.len() / 4 + i],
497            input[input.len() / 2 + i],
498            input[input.len() / 4 * 3 + i],
499        ])));
500    }
501}
502
503fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
504    let samples = usize::from(samples);
505    for i in samples..input.len() {
506        input[i] = input[i].wrapping_add(input[i - samples]);
507    }
508    for (i, chunk) in output.chunks_mut(8).enumerate() {
509        chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([
510            input[i],
511            input[input.len() / 8 + i],
512            input[input.len() / 8 * 2 + i],
513            input[input.len() / 8 * 3 + i],
514            input[input.len() / 8 * 4 + i],
515            input[input.len() / 8 * 5 + i],
516            input[input.len() / 8 * 6 + i],
517            input[input.len() / 8 * 7 + i],
518        ])));
519    }
520}
521
522#[cfg(test)]
523mod tests {
524    use std::path::Path;
525
526    #[cfg(not(feature = "jpeg"))]
527    use super::decompress;
528    #[cfg(feature = "jpeg")]
529    use super::{decompress, merge_jpeg_stream};
530    use super::{decompress_lzw, decompress_packbits, fix_endianness_and_predict};
531    use crate::header::ByteOrder;
532    use std::io::Write;
533    use tiff_core::Compression;
534
535    #[test]
536    fn horizontal_predictor_restores_u16_rows() {
537        let mut row = vec![1, 0, 1, 0, 2, 0];
538        fix_endianness_and_predict(&mut row, 16, 1, ByteOrder::LittleEndian, 2).unwrap();
539        assert_eq!(row, vec![1, 0, 2, 0, 4, 0]);
540    }
541
542    #[test]
543    fn packbits_decoder_rejects_truncated_repeat_run() {
544        let err = decompress_packbits(&[0xff], 0, 1).unwrap_err();
545        assert!(err.to_string().contains("PackBits"));
546    }
547
548    #[test]
549    fn deflate_decoder_rejects_blocks_that_exceed_budget() {
550        let payload = [0x2a; 128];
551        let mut encoder =
552            flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
553        encoder.write_all(&payload).unwrap();
554        let compressed = encoder.finish().unwrap();
555
556        let err =
557            decompress(Compression::Deflate.to_code(), &compressed, 0, None, 127).unwrap_err();
558        assert!(err.to_string().contains("block budget"));
559    }
560
561    #[test]
562    fn lzw_decoder_rejects_blocks_that_exceed_budget() {
563        let payload = [0x2a; 128];
564        let compressed = weezl::encode::Encoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
565            .encode(&payload)
566            .unwrap();
567
568        let err = decompress_lzw(&compressed, 0, 127).unwrap_err();
569        assert!(err.to_string().contains("block budget"));
570    }
571
572    #[test]
573    fn packbits_decoder_rejects_blocks_that_exceed_budget() {
574        let err = decompress_packbits(&[0x81, 0x2a], 0, 127).unwrap_err();
575        assert!(err.to_string().contains("block budget"));
576    }
577
578    #[test]
579    fn uncompressed_decoder_rejects_blocks_that_exceed_budget() {
580        let err =
581            decompress(Compression::None.to_code(), &[1, 2, 3, 4, 5], 0, None, 4).unwrap_err();
582        assert!(err.to_string().contains("block budget"));
583    }
584
585    #[test]
586    fn lzw_real_cog_tile_requires_repeated_trailer_bytes() {
587        let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
588            .join("../testdata/interoperability/gdal/gcore/data/cog/byte_little_endian_golden.tif");
589        let bytes = std::fs::read(fixture).unwrap();
590
591        let without_trailer = &bytes[570..570 + 1223];
592        let with_trailer = &bytes[570..570 + 1227];
593
594        assert!(decompress_lzw(without_trailer, 0, 1_000_000).is_ok());
595        assert!(decompress_lzw(with_trailer, 0, 1_000_000).is_ok());
596    }
597
598    #[cfg(feature = "zstd")]
599    #[test]
600    fn zstd_decoder_rejects_blocks_that_exceed_budget() {
601        let payload = [0x2a; 128];
602        let compressed = zstd::stream::encode_all(&payload[..], 0).unwrap();
603
604        let err = decompress(Compression::Zstd.to_code(), &compressed, 0, None, 127).unwrap_err();
605        assert!(err.to_string().contains("block budget"));
606    }
607
608    #[cfg(feature = "jpeg")]
609    #[test]
610    fn merges_jpeg_tables_with_abbreviated_scan() {
611        let merged = merge_jpeg_stream(
612            Some(&[0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0xff, 0xd9]),
613            &[0xff, 0xda, 0x00, 0x08, 0x00],
614        );
615        assert_eq!(&merged[..6], &[0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43]);
616        assert!(merged.ends_with(&[0xff, 0xd9]));
617    }
618
619    #[cfg(feature = "jpeg")]
620    #[test]
621    fn jpeg_decoder_rejects_frame_sizes_that_exceed_tiff_budget() {
622        let mut jpeg = vec![
623            0xff, 0xd8, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x14, 0x00, 0x14, 0x01, 0x01, 0x11,
624            0x00, 0xff, 0xc4, 0x00, 0x17, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
625            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x06, 0xff, 0xc4,
626            0x00, 0x2a, 0x10, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x05, 0x05, 0x00, 0x00, 0x00,
627            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x03, 0x04, 0x00, 0x18, 0x31, 0x41,
628            0x13, 0x21, 0x51, 0x71, 0x05, 0x22, 0x61, 0x91, 0xb1, 0x14, 0x42, 0x62, 0xc1, 0xf0,
629            0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x75, 0xc5, 0xb7, 0xd2,
630            0x31, 0x4a, 0x75, 0x51, 0xe0, 0x65, 0xf2, 0x19, 0xd8, 0x8d, 0x7d, 0xfe, 0x71, 0x19,
631            0x2b, 0x94, 0x54, 0x2c, 0x33, 0x38, 0x20, 0x2f, 0x7d, 0xf5, 0xd2, 0x40, 0x18, 0x6b,
632            0xdc, 0x3d, 0xa0, 0x44, 0x15, 0xc9, 0x2c, 0xa1, 0xc8, 0x5c, 0xa4, 0x2c, 0xed, 0xcc,
633            0x74, 0x83, 0xcb, 0xaf, 0x59, 0xc2, 0xaf, 0x0f, 0x02, 0xb3, 0x2e, 0x57, 0xfc, 0x79,
634            0x15, 0x9f, 0x58, 0xee, 0x3f, 0x7b, 0xe0, 0x59, 0x95, 0x84, 0x26, 0x56, 0xac, 0xc2,
635            0x62, 0xa0, 0x8c, 0xa4, 0x91, 0xc9, 0x44, 0xed, 0xa4, 0x9e, 0x9a, 0x08, 0xc1, 0x8a,
636            0x54, 0x9d, 0x41, 0xe3, 0xa4, 0xe8, 0x65, 0x01, 0xe7, 0xdc, 0xff, 0x00, 0x6d, 0x8d,
637            0x2f, 0x89, 0x5b, 0x50, 0xbe, 0xb9, 0x4a, 0x0d, 0x4c, 0x53, 0x51, 0x01, 0x8a, 0x31,
638            0x9a, 0x92, 0x22, 0x5a, 0x49, 0xe7, 0xda, 0x37, 0xeb, 0x8c, 0xc5, 0xc7, 0x0a, 0xd5,
639            0x87, 0x0a, 0x85, 0x30, 0xc7, 0xee, 0x69, 0x27, 0x40, 0x77, 0x3e, 0xbf, 0x18, 0x99,
640            0xae, 0x1c, 0xb6, 0xc0, 0x0d, 0x02, 0xf9, 0x47, 0xb0, 0x81, 0x8f, 0xff, 0xd9,
641        ];
642        jpeg[7] = 0x9b;
643        jpeg[8] = 0x43;
644        jpeg[9] = 0xee;
645        jpeg[10] = 0x23;
646
647        let error = decompress(Compression::Jpeg.to_code(), &jpeg, 0, None, 20 * 20).unwrap_err();
648        assert!(error.to_string().contains("block budget"));
649    }
650}