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 tile_attributes: Option<TileAttributes>,
|
||||||
pub chunk_offsets: Vec<u64>,
|
pub chunk_offsets: Vec<u64>,
|
||||||
pub chunk_bytes: Vec<u64>,
|
pub chunk_bytes: Vec<u64>,
|
||||||
|
pub chroma_subsampling: (u16, u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes how to read a tile-aligned portion of the image.
|
/// Describes how to read a tile-aligned portion of the image.
|
||||||
@@ -275,6 +276,25 @@ impl Image {
|
|||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(PlanarConfiguration::Chunky);
|
.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 {
|
let planes = match planar_config {
|
||||||
PlanarConfiguration::Chunky => 1,
|
PlanarConfiguration::Chunky => 1,
|
||||||
PlanarConfiguration::Planar => samples,
|
PlanarConfiguration::Planar => samples,
|
||||||
@@ -386,6 +406,7 @@ impl Image {
|
|||||||
tile_attributes,
|
tile_attributes,
|
||||||
chunk_offsets,
|
chunk_offsets,
|
||||||
chunk_bytes,
|
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 {
|
Ok(ReadoutLayout {
|
||||||
planar_config: self.planar_config,
|
planar_config: self.planar_config,
|
||||||
color,
|
color,
|
||||||
|
|||||||
@@ -860,6 +860,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
tile_attributes: None,
|
tile_attributes: None,
|
||||||
chunk_offsets: Vec::new(),
|
chunk_offsets: Vec::new(),
|
||||||
chunk_bytes: Vec::new(),
|
chunk_bytes: Vec::new(),
|
||||||
|
chroma_subsampling: (2, 2),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
decoder.next_image()?;
|
decoder.next_image()?;
|
||||||
|
|||||||
+10
-1
@@ -11,7 +11,10 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
decoder::ifd::Entry,
|
decoder::ifd::Entry,
|
||||||
error::{TiffResult, UsageError},
|
error::{TiffResult, UsageError},
|
||||||
tags::{CompressionMethod, ExtraSamples, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
|
tags::{
|
||||||
|
CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, ResolutionUnit,
|
||||||
|
SampleFormat, Tag, Type,
|
||||||
|
},
|
||||||
Directory, TiffError, TiffFormatError,
|
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())?;
|
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(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?;
|
||||||
|
|
||||||
encoder.write_tag(
|
encoder.write_tag(
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ quick_error! {
|
|||||||
InvalidTypeForTag {
|
InvalidTypeForTag {
|
||||||
display("tag has invalid type")
|
display("tag has invalid type")
|
||||||
}
|
}
|
||||||
|
InvalidCountForTag(tag: Tag, len: usize) {
|
||||||
|
display("tag `{tag:?}` with incorrect number of elements ({len}) encountered")
|
||||||
|
}
|
||||||
StripTileTagConflict {
|
StripTileTagConflict {
|
||||||
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
|
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) {
|
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
|
||||||
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
||||||
}
|
}
|
||||||
|
ChromaSubsampling {
|
||||||
|
display("chroma subsampling of YCbCr color is unsupported")
|
||||||
|
}
|
||||||
MisalignedTileBoundaries {
|
MisalignedTileBoundaries {
|
||||||
display("tile rows are not aligned to byte boundaries")
|
display("tile rows are not aligned to byte boundaries")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ pub enum Tag(u16) unknown(
|
|||||||
SMaxSampleValue = 341, // TODO add support
|
SMaxSampleValue = 341, // TODO add support
|
||||||
// JPEG
|
// JPEG
|
||||||
JPEGTables = 347,
|
JPEGTables = 347,
|
||||||
|
// Subsampling
|
||||||
|
#[doc(alias = "YCbCrSubsampling")]
|
||||||
|
ChromaSubsampling = 530, // TODO add support
|
||||||
|
#[doc(alias = "YCbCrPositioning")]
|
||||||
|
ChromaPositioning = 531, // TODO add support
|
||||||
// GeoTIFF
|
// GeoTIFF
|
||||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||||
|
|||||||
Reference in new issue
Block a user