Compare commits

...
38 Commits
Author SHA1 Message Date
Andreas Molzer ef7592cf82 Add changelog and metadata for 0.7.2 2022-04-11 18:22:39 +02:00
Jonathan Behrens 7cdfd154ba Merge pull request #155 from Aaron1011/update/jpeg-decoder
Update jpeg-decoder to 0.2.1
2022-02-09 00:44:56 -05:00
Aaron Hill fa0d6eb99f Update jpeg-decoder to 0.2.1 2022-02-08 19:55:40 -05:00
Jonathan Behrens a780da0b34 Merge pull request #154 from fintelia/decoder-refactor
Decoder refactor
2022-01-15 17:04:11 -05:00
Jonathan Behrens ca56836a79 Add back fuzzer generated testcases 2022-01-15 15:11:54 -05:00
Jonathan Behrens 2c7c8891f4 Remove use half-open range pattern which is unstable on rustc 1.48 2022-01-12 20:46:17 -05:00
Jonathan Behrens 01df985ef8 Fix formatting 2022-01-12 20:13:23 -05:00
Jonathan Behrens 593479e5e3 Avoid reading tags in read_jpeg 2022-01-12 19:56:08 -05:00
Jonathan Behrens bc2055243a Add consistency check between StripOffsets and StripByteCounts 2022-01-12 19:56:08 -05:00
Jonathan Behrens 7a7d4f0305 Avoid unused warning 2022-01-12 19:56:08 -05:00
Jonathan Behrens 720e6586b9 Make read_ifd and read_entry stateless 2022-01-12 19:56:08 -05:00
Jonathan Behrens 47080293db Move image loading to Image::from_reader 2022-01-12 19:56:02 -05:00
Jonathan Behrens b385a6e752 Move some decoder fields into a new Image struct 2022-01-12 19:50:06 -05:00
Jonathan Behrens 6ba5553d20 Compute tile attributes on demand 2022-01-12 19:50:06 -05:00
Jonathan Behrens a36cd6bd90 chunk_{offsets,bytes} to replace {tile,strip}_{offsets,bytes} 2022-01-12 19:50:06 -05:00
Jonathan Behrens 63e6ef8bba Add current_chunk to replace strip_index and current_tile 2022-01-12 19:50:06 -05:00
Jonathan Behrens 2d10cff97e Reading fields shouldn't need the entire Decoder 2022-01-12 19:50:06 -05:00
Jonathan Behrens 23fe194ad9 Remove read_ifd_offset_nonrepeating 2022-01-12 19:50:06 -05:00
Jonathan Behrens 2d572c0370 Remove Decoder::byte_order 2022-01-12 19:50:06 -05:00
Jonathan Behrens 4c942ca690 Read JPEGTables ahead of time 2022-01-12 19:50:06 -05:00
Jonathan Behrens a19d35233a Read predictor ahead of time 2022-01-12 19:50:06 -05:00
Jonathan Behrens dc95ce10ac Remove some extremely brittle tests 2022-01-12 19:49:55 -05:00
Jonathan Behrens 31da6737b9 More refactoring 2022-01-12 19:45:41 -05:00
Jonathan Behrens e9aa39d895 Initialize Decoder in new instead of init 2022-01-12 19:45:41 -05:00
HeroicKatora 77f15b5c0a Merge pull request #153 from mindQ-GmbH-Co-KG/compression-release
fixed deflate and packbits
2022-01-11 18:53:12 +01:00
Pretorer 6f89033fd3 moved namespace qualifier 2022-01-11 15:51:34 +01:00
Pretorer 9df18a1674 fixed deflate and packbits 2022-01-11 15:43:00 +01:00
HeroicKatora 37cfcf82aa Merge pull request #151 from mindQ-GmbH-Co-KG/compression-release
Add TIFF compression during encoding and corresponding tests
2022-01-10 22:30:19 +01:00
Pretorer 1d8cbbc6a1 add Packbits compression 2022-01-10 11:57:59 +01:00
Pretorer 9f1e3d7a89 moved compression into TiffWriter 2022-01-10 11:55:08 +01:00
Jonathan Behrens fb4c119b88 Merge pull request #152 from fintelia/refactor-decode
Refactor strip/tile decoding
2022-01-05 21:48:14 -05:00
Jonathan Behrens 3242d328d1 Reformat 2021-12-22 12:06:12 -05:00
Jonathan Behrens d6c42dd60a Some refactoring of strip/tile decoding 2021-12-22 12:05:38 -05:00
Christopher Gundler de95ac35a2 Remove "CompressionError" 2021-12-22 12:29:39 +01:00
Christopher Gundler 30bf508983 Avoid allocations 2021-12-22 11:10:13 +01:00
Christopher Gundler bc7be68d0f Simplify tests 2021-12-22 10:48:52 +01:00
Christopher Gundler 99b2aa9b3a Add TIFF compression and corresponding tests 2021-12-21 16:47:11 +01:00
HeroicKatora d7d63e648b Merge pull request #149 from image-rs/release-0.7.1
Release 0.7.1
2021-09-22 17:47:02 +02:00
20 changed files with 1631 additions and 704 deletions

No files matched your search

+13
View File
@@ -1,3 +1,16 @@
# Version 0.7.2
New features:
* Encoding with `ImageEncoder` now takes an optional compressor argument,
allowing compressed encoding. See the methods
`TiffEncoder::{new_image,write_image}_with_compression`.
* `jpeg_decoder` has been upgraded, now supports lossless JPEG.
Changes:
* Decoding now more consistently reads and interprets the initial IFD, instead
of performing _some_ interpretation lazily. (This change prepares fully lazy
and backwards seeking.)
# Version 0.7.1 # Version 0.7.1
New features: New features:
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tiff" name = "tiff"
version = "0.7.1" version = "0.7.2"
authors = [ authors = [
"ccgn", "ccgn",
"bvssvni <bvssvni@gmail.com>", "bvssvni <bvssvni@gmail.com>",
@@ -19,7 +19,7 @@ edition = "2018"
[dependencies] [dependencies]
weezl = "0.1.0" weezl = "0.1.0"
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false } jpeg = { package = "jpeg-decoder", version = "0.2.4", default-features = false }
flate2 = "1.0.20" flate2 = "1.0.20"
[dev-dependencies] [dev-dependencies]
+5 -5
View File
@@ -17,7 +17,7 @@ TIFF decoding and encoding library in pure Rust
This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type. This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type.
| `PhotometricInterpretation` | UINT Format | IEEEFP Format | | `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|-|-|-| | --------------------------- | --------------------------------------- | ------------------------- |
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | | `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | | `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) | | `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
@@ -30,11 +30,11 @@ This table lists photometric interpretations and sample formats which are suppor
### Compressions ### Compressions
| | Decoding | Encoding | | | Decoding | Encoding |
|-|-|-| | -------- | -------- | -------- |
| None | ✓ | ✓ | | None | ✓ | ✓ |
| LZW | ✓ | | | LZW | ✓ | ✓ |
| Deflate | ✓ | | | Deflate | ✓ | ✓ |
| PackBits | ✓ | | | PackBits | ✓ | ✓ |
## Not yet supported ## Not yet supported
+1 -1
View File
@@ -33,7 +33,7 @@ fn main() {
def.data, def.data,
|b, input| b.iter(|| read_image(input)), |b, input| b.iter(|| read_image(input)),
); );
}; }
let mut c = Criterion::default().configure_from_args(); let mut c = Criterion::default().configure_from_args();
let mut group = c.benchmark_group("tiff-lzw"); let mut group = c.benchmark_group("tiff-lzw");
+82 -61
View File
@@ -341,14 +341,15 @@ impl Entry {
pub fn val<R: Read + Seek>( pub fn val<R: Read + Seek>(
&self, &self,
limits: &super::Limits, limits: &super::Limits,
decoder: &mut super::Decoder<R>, bigtiff: bool,
reader: &mut SmartReader<R>,
) -> TiffResult<Value> { ) -> TiffResult<Value> {
// Case 1: there are no values so we can return immediately. // Case 1: there are no values so we can return immediately.
if self.count == 0 { if self.count == 0 {
return Ok(List(Vec::new())); return Ok(List(Vec::new()));
} }
let bo = decoder.byte_order(); let bo = reader.byte_order();
let tag_size = match self.type_ { let tag_size = match self.type_ {
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
@@ -373,7 +374,7 @@ impl Entry {
// Case 2: there is one value. // Case 2: there is one value.
if self.count == 1 { if self.count == 1 {
// 2a: the value is 5-8 bytes and we're in BigTiff mode. // 2a: the value is 5-8 bytes and we're in BigTiff mode.
if decoder.bigtiff && value_bytes > 4 && value_bytes <= 8 { if bigtiff && value_bytes > 4 && value_bytes <= 8 {
return Ok(match self.type_ { return Ok(match self.type_ {
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?), Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?), Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
@@ -419,36 +420,36 @@ impl Entry {
} }
} }
Type::LONG8 => { Type::LONG8 => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
UnsignedBig(decoder.read_long8()?) UnsignedBig(reader.read_u64()?)
} }
Type::SLONG8 => { Type::SLONG8 => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
SignedBig(decoder.read_slong8()?) SignedBig(reader.read_i64()?)
} }
Type::DOUBLE => { Type::DOUBLE => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
Double(decoder.read_double()?) Double(reader.read_f64()?)
} }
Type::RATIONAL => { Type::RATIONAL => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
Rational(decoder.read_long()?, decoder.read_long()?) Rational(reader.read_u32()?, reader.read_u32()?)
} }
Type::SRATIONAL => { Type::SRATIONAL => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
SRational(decoder.read_slong()?, decoder.read_slong()?) SRational(reader.read_i32()?, reader.read_i32()?)
} }
Type::IFD => Ifd(self.r(bo).read_u32()?), Type::IFD => Ifd(self.r(bo).read_u32()?),
Type::IFD8 => { Type::IFD8 => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
IfdBig(decoder.read_long8()?) IfdBig(reader.read_u64()?)
} }
Type::__NonExhaustive => unreachable!(), Type::__NonExhaustive => unreachable!(),
}); });
} }
// Case 3: There is more than one value, but it fits in the offset field. // Case 3: There is more than one value, but it fits in the offset field.
if value_bytes <= 4 || decoder.bigtiff && value_bytes <= 8 { if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
match self.type_ { match self.type_ {
Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?), Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?),
Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?), Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?),
@@ -535,64 +536,80 @@ impl Entry {
match self.type_ { match self.type_ {
// TODO check if this could give wrong results // TODO check if this could give wrong results
// at a different endianess of file/computer. // at a different endianess of file/computer.
Type::BYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(u64::from(decoder.read_byte()?))) let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
Ok(UnsignedBig(u64::from(buf[0])))
}), }),
Type::SBYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(decoder.read_byte()? as i8))) Ok(SignedBig(i64::from(reader.read_i8()? as i8)))
}), }),
Type::SHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(u64::from(decoder.read_short()?))) Ok(UnsignedBig(u64::from(reader.read_u16()?)))
}), }),
Type::SSHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(decoder.read_sshort()?))) Ok(SignedBig(i64::from(reader.read_i16()?)))
}), }),
Type::LONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Unsigned(decoder.read_long()?)) Ok(Unsigned(reader.read_u32()?))
}), }),
Type::SLONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Signed(decoder.read_slong()?)) Ok(Signed(reader.read_i32()?))
}), }),
Type::FLOAT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Float(decoder.read_float()?)) Ok(Float(reader.read_f32()?))
}), }),
Type::DOUBLE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Double(decoder.read_double()?)) Ok(Double(reader.read_f64()?))
}), }),
Type::RATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::RATIONAL => {
Ok(Rational(decoder.read_long()?, decoder.read_long()?)) self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
})
}
Type::SRATIONAL => {
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
})
}
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(reader.read_u64()?))
}), }),
Type::SRATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?)) Ok(SignedBig(reader.read_i64()?))
}), }),
Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(decoder.read_long8()?)) Ok(Ifd(reader.read_u32()?))
}), }),
Type::SLONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(decoder.read_slong8()?)) Ok(IfdBig(reader.read_u64()?))
}),
Type::IFD => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
Ok(Ifd(decoder.read_long()?))
}),
Type::IFD8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
Ok(IfdBig(decoder.read_long8()?))
}),
Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
Ok(Byte(u8::from(decoder.read_byte()?)))
}), }),
Type::UNDEFINED => {
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
Ok(Byte(buf[0]))
})
}
Type::ASCII => { Type::ASCII => {
let n = usize::try_from(self.count)?; let n = usize::try_from(self.count)?;
if n > limits.decoding_buffer_size { if n > limits.decoding_buffer_size {
return Err(TiffError::LimitsExceeded); return Err(TiffError::LimitsExceeded);
} }
if decoder.bigtiff { if bigtiff {
decoder.goto_offset_u64(self.r(bo).read_u64()?)? reader.goto_offset(self.r(bo).read_u64()?)?
} else { } else {
decoder.goto_offset(self.r(bo).read_u32()?)? reader.goto_offset(self.r(bo).read_u32()?.into())?
} }
let string = decoder.read_string(n)?;
Ok(Ascii(string)) let mut out = vec![0; n];
reader.read_exact(&mut out)?;
// Strings may be null-terminated, so we trim anything downstream of the null byte
if let Some(first) = out.iter().position(|&b| b == 0) {
out.truncate(first);
}
Ok(Ascii(String::from_utf8(out)?))
} }
Type::__NonExhaustive => unreachable!(), Type::__NonExhaustive => unreachable!(),
} }
@@ -603,13 +620,14 @@ impl Entry {
&self, &self,
value_count: u64, value_count: u64,
bo: ByteOrder, bo: ByteOrder,
bigtiff: bool,
limits: &super::Limits, limits: &super::Limits,
decoder: &mut super::Decoder<R>, reader: &mut SmartReader<R>,
decode_fn: F, decode_fn: F,
) -> TiffResult<Value> ) -> TiffResult<Value>
where where
R: Read + Seek, R: Read + Seek,
F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>, F: Fn(&mut SmartReader<R>) -> TiffResult<Value>,
{ {
let value_count = usize::try_from(value_count)?; let value_count = usize::try_from(value_count)?;
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() { if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
@@ -617,13 +635,16 @@ impl Entry {
} }
let mut v = Vec::with_capacity(value_count); let mut v = Vec::with_capacity(value_count);
if decoder.bigtiff {
decoder.goto_offset_u64(self.r(bo).read_u64()?)? let offset = if bigtiff {
self.r(bo).read_u64()?
} else { } else {
decoder.goto_offset(self.r(bo).read_u32()?)? self.r(bo).read_u32()?.into()
} };
reader.goto_offset(offset)?;
for _ in 0..value_count { for _ in 0..value_count {
v.push(decode_fn(decoder)?) v.push(decode_fn(reader)?)
} }
Ok(List(v)) Ok(List(v))
} }
+290
View File
@@ -0,0 +1,290 @@
use super::ifd::{Directory, Value};
use super::tag_reader::TagReader;
use super::Limits;
use super::{stream::SmartReader, ChunkType};
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
use crate::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
use std::convert::{TryFrom, TryInto};
use std::io::{Read, Seek};
#[derive(Debug)]
pub(crate) struct StripDecodeState {
pub rows_per_strip: u32,
}
#[derive(Debug)]
/// Computed values useful for tile decoding
pub(crate) struct TileAttributes {
pub image_width: usize,
pub image_height: usize,
pub samples_per_pixel: usize,
pub tile_width: usize,
pub tile_length: usize,
}
impl TileAttributes {
pub fn tiles_across(&self) -> usize {
(self.image_width + self.tile_width - 1) / self.tile_width
}
pub fn tiles_down(&self) -> usize {
(self.image_height + self.tile_length - 1) / self.tile_length
}
fn padding_right(&self) -> usize {
self.tile_width - self.image_width % self.tile_width
}
fn padding_down(&self) -> usize {
self.tile_length - self.image_height % self.tile_length
}
pub fn row_samples(&self) -> usize {
self.tile_width * self.samples_per_pixel
}
pub fn tile_samples(&self) -> usize {
self.tile_length * self.tile_width * self.samples_per_pixel
}
fn tile_strip_samples(&self) -> usize {
(self.tile_samples() * self.tiles_across())
- (self.padding_right() * self.tile_length * self.samples_per_pixel)
}
/// Returns the tile offset in the result buffer, counted in samples
pub fn get_offset(&self, tile: usize) -> usize {
let row = tile / self.tiles_across();
let column = tile % self.tiles_across();
(row * self.tile_strip_samples()) + (column * self.row_samples())
}
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
let row = tile / self.tiles_across();
let column = tile % self.tiles_across();
let padding_right = if column == self.tiles_across() - 1 {
self.padding_right()
} else {
0
};
let padding_down = if row == self.tiles_down() - 1 {
self.padding_down()
} else {
0
};
(padding_right, padding_down)
}
}
#[derive(Debug)]
pub(crate) struct Image {
pub ifd: Option<Directory>,
pub width: u32,
pub height: u32,
pub bits_per_sample: Vec<u8>,
#[allow(unused)]
pub samples: u8,
pub sample_format: Vec<SampleFormat>,
pub photometric_interpretation: PhotometricInterpretation,
pub compression_method: CompressionMethod,
pub predictor: Predictor,
pub jpeg_tables: Option<Vec<u8>>,
pub chunk_type: ChunkType,
pub strip_decoder: Option<StripDecodeState>,
pub tile_attributes: Option<TileAttributes>,
pub chunk_offsets: Vec<u64>,
pub chunk_bytes: Vec<u64>,
}
impl Image {
pub fn from_reader<R: Read + Seek>(
reader: &mut SmartReader<R>,
ifd: Directory,
limits: &Limits,
bigtiff: bool,
) -> TiffResult<Image> {
let mut tag_reader = TagReader {
reader,
limits,
ifd: &ifd,
bigtiff,
};
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
let photometric_interpretation = tag_reader
.find_tag(Tag::PhotometricInterpretation)?
.map(Value::into_u16)
.transpose()?
.and_then(PhotometricInterpretation::from_u16)
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
// Try to parse both the compression method and the number, format, and bits of the included samples.
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
let compression_method = match tag_reader.find_tag(Tag::Compression)? {
Some(val) => CompressionMethod::from_u16(val.into_u16()?)
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
None => CompressionMethod::None,
};
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
&& ifd.contains_key(&Tag::JPEGTables)
{
let vec = tag_reader
.find_tag(Tag::JPEGTables)?
.unwrap()
.into_u8_vec()?;
if vec.len() < 2 {
return Err(TiffError::FormatError(
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
));
}
Some(vec)
} else {
None
};
let samples = tag_reader
.find_tag(Tag::SamplesPerPixel)?
.map(Value::into_u16)
.transpose()?
.unwrap_or(1)
.try_into()?;
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
Some(vals) => {
let sample_format: Vec<_> = vals
.into_iter()
.map(SampleFormat::from_u16_exhaustive)
.collect();
// TODO: for now, only homogenous formats across samples are supported.
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
}
sample_format
}
None => vec![SampleFormat::Uint],
};
let bits_per_sample = match samples {
1 | 3 | 4 => tag_reader
.find_tag_uint_vec(Tag::BitsPerSample)?
.unwrap_or_else(|| vec![1]),
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()),
};
let predictor = tag_reader
.find_tag(Tag::Predictor)?
.map(Value::into_u16)
.transpose()?
.map(|p| {
Predictor::from_u16(p)
.ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
})
.transpose()?
.unwrap_or(Predictor::None);
let chunk_type;
let chunk_offsets;
let chunk_bytes;
let strip_decoder;
let tile_attributes;
match (
ifd.contains_key(&Tag::StripByteCounts),
ifd.contains_key(&Tag::StripOffsets),
ifd.contains_key(&Tag::TileByteCounts),
ifd.contains_key(&Tag::TileOffsets),
) {
(true, true, false, false) => {
chunk_type = ChunkType::Strip;
chunk_offsets = tag_reader
.find_tag(Tag::StripOffsets)?
.unwrap()
.into_u64_vec()?;
chunk_bytes = tag_reader
.find_tag(Tag::StripByteCounts)?
.unwrap()
.into_u64_vec()?;
let rows_per_strip = tag_reader
.find_tag(Tag::RowsPerStrip)?
.map(Value::into_u32)
.transpose()?
.unwrap_or(height);
strip_decoder = Some(StripDecodeState { rows_per_strip });
tile_attributes = None;
if chunk_offsets.len() != chunk_bytes.len() {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
}
(false, false, true, true) => {
chunk_type = ChunkType::Tile;
let tile_width =
usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
let tile_length =
usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
if tile_width == 0 {
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
} else if tile_length == 0 {
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
}
strip_decoder = None;
tile_attributes = Some(TileAttributes {
image_width: usize::try_from(width)?,
image_height: usize::try_from(height)?,
samples_per_pixel: bits_per_sample.len(),
tile_width,
tile_length,
});
chunk_offsets = tag_reader
.find_tag(Tag::TileOffsets)?
.unwrap()
.into_u64_vec()?;
chunk_bytes = tag_reader
.find_tag(Tag::TileByteCounts)?
.unwrap()
.into_u64_vec()?;
let tile = tile_attributes.as_ref().unwrap();
if chunk_offsets.len() != chunk_bytes.len()
|| chunk_offsets.len() != tile.tiles_down() * tile.tiles_across()
{
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
}
(_, _, _, _) => {
return Err(TiffError::FormatError(
TiffFormatError::StripTileTagConflict,
))
}
};
Ok(Image {
ifd: Some(ifd),
width,
height,
bits_per_sample,
samples,
sample_format,
photometric_interpretation,
compression_method,
jpeg_tables,
predictor,
chunk_type,
strip_decoder,
tile_attributes,
chunk_offsets,
chunk_bytes,
})
}
}
+276 -462
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -339,6 +339,14 @@ where
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> { pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
SmartReader { reader, byte_order } SmartReader { reader, byte_order }
} }
pub fn into_inner(self) -> R {
self.reader
}
}
impl<R: Read + Seek> SmartReader<R> {
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
self.seek(io::SeekFrom::Start(offset)).map(|_| ())
}
} }
impl<R> EndianReader for SmartReader<R> impl<R> EndianReader for SmartReader<R>
+45
View File
@@ -0,0 +1,45 @@
use std::convert::TryFrom;
use std::io::{Read, Seek};
use crate::tags::Tag;
use crate::{TiffError, TiffFormatError, TiffResult};
use super::ifd::{Directory, Value};
use super::stream::SmartReader;
use super::Limits;
pub(crate) struct TagReader<'a, R: Read + Seek> {
pub reader: &'a mut SmartReader<R>,
pub ifd: &'a Directory,
pub limits: &'a Limits,
pub bigtiff: bool,
}
impl<'a, R: Read + Seek> TagReader<'a, R> {
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
Ok(match self.ifd.get(&tag) {
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
None => None,
})
}
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
match self.find_tag(tag)? {
Some(val) => Ok(val),
None => Err(TiffError::FormatError(
TiffFormatError::RequiredTagNotFound(tag),
)),
}
}
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
self.find_tag(tag)?
.map(|v| v.into_u64_vec())
.transpose()?
.map(|v| {
v.into_iter()
.map(|u| {
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
})
.collect()
})
.transpose()
}
}
+83
View File
@@ -0,0 +1,83 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
use std::io::Write;
/// The Deflate algorithm used to compress image data in TIFF files.
#[derive(Debug, Clone, Copy)]
pub struct Deflate {
level: FlateCompression,
}
/// The level of compression used by the Deflate algorithm.
/// It allows trading compression ratio for compression speed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum DeflateLevel {
/// The fastest possible compression mode.
Fast = 1,
/// The conserative choice between speed and ratio.
Balanced = 6,
/// The best compression available with Deflate.
Best = 9,
}
impl Default for DeflateLevel {
fn default() -> Self {
DeflateLevel::Balanced
}
}
impl Deflate {
/// Create a new deflate compressor with a specific level of compression.
pub fn with_level(level: DeflateLevel) -> Self {
Self {
level: FlateCompression::new(level as u32),
}
}
}
impl Default for Deflate {
fn default() -> Self {
Self::with_level(DeflateLevel::default())
}
}
impl Compression for Deflate {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
fn get_algorithm(&self) -> Compressor {
Compressor::Deflate(self.clone())
}
}
impl CompressionAlgorithm for Deflate {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
let mut encoder = ZlibEncoder::new(writer, self.level);
encoder.write_all(&bytes)?;
encoder.try_finish()?;
Ok(encoder.total_out())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_deflate() {
const EXPECTED_COMPRESSED_DATA: [u8; 64] = [
0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02,
0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD,
0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B,
0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91,
0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13,
];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Deflate::default().write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
}
}
+47
View File
@@ -0,0 +1,47 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use std::io::Write;
use weezl::encode::Encoder as LZWEncoder;
/// The LZW algorithm used to compress image data in TIFF files.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Lzw;
impl Compression for Lzw {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
fn get_algorithm(&self) -> Compressor {
Compressor::Lzw(*self)
}
}
impl CompressionAlgorithm for Lzw {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
let result = encoder.into_stream(writer).encode_all(bytes);
let byte_count = result.bytes_written as u64;
result.status.map(|_| byte_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_lzw() {
const EXPECTED_COMPRESSED_DATA: [u8; 63] = [
0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43,
0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31,
0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69,
0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A,
0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01,
];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Lzw::default().write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
}
}
+60
View File
@@ -0,0 +1,60 @@
use crate::tags::CompressionMethod;
use std::io::{self, Write};
mod deflate;
mod lzw;
mod packbits;
mod uncompressed;
pub use self::deflate::{Deflate, DeflateLevel};
pub use self::lzw::Lzw;
pub use self::packbits::Packbits;
pub use self::uncompressed::Uncompressed;
/// An algorithm used for compression
pub trait CompressionAlgorithm {
/// The algorithm writes data directly into the writer.
/// It returns the total number of bytes written.
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
}
/// An algorithm used for compression with associated enums and optional configurations.
pub trait Compression: CompressionAlgorithm {
/// The corresponding tag to the algorithm.
const COMPRESSION_METHOD: CompressionMethod;
/// Method to optain a type that can store each variant of comression algorithm.
fn get_algorithm(&self) -> Compressor;
}
/// An enum to store each compression algorithm.
pub enum Compressor {
Uncompressed(Uncompressed),
Lzw(Lzw),
Deflate(Deflate),
Packbits(Packbits),
}
impl Default for Compressor {
/// The default compression strategy does not apply any compression.
fn default() -> Self {
Compressor::Uncompressed(Uncompressed::default())
}
}
impl CompressionAlgorithm for Compressor {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
match self {
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
}
}
}
#[cfg(test)]
mod tests {
pub const TEST_DATA: &'static [u8] =
b"This is a string for checking various compression algorithms.";
}
+214
View File
@@ -0,0 +1,214 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use std::io::{BufWriter, Error, ErrorKind, Write};
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
///
/// [^note]: PackBits is often ineffective on continuous tone images,
/// including many grayscale images. In such cases, it is better
/// to leave the image uncompressed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Packbits;
impl Compression for Packbits {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits;
fn get_algorithm(&self) -> Compressor {
Compressor::Packbits(*self)
}
}
impl CompressionAlgorithm for Packbits {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
// Inspired by https://github.com/skirridsystems/packbits
const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks
const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte
// Encoding for header byte based on number of bytes represented.
fn encode_diff(n: u8) -> u8 {
n - 1
}
fn encode_rept(n: u8) -> u8 {
let var = 256 - (n - 1) as u16;
var as u8
}
fn write_u8<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
writer.write(&[byte]).map(|byte_count| byte_count as u64)
}
let mut bufwriter = BufWriter::new(writer);
let mut bytes_written = 0u64; // The number of bytes written into the writer
let mut offset: Option<u64> = None; // The index of the first byte written into the writer
let mut src_index: usize = 0; // Index of the current byte
let mut src_count = bytes.len(); //The number of bytes remaining to be compressed
let mut in_run = false; // Indication whether counting of similar bytes is performed
let mut run_index = 0u8; // Distance into pending bytes that a run starts
let mut bytes_pending = 0u8; // Bytes looked at but not yet output
let mut pending_index = 0usize; // Index of the first pending byte
let mut curr_byte: u8; // Byte currently being considered
let mut last_byte: u8; // Previous byte
// Need at least one byte to compress
if src_count == 0 {
return Err(Error::new(ErrorKind::WriteZero, "write zero"));
}
// Prime compressor with first character.
last_byte = bytes[src_index];
src_index += 1;
bytes_pending += 1;
while src_count - 1 != 0 {
src_count -= 1;
curr_byte = bytes[src_index];
src_index += 1;
bytes_pending += 1;
if in_run {
if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) {
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?);
write_u8(&mut bufwriter, last_byte)?;
bytes_written += 2;
bytes_pending = 1;
pending_index = src_index - 1;
run_index = 0;
in_run = false;
}
} else {
if bytes_pending > MAX_BYTES {
// We have as much differing data as we can output in one chunk.
// Output MAX_BYTES leaving one byte.
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
bytes_written += 1 + MAX_BYTES as u64;
pending_index += MAX_BYTES as usize;
bytes_pending -= MAX_BYTES;
run_index = bytes_pending - 1; // A run could start here
} else if curr_byte == last_byte {
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
// This is a worthwhile run
if run_index != 0 {
// Flush differing data out of input buffer
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
bufwriter
.write(&bytes[pending_index..pending_index + run_index as usize])?;
bytes_written += 1 + run_index as u64;
}
bytes_pending -= run_index; // Length of run
in_run = true;
}
} else {
run_index = bytes_pending - 1; // A run could start here
}
}
last_byte = curr_byte;
}
// Output the remainder
if in_run {
bytes_written += 2;
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?);
write_u8(&mut bufwriter, last_byte)?;
} else {
bytes_written += 1 + bytes_pending as u64;
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?;
}
bufwriter.flush()?;
Ok(bytes_written)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_packbits_single_byte() {
// compress single byte
const UNCOMPRESSED_DATA: [u8; 1] = [0x3F];
const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, &UNCOMPRESSED_DATA)
.unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits_rept() {
// compress buffer with repetitive sequence
const UNCOMPRESSED_DATA: &'static [u8] =
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs.";
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, &UNCOMPRESSED_DATA)
.unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits_large_rept_nonrept() {
// compress buffer with large repetitive and non-repetitive sequence
let mut data = b"This st".to_vec();
for _i in 0..158 {
data.push(b'r');
}
data.extend_from_slice(b"ing hangs.");
for i in 0..158 {
data.push(i);
}
const EXPECTED_COMPRESSED_DATA: [u8; 182] = [
0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69,
0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, data.as_slice())
.unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits() {
// compress teststring
const EXPECTED_COMPRESSED_DATA: &'static [u8] =
b"\x3CThis is a string for checking various compression algorithms.";
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, &TEST_DATA)
.unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
}
+37
View File
@@ -0,0 +1,37 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use std::io::Write;
/// The default algorithm which does not compress at all.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Uncompressed;
impl Compression for Uncompressed {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
fn get_algorithm(&self) -> Compressor {
Compressor::Uncompressed(*self)
}
}
impl CompressionAlgorithm for Uncompressed {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
writer.write(bytes).map(|byte_count| byte_count as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_no_compression() {
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Uncompressed::default()
.write_to(&mut writer, TEST_DATA)
.unwrap();
assert_eq!(TEST_DATA, compressed_data);
}
}
+83 -10
View File
@@ -12,14 +12,16 @@ use std::{
use crate::{ use crate::{
error::TiffResult, error::TiffResult,
tags::{self, ResolutionUnit, Tag}, tags::{CompressionMethod, ResolutionUnit, Tag},
}; };
pub mod colortype; pub mod colortype;
pub mod compression;
mod tiff_value; mod tiff_value;
mod writer; mod writer;
use self::colortype::*; use self::colortype::*;
use self::compression::*;
use self::writer::*; use self::writer::*;
/// Encoder for Tiff and BigTiff files. /// Encoder for Tiff and BigTiff files.
@@ -98,11 +100,22 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
&mut self, &mut self,
width: u32, width: u32,
height: u32, height: u32,
) -> TiffResult<ImageEncoder<W, C, K>> { ) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::new(encoder, width, height) ImageEncoder::new(encoder, width, height)
} }
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
pub fn new_image_with_compression<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
) -> TiffResult<ImageEncoder<W, C, K, D>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::with_compression(encoder, width, height, compression)
}
/// Convenience function to write an entire image from memory. /// Convenience function to write an entire image from memory.
pub fn write_image<C: ColorType>( pub fn write_image<C: ColorType>(
&mut self, &mut self,
@@ -117,6 +130,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?; let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
image.write_data(data) image.write_data(data)
} }
/// Convenience function to write an entire image from memory with a given compression.
pub fn write_image_with_compression<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
data: &[C::Inner],
) -> TiffResult<()>
where
[C::Inner]: TiffValue,
{
let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C, K, D> =
ImageEncoder::with_compression(encoder, width, height, compression)?;
image.write_data(data)
}
} }
/// Low level interface to encode ifd directories. /// Low level interface to encode ifd directories.
@@ -216,6 +246,11 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
Ok(offset) Ok(offset)
} }
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
fn last_written(&self) -> u64 {
self.writer.last_written()
}
fn finish_internal(&mut self) -> TiffResult<()> { fn finish_internal(&mut self) -> TiffResult<()> {
let ifd_pointer = self.write_directory()?; let ifd_pointer = self.write_directory()?;
let curr_pos = self.writer.offset(); let curr_pos = self.writer.offset();
@@ -277,7 +312,13 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
/// # } /// # }
/// ``` /// ```
/// You can also call write_data function wich will encode by strip and finish /// You can also call write_data function wich will encode by strip and finish
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> { pub struct ImageEncoder<
'a,
W: 'a + Write + Seek,
C: ColorType,
K: TiffKind,
D: Compression = Uncompressed,
> {
encoder: DirectoryEncoder<'a, W, K>, encoder: DirectoryEncoder<'a, W, K>,
strip_idx: u64, strip_idx: u64,
strip_count: u64, strip_count: u64,
@@ -288,23 +329,43 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
strip_offsets: Vec<K::OffsetType>, strip_offsets: Vec<K::OffsetType>,
strip_byte_count: Vec<K::OffsetType>, strip_byte_count: Vec<K::OffsetType>,
dropped: bool, dropped: bool,
compression: D,
_phantom: ::std::marker::PhantomData<C>, _phantom: ::std::marker::PhantomData<C>,
} }
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> { impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
fn new(mut encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self> { ImageEncoder<'a, W, T, K, D>
{
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self>
where
D: Default,
{
Self::with_compression(encoder, width, height, D::default())
}
fn with_compression(
mut encoder: DirectoryEncoder<'a, W, K>,
width: u32,
height: u32,
compression: D,
) -> TiffResult<Self> {
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?; let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN); let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
// Limit the strip size to prevent potential memory and security issues. // Limit the strip size to prevent potential memory and security issues.
// Also keep the multiple strip handling 'oiled' // Also keep the multiple strip handling 'oiled'
let rows_per_strip = (1_000_000 + row_bytes - 1) / row_bytes; let rows_per_strip = {
match D::COMPRESSION_METHOD {
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
_ => (1_000_000 + row_bytes - 1) / row_bytes,
}
};
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip; let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
encoder.write_tag(Tag::ImageWidth, width)?; encoder.write_tag(Tag::ImageWidth, width)?;
encoder.write_tag(Tag::ImageLength, height)?; encoder.write_tag(Tag::ImageLength, height)?;
encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?; encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?;
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?; encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect(); let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
@@ -332,6 +393,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
strip_offsets: Vec::new(), strip_offsets: Vec::new(),
strip_byte_count: Vec::new(), strip_byte_count: Vec::new(),
dropped: false, dropped: false,
compression: compression,
_phantom: ::std::marker::PhantomData, _phantom: ::std::marker::PhantomData,
}) })
} }
@@ -354,7 +416,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
where where
[T::Inner]: TiffValue, [T::Inner]: TiffValue,
{ {
// TODO: Compression
let samples = self.next_strip_sample_count(); let samples = self.next_strip_sample_count();
if u64::try_from(value.len())? != samples { if u64::try_from(value.len())? != samples {
return Err(io::Error::new( return Err(io::Error::new(
@@ -364,9 +425,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
.into()); .into());
} }
// Write the (possible compressed) data to the encoder.
let offset = self.encoder.write_data(value)?; let offset = self.encoder.write_data(value)?;
let byte_count = self.encoder.last_written() as usize;
self.strip_offsets.push(K::convert_offset(offset)?); self.strip_offsets.push(K::convert_offset(offset)?);
self.strip_byte_count.push(value.bytes().try_into()?); self.strip_byte_count.push(byte_count.try_into()?);
self.strip_idx += 1; self.strip_idx += 1;
Ok(()) Ok(())
@@ -392,12 +456,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
) )
.into()); .into());
} }
self.encoder
.writer
.set_compression(self.compression.get_algorithm());
let mut idx = 0; let mut idx = 0;
while self.next_strip_sample_count() > 0 { while self.next_strip_sample_count() > 0 {
let sample_count = usize::try_from(self.next_strip_sample_count())?; let sample_count = usize::try_from(self.next_strip_sample_count())?;
self.write_strip(&data[idx..idx + sample_count])?; self.write_strip(&data[idx..idx + sample_count])?;
idx += sample_count; idx += sample_count;
} }
self.encoder.writer.reset_compression();
self.finish()?; self.finish()?;
Ok(()) Ok(())
} }
@@ -475,7 +546,9 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
} }
} }
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> { impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop
for ImageEncoder<'a, W, C, K, D>
{
fn drop(&mut self) { fn drop(&mut self) {
if !self.dropped { if !self.dropped {
let _ = self.finish_internal(); let _ = self.finish_internal();
+147 -107
View File
@@ -1,4 +1,4 @@
use std::io::Write; use std::{borrow::Cow, io::Write, slice::from_ref};
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult}; use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
@@ -12,7 +12,18 @@ pub trait TiffValue {
fn bytes(&self) -> usize { fn bytes(&self) -> usize {
self.count() * usize::from(Self::BYTE_LEN) self.count() * usize::from(Self::BYTE_LEN)
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()>;
/// Access this value as an contiguous sequence of bytes.
/// If their is no trivial representation, allocate it on the heap.
fn data(&self) -> Cow<[u8]>;
/// Write this value to a TiffWriter.
/// While the default implementation will work in all cases, it may require unnecessary allocations.
/// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`.
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_bytes(&self.data())?;
Ok(())
}
} }
impl TiffValue for [u8] { impl TiffValue for [u8] {
@@ -23,9 +34,8 @@ impl TiffValue for [u8] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
writer.write_bytes(self)?; Cow::Borrowed(self)
Ok(())
} }
} }
@@ -37,10 +47,8 @@ impl TiffValue for [i8] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::i8_as_ne_bytes(self); Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -52,10 +60,8 @@ impl TiffValue for [u16] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::u16_as_ne_bytes(self); Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -67,10 +73,8 @@ impl TiffValue for [i16] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::i16_as_ne_bytes(self); Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -82,10 +86,8 @@ impl TiffValue for [u32] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::u32_as_ne_bytes(self); Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -97,10 +99,8 @@ impl TiffValue for [i32] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::i32_as_ne_bytes(self); Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -112,10 +112,8 @@ impl TiffValue for [u64] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::u64_as_ne_bytes(self); Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -127,10 +125,8 @@ impl TiffValue for [i64] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
let slice = bytecast::i64_as_ne_bytes(self); Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -142,11 +138,9 @@ impl TiffValue for [f32] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
// We write using nativeedian so this sould be safe // We write using native endian so this should be safe
let slice = bytecast::f32_as_ne_bytes(self); Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
} }
} }
@@ -158,75 +152,9 @@ impl TiffValue for [f64] {
self.len() self.len()
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn data(&self) -> Cow<[u8]> {
// We write using nativeedian so this sould be safe // We write using native endian so this should be safe
let slice = bytecast::f64_as_ne_bytes(self); Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [Ifd] {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::IFD;
fn count(&self) -> usize {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
}
impl TiffValue for [Ifd8] {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::IFD8;
fn count(&self) -> usize {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
}
impl TiffValue for [Rational] {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::RATIONAL;
fn count(&self) -> usize {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
}
impl TiffValue for [SRational] {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::SRATIONAL;
fn count(&self) -> usize {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
} }
} }
@@ -242,6 +170,10 @@ impl TiffValue for u8 {
writer.write_u8(*self)?; writer.write_u8(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(from_ref(self))
}
} }
impl TiffValue for i8 { impl TiffValue for i8 {
@@ -256,6 +188,10 @@ impl TiffValue for i8 {
writer.write_i8(*self)?; writer.write_i8(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for u16 { impl TiffValue for u16 {
@@ -270,6 +206,10 @@ impl TiffValue for u16 {
writer.write_u16(*self)?; writer.write_u16(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for i16 { impl TiffValue for i16 {
@@ -284,6 +224,10 @@ impl TiffValue for i16 {
writer.write_i16(*self)?; writer.write_i16(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for u32 { impl TiffValue for u32 {
@@ -298,6 +242,10 @@ impl TiffValue for u32 {
writer.write_u32(*self)?; writer.write_u32(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for i32 { impl TiffValue for i32 {
@@ -312,6 +260,10 @@ impl TiffValue for i32 {
writer.write_i32(*self)?; writer.write_i32(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for u64 { impl TiffValue for u64 {
@@ -326,6 +278,10 @@ impl TiffValue for u64 {
writer.write_u64(*self)?; writer.write_u64(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for i64 { impl TiffValue for i64 {
@@ -340,6 +296,10 @@ impl TiffValue for i64 {
writer.write_i64(*self)?; writer.write_i64(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for f32 { impl TiffValue for f32 {
@@ -354,6 +314,10 @@ impl TiffValue for f32 {
writer.write_f32(*self)?; writer.write_f32(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for f64 { impl TiffValue for f64 {
@@ -368,6 +332,10 @@ impl TiffValue for f64 {
writer.write_f64(*self)?; writer.write_f64(*self)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
}
} }
impl TiffValue for Ifd { impl TiffValue for Ifd {
@@ -382,6 +350,10 @@ impl TiffValue for Ifd {
writer.write_u32(self.0)?; writer.write_u32(self.0)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
}
} }
impl TiffValue for Ifd8 { impl TiffValue for Ifd8 {
@@ -396,6 +368,10 @@ impl TiffValue for Ifd8 {
writer.write_u64(self.0)?; writer.write_u64(self.0)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
}
} }
impl TiffValue for Rational { impl TiffValue for Rational {
@@ -411,6 +387,14 @@ impl TiffValue for Rational {
writer.write_u32(self.d)?; writer.write_u32(self.d)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Owned({
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
[first_dword, second_dword].concat()
})
}
} }
impl TiffValue for SRational { impl TiffValue for SRational {
@@ -426,6 +410,14 @@ impl TiffValue for SRational {
writer.write_i32(self.d)?; writer.write_i32(self.d)?;
Ok(()) Ok(())
} }
fn data(&self) -> Cow<[u8]> {
Cow::Owned({
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
[first_dword, second_dword].concat()
})
}
} }
impl TiffValue for str { impl TiffValue for str {
@@ -445,6 +437,17 @@ impl TiffValue for str {
Err(TiffError::FormatError(TiffFormatError::InvalidTag)) Err(TiffError::FormatError(TiffFormatError::InvalidTag))
} }
} }
fn data(&self) -> Cow<[u8]> {
Cow::Owned({
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
let bytes: &[u8] = self.as_bytes();
[bytes, &[0]].concat()
} else {
vec![]
}
})
}
} }
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T { impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
@@ -458,8 +461,45 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
(*self).write(writer) (*self).write(writer)
} }
fn data(&self) -> Cow<[u8]> {
T::data(self)
}
} }
macro_rules! impl_tiff_value_for_contiguous_sequence {
($inner_type:ty; $bytes:expr; $field_type:expr) => {
impl $crate::encoder::TiffValue for [$inner_type] {
const BYTE_LEN: u8 = $bytes;
const FIELD_TYPE: Type = $field_type;
fn count(&self) -> usize {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
fn data(&self) -> Cow<[u8]> {
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
for x in self {
buf.extend_from_slice(&x.data());
}
Cow::Owned(buf)
}
}
};
}
impl_tiff_value_for_contiguous_sequence!(Ifd; 4; Type::IFD);
impl_tiff_value_for_contiguous_sequence!(Ifd8; 8; Type::IFD8);
impl_tiff_value_for_contiguous_sequence!(Rational; 8; Type::RATIONAL);
impl_tiff_value_for_contiguous_sequence!(SRational; 8; Type::SRATIONAL);
/// Type to represent tiff values of type `IFD` /// Type to represent tiff values of type `IFD`
#[derive(Clone)] #[derive(Clone)]
pub struct Ifd(pub u32); pub struct Ifd(pub u32);
+63 -23
View File
@@ -1,3 +1,4 @@
use crate::encoder::compression::*;
use crate::error::TiffResult; use crate::error::TiffResult;
use std::io::{self, Seek, SeekFrom, Write}; use std::io::{self, Seek, SeekFrom, Write};
@@ -42,87 +43,126 @@ pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<
pub struct TiffWriter<W> { pub struct TiffWriter<W> {
writer: W, writer: W,
offset: u64, offset: u64,
byte_count: u64,
compressor: Compressor,
} }
impl<W: Write> TiffWriter<W> { impl<W: Write> TiffWriter<W> {
pub fn new(writer: W) -> Self { pub fn new(writer: W) -> Self {
Self { writer, offset: 0 } Self {
writer,
offset: 0,
byte_count: 0,
compressor: Compressor::default(),
}
}
pub fn set_compression(&mut self, compressor: Compressor) {
self.compressor = compressor;
}
pub fn reset_compression(&mut self) {
self.compressor = Compressor::default();
} }
pub fn offset(&self) -> u64 { pub fn offset(&self) -> u64 {
self.offset self.offset
} }
pub fn last_written(&self) -> u64 {
self.byte_count
}
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> { pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
self.writer.write_all(bytes)?; self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
self.offset += bytes.len() as u64; self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> { pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 1; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> { pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 1; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> { pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 2; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> { pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 2; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 4; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> { pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 4; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> { pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 8; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> { pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 8; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?; self.byte_count = self
self.offset += 4; .compressor
.write_to(&mut self.writer, &u32::to_ne_bytes(n.to_bits()))?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> { pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?; self.byte_count = self
self.offset += 8; .compressor
.write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
+12
View File
@@ -10,6 +10,8 @@ use crate::tags::{
}; };
use crate::ColorType; use crate::ColorType;
use crate::weezl::LzwError;
/// Tiff error kinds. /// Tiff error kinds.
#[derive(Debug)] #[derive(Debug)]
pub enum TiffError { pub enum TiffError {
@@ -288,5 +290,15 @@ impl From<std::num::TryFromIntError> for TiffError {
} }
} }
impl From<LzwError> for TiffError {
fn from(err: LzwError) -> TiffError {
match err {
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
"LZW compressed data corrupted",
))),
}
}
}
/// Result of an image decoding/encoding process /// Result of an image decoding/encoding process
pub type TiffResult<T> = Result<T, TiffError>; pub type TiffResult<T> = Result<T, TiffError>;
+6 -33
View File
@@ -344,7 +344,7 @@ fn test_bad_strip_count() {
} }
#[test] #[test]
fn test_too_big_buffer_size() { fn fuzzer_testcase1() {
let image = [ let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1, 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
@@ -353,14 +353,7 @@ fn test_too_big_buffer_size() {
178, 178,
]; ];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let err = decoder.read_image().unwrap_err();
match err {
tiff::TiffError::LimitsExceeded => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
} }
#[test] #[test]
@@ -422,10 +415,7 @@ fn oom_2() {
} }
#[test] #[test]
fn invalid_jpeg_tag() { fn fuzzer_testcase2() {
use tiff::tags;
use tiff::{TiffError, TiffFormatError};
let image = [ let image = [
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0, 73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0, 0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
@@ -437,14 +427,7 @@ fn invalid_jpeg_tag() {
73, 73,
]; ];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let err = decoder.read_image().unwrap_err();
match err {
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
} }
#[test] #[test]
@@ -474,9 +457,7 @@ fn invalid_jpeg_tag_2() {
} }
#[test] #[test]
fn multiple_images_strip_height() { fn fuzzer_testcase3() {
use tiff::TiffError;
let image = [ let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22, 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
@@ -485,15 +466,7 @@ fn multiple_images_strip_height() {
255, 255, 255, 255,
]; ];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let _ = decoder.read_image().unwrap();
let err = decoder.read_image().unwrap_err();
match err {
TiffError::IntSizeError => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
} }
#[test] #[test]
+157
View File
@@ -0,0 +1,157 @@
extern crate tiff;
use std::io::{Cursor, Seek, Write};
use tiff::{
decoder::{Decoder, DecodingResult},
encoder::{
colortype::{self, ColorType},
compression::*,
TiffEncoder, TiffValue,
},
};
trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
const WIDTH: u32;
const HEIGHT: u32;
type Color: ColorType;
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
fn compress<C: Compression, W: Write + Seek>(
&self,
encoder: &mut TiffEncoder<W>,
compression: C,
) where
[<Self::Color as ColorType>::Inner]: TiffValue,
{
let image = encoder
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
.unwrap();
image.write_data(self.reference_data()).unwrap();
}
fn generate() -> Self {
assert_eq!(
Self::Color::BITS_PER_SAMPLE.len(),
NUM_CHANNELS,
"Incompatible color type"
);
let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
for x in 0..Self::WIDTH {
for y in 0..Self::HEIGHT {
data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
}
}
Self::from(data)
}
}
struct TestImageColor(Vec<u16>);
impl From<Vec<u16>> for TestImageColor {
fn from(value: Vec<u16>) -> Self {
Self(value)
}
}
impl TestImage<3> for TestImageColor {
const WIDTH: u32 = 1;
const HEIGHT: u32 = 7;
type Color = colortype::RGB16;
fn reference_data(&self) -> &[u16] {
&self.0
}
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
[val as <Self::Color as ColorType>::Inner; 3]
}
}
struct TestImageGrayscale(Vec<u8>);
impl From<Vec<u8>> for TestImageGrayscale {
fn from(value: Vec<u8>) -> Self {
Self(value)
}
}
impl TestImage<1> for TestImageGrayscale {
const WIDTH: u32 = 21;
const HEIGHT: u32 = 10;
type Color = colortype::Gray8;
fn reference_data(&self) -> &[u8] {
&self.0
}
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
[val as <Self::Color as ColorType>::Inner]
}
}
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
let mut data = Cursor::new(Vec::new());
let image_rgb = TestImageColor::generate();
let image_grayscale = TestImageGrayscale::generate();
// Encode tiff with compression
{
// Create a multipage image with 2 images
let mut encoder = TiffEncoder::new(&mut data).unwrap();
image_rgb.compress(&mut encoder, compression.clone());
image_grayscale.compress(&mut encoder, compression);
}
// Decode tiff
data.set_position(0);
{
let mut decoder = Decoder::new(data).unwrap();
// Check the RGB image
assert_eq!(
match decoder.read_image() {
Ok(DecodingResult::U16(image_data)) => image_data,
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
},
image_rgb.reference_data()
);
// Check the grayscale image
decoder.next_image().unwrap();
assert_eq!(
match decoder.read_image() {
Ok(DecodingResult::U8(image_data)) => image_data,
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
},
image_grayscale.reference_data()
);
}
}
#[test]
fn encode_decode_without_compression() {
encode_decode_with_compression(Uncompressed::default());
}
#[test]
fn encode_decode_with_lzw() {
encode_decode_with_compression(Lzw::default());
}
#[test]
fn encode_decode_with_deflate() {
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
}
#[test]
fn encode_decode_with_packbits() {
encode_decode_with_compression(Packbits::default());
}