Compare commits

...
Author SHA1 Message Date
A. Molzer d7d24d0f7c Changelog entry for tile treatment 2026-03-08 03:00:15 +01:00
A. Molzer 58e93fb8a3 Align tile detection to libtiff 2026-03-08 03:00:15 +01:00
Aurelia Molzer e8be8656f2 Merge pull request #353 from Shnatsel/non-exhaustive-compression
Make Compression enum non-exhaustive
2026-03-06 10:45:28 +00:00
Sergey "Shnatsel" Davidoff e1e9f36c5a Make Compression enum non-exhaustive 2026-03-06 09:49:10 +00:00
Aurelia Molzer 0ad653433a Merge pull request #352 from image-rs/iterate-over-raw-tiff-or-exif
Open a Tiff without interpreting the first directory
2026-03-06 08:34:19 +00:00
Aurelia Molzer 54dddc4943 Merge pull request #347 from image-rs/iterate-over-raw-tiff-or-exif
Enable iteration over raw, malformed tiff or exif
2026-03-06 07:31:13 +00:00
Aurelia Molzer 31c49ea6ef Merge pull request #351 from Shnatsel/semver-breaking-deflate-level
Implement DEFLATE compression level control properly, with a semver break
2026-03-05 20:12:27 +00:00
Sergey "Shnatsel" Davidoff 0362e6e31d Implement DEFLATE compression level control properly, with a semver break 2026-03-05 17:59:18 +00:00
Sergey "Shnatsel" Davidoff 0fe5b2804e Merge pull request #348 from Shnatsel/deflate-level
Expose precise DEFLATE level controls
2026-03-05 17:23:59 +00:00
Sergey "Shnatsel" Davidoff a589010099 Fix typo 2026-03-05 12:22:37 +00:00
Sergey "Shnatsel" Davidoff 53498074a4 Merge impl blocks 2026-03-05 12:22:25 +00:00
Sergey "Shnatsel" Davidoff cb543cded7 Expose precise DEFLATE level controls 2026-03-05 11:58:18 +00:00
8 changed files with 62 additions and 58 deletions

No files matched your search

+6
View File
@@ -11,6 +11,12 @@ Changes:
directory state if they fail. This includes the failure to interpret an IFD
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
Additions:
+2 -2
View File
@@ -4,7 +4,7 @@ extern crate tiff;
use criterion::{black_box, Criterion, Throughput};
use std::io::Cursor;
use tiff::encoder::colortype::RGB32Float;
use tiff::encoder::{Compression, DeflateLevel, Predictor, TiffEncoder};
use tiff::encoder::{Compression, Predictor, TiffEncoder};
const WIDTH: u32 = 500;
const HEIGHT: u32 = 500;
@@ -14,7 +14,7 @@ fn encode_image_f32_float_predictor_deflate(data: &[f32]) {
TiffEncoder::new(&mut buf)
.unwrap()
.with_predictor(Predictor::FloatingPoint)
.with_compression(Compression::Deflate(DeflateLevel::Fast))
.with_compression(Compression::Deflate(1))
.write_image::<RGB32Float>(WIDTH, HEIGHT, black_box(data))
.unwrap();
}
+26 -25
View File
@@ -330,18 +330,22 @@ impl Image {
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_offsets;
let chunk_bytes;
let strip_decoder;
let tile_attributes;
match (
ifd.contains(Tag::StripByteCounts),
ifd.contains(Tag::StripOffsets),
ifd.contains(Tag::TileByteCounts),
ifd.contains(Tag::TileOffsets),
) {
(true, true, false, false) => {
if !is_tiled && has_strips {
chunk_type = ChunkType::Strip;
chunk_offsets = tag_reader
@@ -382,15 +386,20 @@ impl Image {
},
));
}
}
(false, false, true, true) => {
} 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_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 {
@@ -404,14 +413,9 @@ impl Image {
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()?;
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();
@@ -421,8 +425,7 @@ impl Image {
));
}
if chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() * planes as usize
{
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)
@@ -431,12 +434,10 @@ impl Image {
},
));
}
}
(_, _, _, _) => {
} else {
return Err(TiffError::FormatError(
TiffFormatError::StripTileTagConflict,
))
}
));
};
let mut sample_format = sample_format;
+20 -17
View File
@@ -7,24 +7,27 @@ 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]
#[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 {
/// 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 {
level: FlateCompression::new(level as u32),
}
@@ -33,7 +36,7 @@ impl Deflate {
impl Default for Deflate {
fn default() -> Self {
Self::with_level(DeflateLevel::default())
Self::with_level(6)
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ mod packbits;
mod uncompressed;
#[cfg(feature = "deflate")]
pub use self::deflate::{Deflate, DeflateLevel};
pub use self::deflate::Deflate;
#[cfg(feature = "lzw")]
pub use self::lzw::Lzw;
+2 -3
View File
@@ -38,9 +38,8 @@ use self::writer::*;
/// [Predictor::Horizontal] is for integer sample types.
/// [Predictor::FloatingPoint] is for floating-point sample types (f32, f64).
pub type Predictor = crate::tags::Predictor;
#[cfg(feature = "deflate")]
pub type DeflateLevel = compression::DeflateLevel;
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Default)]
pub enum Compression {
#[default]
@@ -48,7 +47,7 @@ pub enum Compression {
#[cfg(feature = "lzw")]
Lzw,
#[cfg(feature = "deflate")]
Deflate(DeflateLevel),
Deflate(u8),
Packbits,
}
+2 -6
View File
@@ -12,9 +12,7 @@ const REFERENCE_DIR: &str = "tests/libtiffpic";
const FILES: &[(&str, bool)] = &[
("tests/libtiffpic/caspian.tif", true),
("tests/libtiffpic/cramps.tif", true),
// FIXME: See <https://github.com/image-rs/image-tiff/issues/338>, this has tiles as
// StripOffsets and has a RowsPerStrip.
("tests/libtiffpic/cramps-tile.tif", false),
("tests/libtiffpic/cramps-tile.tif", true),
("tests/libtiffpic/CVS/Entries", false),
("tests/libtiffpic/CVS/Repository", false),
("tests/libtiffpic/CVS/Root", false),
@@ -90,9 +88,7 @@ const FILES: &[(&str, bool)] = &[
("tests/libtiffpic/pc260001.tif", true),
("tests/libtiffpic/quad-jpeg.tif", true),
("tests/libtiffpic/quad-lzw.tif", true),
// FIXME: See <https://github.com/image-rs/image-tiff/issues/338>, this has tiles as
// StripOffsets and has a RowsPerStrip.
("tests/libtiffpic/quad-tile.tif", false),
("tests/libtiffpic/quad-tile.tif", true),
("tests/libtiffpic/README", false),
("tests/libtiffpic/smallliz.tif", true),
("tests/libtiffpic/strike.tif", true),
+3 -4
View File
@@ -5,7 +5,6 @@ use tiff::{
decoder::{Decoder, DecodingResult},
encoder::{
colortype::{self, ColorType},
compression::*,
Compression, TiffEncoder, TiffValue,
},
};
@@ -148,9 +147,9 @@ fn encode_decode_with_lzw() {
#[test]
#[cfg(feature = "deflate")]
fn encode_decode_with_deflate() {
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
encode_decode_with_compression(Compression::Deflate(1));
encode_decode_with_compression(Compression::Deflate(6));
encode_decode_with_compression(Compression::Deflate(9));
}
#[test]