@@ -3,6 +3,7 @@ use crate::core::{ImageError, Result};
33use crate :: formats:: pixel_data:: PixelData ;
44use std:: path:: Path ;
55use tiff:: encoder:: { colortype, TiffEncoder } ;
6+ use tiff:: ColorType ;
67
78impl Image {
89 pub fn open_tiff < P : AsRef < Path > > ( path : P ) -> Result < Self > {
@@ -16,12 +17,21 @@ impl Image {
1617 ImageError :: FormatDetectionFailed ( format ! ( "TIFF dimensions error: {}" , e) )
1718 } ) ?;
1819
20+ // Samples-per-pixel comes from the TIFF colortype, not from the
21+ // decoded buffer (which is always flat). Without this, RGB TIFFs
22+ // get reported as 1-channel and downstream code treats the
23+ // interleaved R/G/B samples as 3× as many grayscale pixels.
24+ let colortype = decoder. colortype ( ) . map_err ( |e| {
25+ ImageError :: FormatDetectionFailed ( format ! ( "TIFF colortype error: {}" , e) )
26+ } ) ?;
27+ let samples = samples_per_pixel ( colortype) ;
28+
1929 let image = decoder
2030 . read_image ( )
2131 . map_err ( |e| ImageError :: FormatDetectionFailed ( format ! ( "TIFF read error: {}" , e) ) ) ?;
2232
23- let ( pixels, channels ) = decode_tiff_image ( image) ?;
24- let dimensions = build_tiff_dimensions ( width, height, channels ) ;
33+ let pixels = decode_tiff_image ( image) ?;
34+ let dimensions = build_tiff_dimensions ( width, height, samples ) ;
2535
2636 Ok ( Self {
2737 pixels,
@@ -41,18 +51,30 @@ impl Image {
4151 }
4252}
4353
44- fn decode_tiff_image ( image : tiff:: decoder:: DecodingResult ) -> Result < ( PixelData , usize ) > {
54+ fn samples_per_pixel ( colortype : ColorType ) -> usize {
55+ match colortype {
56+ ColorType :: Gray ( _) | ColorType :: Palette ( _) => 1 ,
57+ ColorType :: GrayA ( _) => 2 ,
58+ ColorType :: RGB ( _) | ColorType :: YCbCr ( _) | ColorType :: Lab ( _) => 3 ,
59+ ColorType :: RGBA ( _) | ColorType :: CMYK ( _) => 4 ,
60+ ColorType :: CMYKA ( _) => 5 ,
61+ ColorType :: Multiband { num_samples, .. } => num_samples as usize ,
62+ _ => 1 ,
63+ }
64+ }
65+
66+ fn decode_tiff_image ( image : tiff:: decoder:: DecodingResult ) -> Result < PixelData > {
4567 use tiff:: decoder:: DecodingResult ;
4668
4769 match image {
48- DecodingResult :: U8 ( data) => Ok ( ( PixelData :: U8 ( data) , 1 ) ) ,
49- DecodingResult :: U16 ( data) => Ok ( ( PixelData :: U16 ( data) , 1 ) ) ,
70+ DecodingResult :: U8 ( data) => Ok ( PixelData :: U8 ( data) ) ,
71+ DecodingResult :: U16 ( data) => Ok ( PixelData :: U16 ( data) ) ,
5072 DecodingResult :: U32 ( data) => {
5173 let converted: Vec < i32 > = data. iter ( ) . map ( |& v| v as i32 ) . collect ( ) ;
52- Ok ( ( PixelData :: I32 ( converted) , 1 ) )
74+ Ok ( PixelData :: I32 ( converted) )
5375 }
54- DecodingResult :: F32 ( data) => Ok ( ( PixelData :: F32 ( data) , 1 ) ) ,
55- DecodingResult :: F64 ( data) => Ok ( ( PixelData :: F64 ( data) , 1 ) ) ,
76+ DecodingResult :: F32 ( data) => Ok ( PixelData :: F32 ( data) ) ,
77+ DecodingResult :: F64 ( data) => Ok ( PixelData :: F64 ( data) ) ,
5678 _ => Err ( ImageError :: UnsupportedFormat ) ,
5779 }
5880}
@@ -84,6 +106,9 @@ fn write_tiff_image(
84106 ( PixelData :: F32 ( data) , 1 ) => {
85107 encoder. write_image :: < colortype:: Gray32Float > ( width, height, data)
86108 }
109+ ( PixelData :: F32 ( data) , 3 ) => {
110+ encoder. write_image :: < colortype:: RGB32Float > ( width, height, data)
111+ }
87112 _ => return Err ( ImageError :: UnsupportedFormat ) ,
88113 }
89114 . map_err ( |e| ImageError :: FormatDetectionFailed ( format ! ( "TIFF write error: {}" , e) ) )
@@ -147,6 +172,35 @@ mod tests {
147172 img. save ( tmp. path ( ) ) . unwrap ( ) ;
148173 }
149174
175+ #[ test]
176+ fn roundtrip_rgb_u8_reports_three_channels ( ) {
177+ let tmp = tmp_tiff ( ) ;
178+ let img = Image :: new ( PixelData :: U8 ( ( 0u8 ..48 ) . collect ( ) ) , vec ! [ 4usize , 4 , 3 ] ) ;
179+ img. save ( tmp. path ( ) ) . unwrap ( ) ;
180+
181+ let restored = Image :: open_tiff ( tmp. path ( ) ) . unwrap ( ) ;
182+ assert ! ( restored. is_rgb( ) , "RGB TIFF should round-trip with channels==3" ) ;
183+ assert_eq ! ( restored. channels( ) , 3 ) ;
184+ assert_eq ! ( restored. width( ) , 4 ) ;
185+ assert_eq ! ( restored. height( ) , 4 ) ;
186+ assert_eq ! ( restored. pixels. as_u8( ) . unwrap( ) . len( ) , 48 ) ;
187+ }
188+
189+ #[ test]
190+ fn roundtrip_rgb_u16_reports_three_channels ( ) {
191+ let tmp = tmp_tiff ( ) ;
192+ let img = Image :: new (
193+ PixelData :: U16 ( ( 0u16 ..48 ) . map ( |i| i * 1000 ) . collect ( ) ) ,
194+ vec ! [ 4usize , 4 , 3 ] ,
195+ ) ;
196+ img. save ( tmp. path ( ) ) . unwrap ( ) ;
197+
198+ let restored = Image :: open_tiff ( tmp. path ( ) ) . unwrap ( ) ;
199+ assert ! ( restored. is_rgb( ) ) ;
200+ assert_eq ! ( restored. channels( ) , 3 ) ;
201+ assert_eq ! ( restored. pixels. as_u16( ) . unwrap( ) . len( ) , 48 ) ;
202+ }
203+
150204 #[ test]
151205 fn save_rejects_unsupported_combos ( ) {
152206 let tmp = tmp_tiff ( ) ;
0 commit comments