diff --git a/CHANGES.md b/CHANGES.md index 08e145b..f0d4d0a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,12 @@ +# Version 0.5.0 + +* Added support for 32-bit and 64-bit decoded values. +* Added CMYK(16|32|64) color type support. +* Check many internal integer conversions to increase stability. This should + only lead to images being reported as faulty that would previously silently + break platform limits. If there are any false positives, please report them. +* Remove an erroneous check of decoded length in lzw compressed images. + # Version 0.4.0 * Several enumerations are now non_exhaustive for future extensions. diff --git a/Cargo.toml b/Cargo.toml index c0d2dd0..ee78534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiff" -version = "0.4.0" +version = "0.5.0" authors = [ "ccgn", "bvssvni ", diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 58965d1..444597e 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -198,9 +198,17 @@ impl Entry { if n > limits.decoding_buffer_size { return Err(TiffError::LimitsExceeded); } - decoder.goto_offset(self.r(bo).read_u32()?)?; - let string = decoder.read_string(n)?; - Ok(Ascii(string)) + if n <= 4 { + let mut buf = vec![0; n]; + self.r(bo).read_exact(&mut buf )?; + let v = String::from_utf8(buf)?; + let v = v.trim_matches(char::from(0)); + Ok(Ascii(v.into())) + } else { + decoder.goto_offset(self.r(bo).read_u32()?)?; + let string = decoder.read_string(n)?; + Ok(Ascii(string)) + } } _ => Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedDataType, diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 398280e..318af19 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -486,11 +486,13 @@ impl Decoder { /// Reads a string #[inline] pub fn read_string(&mut self, length: usize) -> TiffResult { - let mut out = String::with_capacity(length); - self.reader.read_to_string(&mut out)?; + let mut out = vec![0; length]; + self.reader.read_exact(&mut out)?; // Strings may be null-terminated, so we trim anything downstream of the null byte - let trimmed = out.bytes().take_while(|&n| n != 0).collect::>(); - Ok(String::from_utf8(trimmed)?) + if let Some(first) = out.iter().position(|&b| b == 0) { + out.truncate(first); + } + Ok(String::from_utf8(out)?) } /// Reads a TIFF IFA offset/value field @@ -646,7 +648,7 @@ impl Decoder { let (bytes, reader) = LZWReader::new( &mut self.reader, usize::try_from(length)?, - max_uncompressed_length + max_uncompressed_length * buffer.byte_len() )?; (bytes, Box::new(reader)) } @@ -838,7 +840,7 @@ impl Decoder { let buffer_size = usize::try_from(self.width)? * strip_height * self.bits_per_sample.len(); - if buffer.len() < buffer_size || usize::try_from(byte_count)? / buffer.byte_len() > buffer_size { + if buffer.len() < buffer_size { return Err(TiffError::FormatError( TiffFormatError::InconsistentSizesEncountered, )); diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 1d4ac3f..c99be5e 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -469,26 +469,9 @@ impl TiffEncoder { where [C::Inner]: TiffValue, { - let num_pix = usize::try_from(width)?.checked_mul(usize::try_from(height)?) - .ok_or_else(|| ::std::io::Error::new( - ::std::io::ErrorKind::InvalidInput, - "Image width * height exceeds usize"))?; - if data.len() < num_pix { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::InvalidData, - "Input data slice is undersized for provided dimensions").into()); - } - let encoder = DirectoryEncoder::new(&mut self.writer)?; - let mut image: ImageEncoder = ImageEncoder::new(encoder, width, height)?; - - let mut idx = 0; - while image.next_strip_sample_count() > 0 { - let sample_count = usize::try_from(image.next_strip_sample_count())?; - image.write_strip(&data[idx..idx + sample_count])?; - idx += sample_count; - } - image.finish() + let image: ImageEncoder = ImageEncoder::new(encoder, width, height)?; + image.write_data(data) } } @@ -610,10 +593,14 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> { /// # let mut file = std::io::Cursor::new(Vec::new()); /// # let image_data = vec![0; 100*100*3]; /// use tiff::encoder::*; +/// use tiff::tags::Tag; /// /// let mut tiff = TiffEncoder::new(&mut file).unwrap(); /// let mut image = tiff.new_image::(100, 100).unwrap(); /// +/// // You can encode tags here +/// image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap(); +/// /// let mut idx = 0; /// while image.next_strip_sample_count() > 0 { /// let sample_count = image.next_strip_sample_count() as usize; @@ -623,11 +610,13 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> { /// image.finish().unwrap(); /// # } /// ``` +/// You can also call write_data function wich will encode by strip and finish pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType> { encoder: DirectoryEncoder<'a, W>, strip_idx: u64, strip_count: u64, row_samples: u64, + width: u32, height: u32, rows_per_strip: u64, strip_offsets: Vec, @@ -672,6 +661,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { strip_idx: 0, row_samples, rows_per_strip, + width, height, strip_offsets: Vec::new(), strip_byte_count: Vec::new(), @@ -716,6 +706,30 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { Ok(()) } + /// Write strips from data + pub fn write_data(mut self, data: &[T::Inner]) -> TiffResult<()> + where + [T::Inner]: TiffValue, + { + let num_pix = usize::try_from(self.width)?.checked_mul(usize::try_from(self.height)?) + .ok_or_else(|| ::std::io::Error::new( + ::std::io::ErrorKind::InvalidInput, + "Image width * height exceeds usize"))?; + if data.len() < num_pix { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::InvalidData, + "Input data slice is undersized for provided dimensions").into()); + } + let mut idx = 0; + while self.next_strip_sample_count() > 0 { + let sample_count = usize::try_from(self.next_strip_sample_count())?; + self.write_strip(&data[idx..idx + sample_count])?; + idx += sample_count; + } + self.finish()?; + Ok(()) + } + /// Set image resolution pub fn resolution(&mut self, unit: ResolutionUnit, value: Rational) { self.encoder.write_tag(Tag::ResolutionUnit, unit.to_u16()).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 7dec5ba..9906732 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! lossless and lossy compression. //! //! # Related Links -//! * - The TIFF specification +//! * - The TIFF specification extern crate byteorder; extern crate lzw; diff --git a/src/tags.rs b/src/tags.rs index f13034d..3983f09 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -114,6 +114,11 @@ pub enum Tag(u16) unknown("A private or extension tag") { YResolution = 283, // Advanced tags Predictor = 317, + TileWidth = 322, + TileLength = 323, + TileOffsets = 324, + TileByteCounts = 325, + SampleFormat = 339, } } diff --git a/tests/decode_images.rs b/tests/decode_images.rs index f8642c2..90d77ac 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -171,6 +171,12 @@ fn test_decode_data() { } } +#[test] +fn issue_69() { + test_image_sum_u16("issue_69_lzw.tiff", ColorType::Gray(16), 1015486); + test_image_sum_u16("issue_69_packbits.tiff", ColorType::Gray(16), 1015486); +} + // TODO: GrayA support //#[test] //fn test_gray_alpha_u8() diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 6902cff..f080418 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -1,6 +1,6 @@ extern crate tiff; -use tiff::decoder::{Decoder, DecodingResult}; +use tiff::decoder::{Decoder, DecodingResult, ifd}; use tiff::encoder::{colortype, TiffEncoder, SRational}; use tiff::ColorType; use tiff::tags::Tag; @@ -24,14 +24,18 @@ fn encode_decode() { { let mut tiff = TiffEncoder::new(&mut file).unwrap(); - tiff.write_image::(100, 100, &image_data) + let mut image = tiff.new_image::(100, 100) .unwrap(); + image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap(); + image.write_data(&image_data).unwrap(); + } { file.seek(SeekFrom::Start(0)).unwrap(); let mut decoder = Decoder::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + assert_eq!(decoder.get_tag(Tag::Artist).unwrap(), ifd::Value::Ascii("Image-tiff".into())); if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { assert_eq!(image_data, img_res); } else { @@ -281,4 +285,4 @@ fn test_multipage_image() { // check the dimensions of the image in the second page assert_eq!(img_decoder.dimensions().unwrap(), (3, 3)); } -} +} \ No newline at end of file diff --git a/tests/images/issue_69_lzw.tiff b/tests/images/issue_69_lzw.tiff new file mode 100644 index 0000000..3870fc6 Binary files /dev/null and b/tests/images/issue_69_lzw.tiff differ diff --git a/tests/images/issue_69_packbits.tiff b/tests/images/issue_69_packbits.tiff new file mode 100644 index 0000000..224edc0 Binary files /dev/null and b/tests/images/issue_69_packbits.tiff differ