Merge pull request #95 from ruffson/support_modern_jpeg
Support decoding of modern JPEG compression
This commit is contained in:
7 files changed
+205
-5
No files matched your search
@@ -18,6 +18,7 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
weezl = "0.1.0"
|
weezl = "0.1.0"
|
||||||
|
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false }
|
||||||
|
|
||||||
[dependencies.miniz_oxide]
|
[dependencies.miniz_oxide]
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|||||||
+30
-2
@@ -10,13 +10,14 @@ use tags::{Tag, Type};
|
|||||||
use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||||
|
|
||||||
use self::Value::{
|
use self::Value::{
|
||||||
Ascii, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Signed, SignedBig,
|
Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Signed,
|
||||||
Unsigned, UnsignedBig,
|
SignedBig, Unsigned, UnsignedBig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused_qualifications)]
|
#[allow(unused_qualifications)]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
Byte(u8),
|
||||||
Signed(i32),
|
Signed(i32),
|
||||||
SignedBig(i64),
|
SignedBig(i64),
|
||||||
Unsigned(u32),
|
Unsigned(u32),
|
||||||
@@ -34,6 +35,13 @@ pub enum Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
pub fn into_u8(self) -> TiffResult<u8> {
|
||||||
|
match self {
|
||||||
|
Byte(val) => Ok(val),
|
||||||
|
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_u32(self) -> TiffResult<u32> {
|
pub fn into_u32(self) -> TiffResult<u32> {
|
||||||
match self {
|
match self {
|
||||||
Unsigned(val) => Ok(val),
|
Unsigned(val) => Ok(val),
|
||||||
@@ -114,6 +122,23 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_u8_vec(self) -> TiffResult<Vec<u8>> {
|
||||||
|
match self {
|
||||||
|
List(vec) => {
|
||||||
|
let mut new_vec = Vec::with_capacity(vec.len());
|
||||||
|
for v in vec {
|
||||||
|
new_vec.push(v.into_u8()?)
|
||||||
|
}
|
||||||
|
Ok(new_vec)
|
||||||
|
}
|
||||||
|
Byte(val) => Ok(vec![val]),
|
||||||
|
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_i32_vec(self) -> TiffResult<Vec<i32>> {
|
pub fn into_i32_vec(self) -> TiffResult<Vec<i32>> {
|
||||||
match self {
|
match self {
|
||||||
List(vec) => {
|
List(vec) => {
|
||||||
@@ -426,6 +451,9 @@ impl Entry {
|
|||||||
Ok(Ascii(string))
|
Ok(Ascii(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(Type::UNDEFINED, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
|
Ok(Byte(u8::from(decoder.read_byte()?)))
|
||||||
|
}),
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::UnsupportedDataType,
|
TiffUnsupportedError::UnsupportedDataType,
|
||||||
)),
|
)),
|
||||||
|
|||||||
+71
-3
@@ -9,7 +9,7 @@ use self::ifd::Directory;
|
|||||||
use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type};
|
use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type};
|
||||||
|
|
||||||
use self::stream::{
|
use self::stream::{
|
||||||
ByteOrder, DeflateReader, EndianReader, LZWReader, PackBitsReader, SmartReader,
|
ByteOrder, DeflateReader, EndianReader, JpegReader, LZWReader, PackBitsReader, SmartReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod ifd;
|
pub mod ifd;
|
||||||
@@ -165,6 +165,9 @@ pub struct Limits {
|
|||||||
/// The maximum size of any ifd value in bytes, the default is
|
/// The maximum size of any ifd value in bytes, the default is
|
||||||
/// 1MiB.
|
/// 1MiB.
|
||||||
pub ifd_value_size: usize,
|
pub ifd_value_size: usize,
|
||||||
|
/// Maximum size for intermediate buffer which may be used to limit the amount of data read per
|
||||||
|
/// segment even if the entire image is decoded at once.
|
||||||
|
pub intermediate_buffer_size: usize,
|
||||||
/// The purpose of this is to prevent all the fields of the struct from
|
/// The purpose of this is to prevent all the fields of the struct from
|
||||||
/// being public, as this would make adding new fields a major version
|
/// being public, as this would make adding new fields a major version
|
||||||
/// bump.
|
/// bump.
|
||||||
@@ -175,6 +178,7 @@ impl Default for Limits {
|
|||||||
fn default() -> Limits {
|
fn default() -> Limits {
|
||||||
Limits {
|
Limits {
|
||||||
decoding_buffer_size: 256 * 1024 * 1024,
|
decoding_buffer_size: 256 * 1024 * 1024,
|
||||||
|
intermediate_buffer_size: 128 * 1024 * 1024,
|
||||||
ifd_value_size: 1024 * 1024,
|
ifd_value_size: 1024 * 1024,
|
||||||
_non_exhaustive: (),
|
_non_exhaustive: (),
|
||||||
}
|
}
|
||||||
@@ -638,7 +642,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
for _ in 0..num_tags {
|
for _ in 0..num_tags {
|
||||||
let (tag, entry) = match self.read_entry()? {
|
let (tag, entry) = match self.read_entry()? {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => continue, // Unknown data type in tag, skip
|
None => {
|
||||||
|
continue;
|
||||||
|
} // Unknown data type in tag, skip
|
||||||
};
|
};
|
||||||
dir.insert(tag, entry);
|
dir.insert(tag, entry);
|
||||||
}
|
}
|
||||||
@@ -746,6 +752,11 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.get_tag(tag)?.into_f64_vec()
|
self.get_tag(tag)?.into_f64_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag and convert it to a 8bit vector.
|
||||||
|
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
|
||||||
|
self.get_tag(tag)?.into_u8_vec()
|
||||||
|
}
|
||||||
|
|
||||||
/// Decompresses the strip into the supplied buffer.
|
/// Decompresses the strip into the supplied buffer.
|
||||||
/// Returns the number of bytes read.
|
/// Returns the number of bytes read.
|
||||||
fn expand_strip<'a>(
|
fn expand_strip<'a>(
|
||||||
@@ -923,6 +934,60 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_jpeg(&mut self) -> TiffResult<DecodingResult> {
|
||||||
|
let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?;
|
||||||
|
let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?;
|
||||||
|
|
||||||
|
let jpeg_tables: Option<Vec<u8>> = match self.find_tag(Tag::JPEGTables) {
|
||||||
|
Ok(None) => None,
|
||||||
|
Ok(_) => Some(self.get_tag_u8_vec(Tag::JPEGTables)?),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if offsets.len() == 0 {
|
||||||
|
return Err(TiffError::FormatError(TiffFormatError::RequiredTagEmpty(
|
||||||
|
Tag::StripOffsets,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if offsets.len() != bytes.len() {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res_img = Vec::with_capacity(offsets[0] as usize);
|
||||||
|
|
||||||
|
for (idx, offset) in offsets.iter().enumerate() {
|
||||||
|
if bytes[idx] as usize > self.limits.intermediate_buffer_size {
|
||||||
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.goto_offset(*offset)?;
|
||||||
|
let jpeg_reader = JpegReader::new(&mut self.reader, bytes[idx], &jpeg_tables)?;
|
||||||
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
|
|
||||||
|
match decoder.decode() {
|
||||||
|
Ok(mut val) => res_img.append(&mut val),
|
||||||
|
Err(e) => {
|
||||||
|
return match e {
|
||||||
|
jpeg::Error::Io(io_err) => Err(TiffError::IoError(io_err)),
|
||||||
|
jpeg::Error::Format(fmt_err) => {
|
||||||
|
Err(TiffError::FormatError(TiffFormatError::Format(fmt_err)))
|
||||||
|
}
|
||||||
|
jpeg::Error::Unsupported(_) => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnknownInterpretation,
|
||||||
|
)),
|
||||||
|
jpeg::Error::Internal(_) => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnknownInterpretation,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DecodingResult::U8(res_img))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
|
pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
|
||||||
self.initialize_strip_decoder()?;
|
self.initialize_strip_decoder()?;
|
||||||
|
|
||||||
@@ -1045,6 +1110,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Decodes the entire image and return it as a Vector
|
/// Decodes the entire image and return it as a Vector
|
||||||
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
||||||
|
if self.compression_method == CompressionMethod::ModernJPEG {
|
||||||
|
return self.read_jpeg();
|
||||||
|
}
|
||||||
|
|
||||||
self.initialize_strip_decoder()?;
|
self.initialize_strip_decoder()?;
|
||||||
let rows_per_strip =
|
let rows_per_strip =
|
||||||
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
|
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
|
||||||
@@ -1057,7 +1126,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
for i in 0..usize::try_from(self.strip_count()?)? {
|
for i in 0..usize::try_from(self.strip_count()?)? {
|
||||||
self.read_strip_to_buffer(result.as_buffer(samples_per_strip * i))?;
|
self.read_strip_to_buffer(result.as_buffer(samples_per_strip * i))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +187,14 @@ pub trait EndianReader: Read {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// # READERS
|
||||||
|
///
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## Deflate Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that decompresses DEFLATE streams
|
/// Reader that decompresses DEFLATE streams
|
||||||
pub struct DeflateReader {
|
pub struct DeflateReader {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
@@ -233,6 +241,10 @@ impl EndianReader for DeflateReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## LZW Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
/// Reader that decompresses LZW streams
|
||||||
pub struct LZWReader {
|
pub struct LZWReader {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
@@ -306,6 +318,79 @@ impl EndianReader for LZWReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
|
||||||
|
///
|
||||||
|
|
||||||
|
pub(crate) struct JpegReader {
|
||||||
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
}
|
||||||
|
impl JpegReader {
|
||||||
|
/// Constructs new JpegReader wrapping a SmartReader.
|
||||||
|
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
||||||
|
/// central location, the constructor accepts this data as `jpeg_tables` here containing either
|
||||||
|
/// or both.
|
||||||
|
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
|
||||||
|
/// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
|
||||||
|
/// which is also at the beginning of the remaining JPEG image data and would
|
||||||
|
/// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
|
||||||
|
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
||||||
|
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
||||||
|
/// this has to be removed as well (last two bytes of `jpeg_tables`).
|
||||||
|
|
||||||
|
pub fn new<R>(
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
|
length: u32,
|
||||||
|
jpeg_tables: &Option<Vec<u8>>,
|
||||||
|
) -> io::Result<JpegReader>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
let order = reader.byte_order;
|
||||||
|
|
||||||
|
// Read jpeg image data
|
||||||
|
let mut segment = vec![0; length as usize];
|
||||||
|
reader.read_exact(&mut segment[..])?;
|
||||||
|
|
||||||
|
match jpeg_tables {
|
||||||
|
Some(tables) => {
|
||||||
|
let mut jpeg_data = tables.clone();
|
||||||
|
let truncated_length = jpeg_data.len() - 2;
|
||||||
|
jpeg_data.truncate(truncated_length);
|
||||||
|
jpeg_data.extend_from_slice(&mut segment[2..]);
|
||||||
|
|
||||||
|
Ok(JpegReader {
|
||||||
|
buffer: io::Cursor::new(jpeg_data),
|
||||||
|
byte_order: order,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => Ok(JpegReader {
|
||||||
|
buffer: io::Cursor::new(segment),
|
||||||
|
byte_order: order,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for JpegReader {
|
||||||
|
// #[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianReader for JpegReader {
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## PackBits Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that unpacks Apple's `PackBits` format
|
/// Reader that unpacks Apple's `PackBits` format
|
||||||
pub struct PackBitsReader {
|
pub struct PackBitsReader {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
@@ -378,6 +463,10 @@ impl EndianReader for PackBitsReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## SmartReader Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that is aware of the byte order.
|
/// Reader that is aware of the byte order.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SmartReader<R>
|
pub struct SmartReader<R>
|
||||||
|
|||||||
@@ -45,9 +45,12 @@ pub enum TiffFormatError {
|
|||||||
InvalidTagValueType(Tag),
|
InvalidTagValueType(Tag),
|
||||||
RequiredTagNotFound(Tag),
|
RequiredTagNotFound(Tag),
|
||||||
UnknownPredictor(u16),
|
UnknownPredictor(u16),
|
||||||
|
ByteExpected(Value),
|
||||||
UnsignedIntegerExpected(Value),
|
UnsignedIntegerExpected(Value),
|
||||||
SignedIntegerExpected(Value),
|
SignedIntegerExpected(Value),
|
||||||
InflateError(InflateError),
|
InflateError(InflateError),
|
||||||
|
Format(String),
|
||||||
|
RequiredTagEmpty(Tag),
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Do not match against this variant. It may get removed.
|
/// Do not match against this variant. It may get removed.
|
||||||
__NonExhaustive,
|
__NonExhaustive,
|
||||||
@@ -69,6 +72,7 @@ impl fmt::Display for TiffFormatError {
|
|||||||
UnknownPredictor(ref predictor) => {
|
UnknownPredictor(ref predictor) => {
|
||||||
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
||||||
}
|
}
|
||||||
|
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val),
|
||||||
UnsignedIntegerExpected(ref val) => {
|
UnsignedIntegerExpected(ref val) => {
|
||||||
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
||||||
}
|
}
|
||||||
@@ -76,6 +80,8 @@ impl fmt::Display for TiffFormatError {
|
|||||||
write!(fmt, "Expected signed integer, {:?} found.", val)
|
write!(fmt, "Expected signed integer, {:?} found.", val)
|
||||||
}
|
}
|
||||||
InflateError(_) => write!(fmt, "Failed to decode inflate data."),
|
InflateError(_) => write!(fmt, "Failed to decode inflate data."),
|
||||||
|
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||||
|
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||||
__NonExhaustive => unreachable!(),
|
__NonExhaustive => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//! # Related Links
|
//! # Related Links
|
||||||
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||||
|
|
||||||
|
extern crate jpeg;
|
||||||
extern crate miniz_oxide;
|
extern crate miniz_oxide;
|
||||||
extern crate weezl;
|
extern crate weezl;
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
Threshholding = 263, // TODO add support
|
Threshholding = 263, // TODO add support
|
||||||
XResolution = 282,
|
XResolution = 282,
|
||||||
YResolution = 283,
|
YResolution = 283,
|
||||||
|
// JPEG
|
||||||
|
JPEGTables = 347,
|
||||||
// Advanced tags
|
// Advanced tags
|
||||||
Predictor = 317,
|
Predictor = 317,
|
||||||
TileWidth = 322,
|
TileWidth = 322,
|
||||||
@@ -130,6 +132,7 @@ pub enum Type(u16) {
|
|||||||
LONG = 4,
|
LONG = 4,
|
||||||
RATIONAL = 5,
|
RATIONAL = 5,
|
||||||
SBYTE = 6,
|
SBYTE = 6,
|
||||||
|
UNDEFINED = 7,
|
||||||
SSHORT = 8,
|
SSHORT = 8,
|
||||||
SLONG = 9,
|
SLONG = 9,
|
||||||
SRATIONAL = 10,
|
SRATIONAL = 10,
|
||||||
@@ -141,6 +144,8 @@ pub enum Type(u16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
|
/// for reference.
|
||||||
pub enum CompressionMethod(u16) {
|
pub enum CompressionMethod(u16) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
@@ -148,6 +153,8 @@ pub enum CompressionMethod(u16) {
|
|||||||
Fax4 = 4,
|
Fax4 = 4,
|
||||||
LZW = 5,
|
LZW = 5,
|
||||||
JPEG = 6,
|
JPEG = 6,
|
||||||
|
// "Extended JPEG" or "new JPEG" style
|
||||||
|
ModernJPEG = 7,
|
||||||
Deflate = 8,
|
Deflate = 8,
|
||||||
OldDeflate = 0x80B2,
|
OldDeflate = 0x80B2,
|
||||||
PackBits = 0x8005,
|
PackBits = 0x8005,
|
||||||
|
|||||||
Reference in new issue
Block a user