Merge pull request #319 from image-rs/ycbcr-subsampling
Recognize Ycbcr/Chroma subsampling tags
This commit is contained in:
5 files changed
+60
-1
No files matched your search
@@ -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.
|
||||
@@ -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,
|
||||
@@ -386,6 +406,7 @@ impl Image {
|
||||
tile_attributes,
|
||||
chunk_offsets,
|
||||
chunk_bytes,
|
||||
chroma_subsampling,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
+10
-1
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -128,6 +128,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)
|
||||
|
||||
Reference in new issue
Block a user