Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/decoder/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub(crate) struct Image {
pub tile_attributes: Option<TileAttributes>,
pub chunk_offsets: Vec<u64>,
pub chunk_bytes: Vec<u64>,
pub chroma_subsampling: (u16, u16),
}

/// Describes how to read a tile-aligned portion of the image.
Expand Down Expand Up @@ -275,6 +276,25 @@ impl Image {
.transpose()?
.unwrap_or(PlanarConfiguration::Chunky);

let ycbcr_subsampling = tag_reader.find_tag_uint_vec::<u16>(Tag::ChromaSubsampling)?;

let chroma_subsampling = if let Some(subsamples) = &ycbcr_subsampling {
let [a, b] = subsamples.as_slice() else {
return Err(TiffError::FormatError(TiffFormatError::InvalidCountForTag(
Tag::ChromaSubsampling,
subsamples.len(),
)));
};

// ImageWidth and ImageLength are constrained to be integer multiples of
// YCbCrSubsampleHoriz and YCbCrSubsampleVert respectively. TileWidth and TileLength
// have the same constraints. RowsPerStrip must be an integer multiple of
// YCbCrSubsampleVert.
(*a, *b)
} else {
(2, 2)
};

let planes = match planar_config {
PlanarConfiguration::Chunky => 1,
PlanarConfiguration::Planar => samples,
Expand Down Expand Up @@ -386,6 +406,7 @@ impl Image {
tile_attributes,
chunk_offsets,
chunk_bytes,
chroma_subsampling,
})
}

Expand Down Expand Up @@ -715,6 +736,23 @@ impl Image {
));
}

// Only this color type interprets the tag, which is defined with a default of (2, 2)
if matches!(color, ColorType::YCbCr(_)) && self.chroma_subsampling != (1, 1) {
// The JPEG library does upsampling for us and defines its buffers correctly
// (presumably). All other compression schemes are not supported..
//
// NOTE: as explained in <fa225e820b96bef35f01bf4685654beeb4a8df0c> we may be better
// off supporting this tag by consistently upsampling, not by adjusting the buffer
// size. At least as a default this makes more sense and is much more permissive in
// case the compression stream disagrees with the tags (we would not have enough / or
// the wrong buffer layout if we only asked for subsampled planes in a planar layout).
if !matches!(self.compression_method, CompressionMethod::ModernJPEG) {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::ChromaSubsampling,
));
}
}

Ok(ReadoutLayout {
planar_config: self.planar_config,
color,
Expand Down
1 change: 1 addition & 0 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,7 @@ impl<R: Read + Seek> Decoder<R> {
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
chroma_subsampling: (2, 2),
},
};
decoder.next_image()?;
Expand Down
11 changes: 10 additions & 1 deletion src/encoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use std::{
use crate::{
decoder::ifd::Entry,
error::{TiffResult, UsageError},
tags::{CompressionMethod, ExtraSamples, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
tags::{
CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, ResolutionUnit,
SampleFormat, Tag, Type,
},
Directory, TiffError, TiffFormatError,
};

Expand Down Expand Up @@ -565,6 +568,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,

encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE.to_u16())?;

if matches!(<T>::TIFF_VALUE, PhotometricInterpretation::YCbCr) {
// The default for this tag is 2,2 for subsampling but we do not support such a
// transformation. Instead all samples must be provided.
encoder.write_tag(Tag::ChromaSubsampling, &[1u16, 1u16][..])?;
}

encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?;

encoder.write_tag(
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ quick_error! {
InvalidTypeForTag {
display("tag has invalid type")
}
InvalidCountForTag(tag: Tag, len: usize) {
display("tag `{tag:?}` with incorrect number of elements ({len}) encountered")
}
StripTileTagConflict {
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
}
Expand Down Expand Up @@ -156,6 +159,9 @@ quick_error! {
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
display("unsupported photometric interpretation \"{interpretation:?}\"")
}
ChromaSubsampling {
display("chroma subsampling of YCbCr color is unsupported")
}
MisalignedTileBoundaries {
display("tile rows are not aligned to byte boundaries")
}
Expand Down
5 changes: 5 additions & 0 deletions src/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ pub enum Tag(u16) unknown(
SMaxSampleValue = 341, // TODO add support
// JPEG
JPEGTables = 347,
// Subsampling
#[doc(alias = "YCbCrSubsampling")]
ChromaSubsampling = 530, // TODO add support
#[doc(alias = "YCbCrPositioning")]
ChromaPositioning = 531, // TODO add support
// GeoTIFF
ModelPixelScaleTag = 33550, // (SoftDesk)
ModelTransformationTag = 34264, // (JPL Carto Group)
Expand Down
Loading