@@ -85,6 +85,7 @@ pub(crate) struct Image {
8585 pub tile_attributes : Option < TileAttributes > ,
8686 pub chunk_offsets : Vec < u64 > ,
8787 pub chunk_bytes : Vec < u64 > ,
88+ pub chroma_subsampling : ( u16 , u16 ) ,
8889}
8990
9091/// Describes how to read a tile-aligned portion of the image.
@@ -275,6 +276,25 @@ impl Image {
275276 . transpose ( ) ?
276277 . unwrap_or ( PlanarConfiguration :: Chunky ) ;
277278
279+ let ycbcr_subsampling = tag_reader. find_tag_uint_vec :: < u16 > ( Tag :: ChromaSubsampling ) ?;
280+
281+ let chroma_subsampling = if let Some ( subsamples) = & ycbcr_subsampling {
282+ let [ a, b] = subsamples. as_slice ( ) else {
283+ return Err ( TiffError :: FormatError ( TiffFormatError :: InvalidCountForTag (
284+ Tag :: ChromaSubsampling ,
285+ subsamples. len ( ) ,
286+ ) ) ) ;
287+ } ;
288+
289+ // ImageWidth and ImageLength are constrained to be integer multiples of
290+ // YCbCrSubsampleHoriz and YCbCrSubsampleVert respectively. TileWidth and TileLength
291+ // have the same constraints. RowsPerStrip must be an integer multiple of
292+ // YCbCrSubsampleVert.
293+ ( * a, * b)
294+ } else {
295+ ( 2 , 2 )
296+ } ;
297+
278298 let planes = match planar_config {
279299 PlanarConfiguration :: Chunky => 1 ,
280300 PlanarConfiguration :: Planar => samples,
@@ -386,6 +406,7 @@ impl Image {
386406 tile_attributes,
387407 chunk_offsets,
388408 chunk_bytes,
409+ chroma_subsampling,
389410 } )
390411 }
391412
@@ -715,6 +736,23 @@ impl Image {
715736 ) ) ;
716737 }
717738
739+ // Only this color type interprets the tag, which is defined with a default of (2, 2)
740+ if matches ! ( color, ColorType :: YCbCr ( _) ) && self . chroma_subsampling != ( 1 , 1 ) {
741+ // The JPEG library does upsampling for us and defines its buffers correctly
742+ // (presumably). All other compression schemes are not supported..
743+ //
744+ // NOTE: as explained in <fa225e820b96bef35f01bf4685654beeb4a8df0c> we may be better
745+ // off supporting this tag by consistently upsampling, not by adjusting the buffer
746+ // size. At least as a default this makes more sense and is much more permissive in
747+ // case the compression stream disagrees with the tags (we would not have enough / or
748+ // the wrong buffer layout if we only asked for subsampled planes in a planar layout).
749+ if !matches ! ( self . compression_method, CompressionMethod :: ModernJPEG ) {
750+ return Err ( TiffError :: UnsupportedError (
751+ TiffUnsupportedError :: ChromaSubsampling ,
752+ ) ) ;
753+ }
754+ }
755+
718756 Ok ( ReadoutLayout {
719757 planar_config : self . planar_config ,
720758 color,
0 commit comments