Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d24d0f7c | ||
|
|
58e93fb8a3 | ||
|
|
e8be8656f2 | ||
|
|
e1e9f36c5a | ||
|
|
0ad653433a | ||
|
|
54dddc4943 | ||
|
|
31c49ea6ef | ||
|
|
0362e6e31d | ||
|
|
0fe5b2804e | ||
|
|
a589010099 | ||
|
|
53498074a4 | ||
|
|
cb543cded7 |
No files matched your search
@@ -11,6 +11,12 @@ Changes:
|
|||||||
directory state if they fail. This includes the failure to interpret an IFD
|
directory state if they fail. This includes the failure to interpret an IFD
|
||||||
as a supported image in the case of `*_image` variants.
|
as a supported image in the case of `*_image` variants.
|
||||||
|
|
||||||
|
Compatibility:
|
||||||
|
- Changed how tiled TIFF files are detected. The presence of a TileWidth OR
|
||||||
|
TileHeight/TileLength tag now marks an image as tiled. Data is no retrieved
|
||||||
|
from either StripOffsets or TileOffsets with the latter taking precedence.
|
||||||
|
This is closer to the behavior of libtiff.
|
||||||
|
|
||||||
# Version 0.11.3
|
# Version 0.11.3
|
||||||
|
|
||||||
Additions:
|
Additions:
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ extern crate tiff;
|
|||||||
use criterion::{black_box, Criterion, Throughput};
|
use criterion::{black_box, Criterion, Throughput};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use tiff::encoder::colortype::RGB32Float;
|
use tiff::encoder::colortype::RGB32Float;
|
||||||
use tiff::encoder::{Compression, DeflateLevel, Predictor, TiffEncoder};
|
use tiff::encoder::{Compression, Predictor, TiffEncoder};
|
||||||
|
|
||||||
const WIDTH: u32 = 500;
|
const WIDTH: u32 = 500;
|
||||||
const HEIGHT: u32 = 500;
|
const HEIGHT: u32 = 500;
|
||||||
@@ -14,7 +14,7 @@ fn encode_image_f32_float_predictor_deflate(data: &[f32]) {
|
|||||||
TiffEncoder::new(&mut buf)
|
TiffEncoder::new(&mut buf)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_predictor(Predictor::FloatingPoint)
|
.with_predictor(Predictor::FloatingPoint)
|
||||||
.with_compression(Compression::Deflate(DeflateLevel::Fast))
|
.with_compression(Compression::Deflate(1))
|
||||||
.write_image::<RGB32Float>(WIDTH, HEIGHT, black_box(data))
|
.write_image::<RGB32Float>(WIDTH, HEIGHT, black_box(data))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-97
@@ -330,113 +330,114 @@ impl Image {
|
|||||||
PlanarConfiguration::Planar => samples,
|
PlanarConfiguration::Planar => samples,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Note: Not strictly according to specification. The specification says that tiled images
|
||||||
|
// must have their data encoded in `TileByteCounts` and `TileOffsets`. But `libtiff`
|
||||||
|
// processes tags one-by-one and with *either* of these it will set the flag for the image
|
||||||
|
// being tiled. Also it will just use whatever offsets are available. (I believe also using
|
||||||
|
// the first of the two tag pairs that's available—remember tags are encoded ascending).
|
||||||
|
let is_tiled = ifd.contains(Tag::TileWidth) || ifd.contains(Tag::TileLength);
|
||||||
|
let has_strips = ifd.contains(Tag::StripByteCounts) && ifd.contains(Tag::StripOffsets);
|
||||||
|
let has_tiles = ifd.contains(Tag::TileByteCounts) && ifd.contains(Tag::TileOffsets);
|
||||||
|
|
||||||
let chunk_type;
|
let chunk_type;
|
||||||
let chunk_offsets;
|
let chunk_offsets;
|
||||||
let chunk_bytes;
|
let chunk_bytes;
|
||||||
let strip_decoder;
|
let strip_decoder;
|
||||||
let tile_attributes;
|
let tile_attributes;
|
||||||
match (
|
|
||||||
ifd.contains(Tag::StripByteCounts),
|
|
||||||
ifd.contains(Tag::StripOffsets),
|
|
||||||
ifd.contains(Tag::TileByteCounts),
|
|
||||||
ifd.contains(Tag::TileOffsets),
|
|
||||||
) {
|
|
||||||
(true, true, false, false) => {
|
|
||||||
chunk_type = ChunkType::Strip;
|
|
||||||
|
|
||||||
chunk_offsets = tag_reader
|
if !is_tiled && has_strips {
|
||||||
.find_tag(Tag::StripOffsets)?
|
chunk_type = ChunkType::Strip;
|
||||||
.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() {
|
chunk_offsets = tag_reader
|
||||||
return Err(TiffError::FormatError(
|
.find_tag(Tag::StripOffsets)?
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
.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 rows_per_strip == 0 {
|
if chunk_offsets.len() != chunk_bytes.len() {
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if u32::try_from(chunk_offsets.len())?
|
|
||||||
!= height.div_ceil(rows_per_strip) * planes as u32
|
|
||||||
{
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::IncorrectChunkCount {
|
|
||||||
expected: height.div_ceil(rows_per_strip) * planes as u32,
|
|
||||||
found: chunk_offsets.len() as u32,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(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)?,
|
|
||||||
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() {
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() * planes as usize
|
|
||||||
{
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::IncorrectChunkCount {
|
|
||||||
expected: (tile.tiles_down() * tile.tiles_across() * planes as usize)
|
|
||||||
as u32,
|
|
||||||
found: chunk_offsets.len() as u32,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, _, _, _) => {
|
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::StripTileTagConflict,
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rows_per_strip == 0 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if u32::try_from(chunk_offsets.len())?
|
||||||
|
!= height.div_ceil(rows_per_strip) * planes as u32
|
||||||
|
{
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::IncorrectChunkCount {
|
||||||
|
expected: height.div_ceil(rows_per_strip) * planes as u32,
|
||||||
|
found: chunk_offsets.len() as u32,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if is_tiled && (has_strips || has_tiles) {
|
||||||
|
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()?)?;
|
||||||
|
|
||||||
|
// Prioritize tiles over strips, tiles are what we expect.
|
||||||
|
let (tag_offsets, tag_counts) = if has_tiles {
|
||||||
|
(Tag::TileOffsets, Tag::TileByteCounts)
|
||||||
|
} else {
|
||||||
|
(Tag::StripOffsets, Tag::StripByteCounts)
|
||||||
|
};
|
||||||
|
|
||||||
|
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)?,
|
||||||
|
tile_width,
|
||||||
|
tile_length,
|
||||||
|
});
|
||||||
|
|
||||||
|
chunk_offsets = tag_reader.find_tag(tag_offsets)?.unwrap().into_u64_vec()?;
|
||||||
|
chunk_bytes = tag_reader.find_tag(tag_counts)?.unwrap().into_u64_vec()?;
|
||||||
|
|
||||||
|
let tile = tile_attributes.as_ref().unwrap();
|
||||||
|
|
||||||
|
if chunk_offsets.len() != chunk_bytes.len() {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() * planes as usize {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::IncorrectChunkCount {
|
||||||
|
expected: (tile.tiles_down() * tile.tiles_across() * planes as usize)
|
||||||
|
as u32,
|
||||||
|
found: chunk_offsets.len() as u32,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::StripTileTagConflict,
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut sample_format = sample_format;
|
let mut sample_format = sample_format;
|
||||||
|
|||||||
@@ -7,24 +7,27 @@ pub struct Deflate {
|
|||||||
level: FlateCompression,
|
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]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub enum DeflateLevel {
|
|
||||||
/// The fastest possible compression mode.
|
|
||||||
Fast = 1,
|
|
||||||
/// The conserative choice between speed and ratio.
|
|
||||||
#[default]
|
|
||||||
Balanced = 6,
|
|
||||||
/// The best compression available with Deflate.
|
|
||||||
Best = 9,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deflate {
|
impl Deflate {
|
||||||
/// Create a new deflate compressor with a specific level of compression.
|
/// Create a new deflate compressor with a specific level of compression.
|
||||||
pub fn with_level(level: DeflateLevel) -> Self {
|
///
|
||||||
|
/// The valid levels are 1 through 9 inclusive. The default level is 6.
|
||||||
|
///
|
||||||
|
/// - `1` stands for light but fast compression
|
||||||
|
/// - `6` is the recommended balanced default
|
||||||
|
/// - `9` is maximum compression ratio at the cost of slow encoding
|
||||||
|
pub fn with_level(level: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
level: FlateCompression::new(level as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new deflate compressor with an exact level of compression.
|
||||||
|
///
|
||||||
|
/// `0` stands for uncompressed
|
||||||
|
/// `1` stands for light but fast compression
|
||||||
|
/// `6` is the recommended balanced default
|
||||||
|
/// `9` is maximum compression ratio at the cost of slow encoding
|
||||||
|
pub fn exact_level(level: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
level: FlateCompression::new(level as u32),
|
level: FlateCompression::new(level as u32),
|
||||||
}
|
}
|
||||||
@@ -33,7 +36,7 @@ impl Deflate {
|
|||||||
|
|
||||||
impl Default for Deflate {
|
impl Default for Deflate {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::with_level(DeflateLevel::default())
|
Self::with_level(6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ mod packbits;
|
|||||||
mod uncompressed;
|
mod uncompressed;
|
||||||
|
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub use self::deflate::{Deflate, DeflateLevel};
|
pub use self::deflate::Deflate;
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
pub use self::lzw::Lzw;
|
pub use self::lzw::Lzw;
|
||||||
|
|||||||
+2
-3
@@ -38,9 +38,8 @@ use self::writer::*;
|
|||||||
/// [Predictor::Horizontal] is for integer sample types.
|
/// [Predictor::Horizontal] is for integer sample types.
|
||||||
/// [Predictor::FloatingPoint] is for floating-point sample types (f32, f64).
|
/// [Predictor::FloatingPoint] is for floating-point sample types (f32, f64).
|
||||||
pub type Predictor = crate::tags::Predictor;
|
pub type Predictor = crate::tags::Predictor;
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
pub type DeflateLevel = compression::DeflateLevel;
|
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
#[derive(Clone, Copy, PartialEq, Default)]
|
#[derive(Clone, Copy, PartialEq, Default)]
|
||||||
pub enum Compression {
|
pub enum Compression {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -48,7 +47,7 @@ pub enum Compression {
|
|||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
Lzw,
|
Lzw,
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
Deflate(DeflateLevel),
|
Deflate(u8),
|
||||||
Packbits,
|
Packbits,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ const REFERENCE_DIR: &str = "tests/libtiffpic";
|
|||||||
const FILES: &[(&str, bool)] = &[
|
const FILES: &[(&str, bool)] = &[
|
||||||
("tests/libtiffpic/caspian.tif", true),
|
("tests/libtiffpic/caspian.tif", true),
|
||||||
("tests/libtiffpic/cramps.tif", true),
|
("tests/libtiffpic/cramps.tif", true),
|
||||||
// FIXME: See <https://github.com/image-rs/image-tiff/issues/338>, this has tiles as
|
("tests/libtiffpic/cramps-tile.tif", true),
|
||||||
// StripOffsets and has a RowsPerStrip.
|
|
||||||
("tests/libtiffpic/cramps-tile.tif", false),
|
|
||||||
("tests/libtiffpic/CVS/Entries", false),
|
("tests/libtiffpic/CVS/Entries", false),
|
||||||
("tests/libtiffpic/CVS/Repository", false),
|
("tests/libtiffpic/CVS/Repository", false),
|
||||||
("tests/libtiffpic/CVS/Root", false),
|
("tests/libtiffpic/CVS/Root", false),
|
||||||
@@ -90,9 +88,7 @@ const FILES: &[(&str, bool)] = &[
|
|||||||
("tests/libtiffpic/pc260001.tif", true),
|
("tests/libtiffpic/pc260001.tif", true),
|
||||||
("tests/libtiffpic/quad-jpeg.tif", true),
|
("tests/libtiffpic/quad-jpeg.tif", true),
|
||||||
("tests/libtiffpic/quad-lzw.tif", true),
|
("tests/libtiffpic/quad-lzw.tif", true),
|
||||||
// FIXME: See <https://github.com/image-rs/image-tiff/issues/338>, this has tiles as
|
("tests/libtiffpic/quad-tile.tif", true),
|
||||||
// StripOffsets and has a RowsPerStrip.
|
|
||||||
("tests/libtiffpic/quad-tile.tif", false),
|
|
||||||
("tests/libtiffpic/README", false),
|
("tests/libtiffpic/README", false),
|
||||||
("tests/libtiffpic/smallliz.tif", true),
|
("tests/libtiffpic/smallliz.tif", true),
|
||||||
("tests/libtiffpic/strike.tif", true),
|
("tests/libtiffpic/strike.tif", true),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use tiff::{
|
|||||||
decoder::{Decoder, DecodingResult},
|
decoder::{Decoder, DecodingResult},
|
||||||
encoder::{
|
encoder::{
|
||||||
colortype::{self, ColorType},
|
colortype::{self, ColorType},
|
||||||
compression::*,
|
|
||||||
Compression, TiffEncoder, TiffValue,
|
Compression, TiffEncoder, TiffValue,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -148,9 +147,9 @@ fn encode_decode_with_lzw() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
fn encode_decode_with_deflate() {
|
fn encode_decode_with_deflate() {
|
||||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
|
encode_decode_with_compression(Compression::Deflate(1));
|
||||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
|
encode_decode_with_compression(Compression::Deflate(6));
|
||||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
|
encode_decode_with_compression(Compression::Deflate(9));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user