@@ -225,7 +225,11 @@ public AnyBitmap Clone()
225225 public AnyBitmap Clone ( Rectangle rectangle )
226226 {
227227 var cloned = GetInternalImages ( ) . Select ( img => img . Clone ( x => x . Crop ( rectangle ) ) ) ;
228- return new AnyBitmap ( cloned ) ;
228+ var result = new AnyBitmap ( cloned ) ;
229+ // Cropping preserves the source color depth, so carry the original depth over (otherwise
230+ // it would fall back to the decoded 32bpp value).
231+ result . _originalBitsPerPixel = _originalBitsPerPixel ;
232+ return result ;
229233 }
230234
231235 /// <summary>
@@ -695,9 +699,15 @@ public AnyBitmap(Stream stream, bool preserveOriginalFormat)
695699 }
696700
697701 /// <summary>
698- ///
702+ /// Creates a new <see cref="AnyBitmap"/> by resizing <paramref name="original"/> to the
703+ /// given dimensions.
704+ /// <para><b>Color depth:</b> resizing resamples (blends) pixels, so <see cref="BitsPerPixel"/>
705+ /// of the result reflects the resized image and may differ from the source. In particular a
706+ /// source whose original depth is below 8bpp (e.g. a 1bpp black & white image) cannot exist
707+ /// below 8bpp in memory, so the resized result reports its actual decoded depth rather than the
708+ /// original low depth. The depth of 8/24/32/64bpp sources is otherwise preserved.</para>
699709 /// </summary>
700- /// <param name="original">The <see cref="AnyBitmap"/> from which to
710+ /// <param name="original">The <see cref="AnyBitmap"/> from which to
701711 /// create the new <see cref="AnyBitmap"/>.</param>
702712 /// <param name="width">The width of the new AnyBitmap.</param>
703713 /// <param name="height">The height of the new AnyBitmap.</param>
@@ -990,6 +1000,14 @@ public static AnyBitmap LoadAnyBitmapFromRGBBuffer(byte[] buffer, int width, int
9901000 /// </summary>
9911001 private int ? _originalBitsPerPixel = null ;
9921002
1003+ /// <summary>
1004+ /// The original color depth (bits per pixel) of each frame of a multi-page TIFF, in the same
1005+ /// order as the decoded frames. Populated while decoding the TIFF so that <see cref="GetAllFrames"/>
1006+ /// can report each frame's true source depth (pages of a TIFF may differ in depth). Remains
1007+ /// <c>null</c> for non-TIFF sources.
1008+ /// </summary>
1009+ private IReadOnlyList < int ? > _framesOriginalBitsPerPixel = null ;
1010+
9931011 //cache of the bits per pixel of the in-memory (decoded) image
9941012 private int InMemoryBitsPerPixel => _bitsPerPixel ??= GetFirstInternalImage ( ) . PixelType . BitsPerPixel ;
9951013
@@ -1085,14 +1103,26 @@ public IEnumerable<AnyBitmap> GetAllFrames
10851103 get
10861104 {
10871105 var images = GetInternalImages ( ) ;
1088- if ( images . Count == 1 )
1106+ IEnumerable < AnyBitmap > frames = images . Count == 1
1107+ ? ImageFrameCollectionToImages ( images [ 0 ] . Frames ) . Select ( x => ( AnyBitmap ) x )
1108+ : images . Select ( x => ( AnyBitmap ) x ) ;
1109+
1110+ // Each frame is a freshly-cast AnyBitmap. Carry the captured original source depth
1111+ // onto each frame. Only do this when this object captured an original depth (i.e. a
1112+ // TIFF loaded preserving its original format); otherwise leave frames as-is.
1113+ if ( _originalBitsPerPixel == null )
10891114 {
1090- return ImageFrameCollectionToImages ( images [ 0 ] . Frames ) . Select ( x => ( AnyBitmap ) x ) ;
1115+ return frames ;
10911116 }
1092- else
1117+
1118+ IReadOnlyList < int ? > perFrame = _framesOriginalBitsPerPixel ;
1119+ return frames . Select ( ( frame , index ) =>
10931120 {
1094- return images . Select ( x => ( AnyBitmap ) x ) ;
1095- }
1121+ frame . _originalBitsPerPixel = perFrame != null && index < perFrame . Count
1122+ ? perFrame [ index ]
1123+ : _originalBitsPerPixel ;
1124+ return frame ;
1125+ } ) ;
10961126 }
10971127 }
10981128
@@ -1375,7 +1405,11 @@ public static AnyBitmap RotateFlip(
13751405
13761406 image . Mutate ( x => x . RotateFlip ( rotateModeImgSharp , flipModeImgSharp ) ) ;
13771407
1378- return new AnyBitmap ( image ) ;
1408+ var result = new AnyBitmap ( image ) ;
1409+ // Rotating/flipping preserves the source color depth, so carry the original depth over
1410+ // (otherwise it would fall back to the decoded 32bpp value).
1411+ result . _originalBitsPerPixel = bitmap . _originalBitsPerPixel ;
1412+ return result ;
13791413 }
13801414
13811415 /// <summary>
@@ -1411,8 +1445,12 @@ public static AnyBitmap Redact(
14111445 Rectangle rectangle = Rectangle ;
14121446 var brush = new SolidBrush ( color ) ;
14131447 image . Mutate ( ctx => ctx . Fill ( brush , rectangle ) ) ;
1414-
1415- return new AnyBitmap ( image ) ;
1448+
1449+ var result = new AnyBitmap ( image ) ;
1450+ // Redacting a region preserves the source color depth, so carry the original depth over
1451+ // (otherwise it would fall back to the decoded 32bpp value).
1452+ result . _originalBitsPerPixel = bitmap . _originalBitsPerPixel ;
1453+ return result ;
14161454 }
14171455
14181456 /// <summary>
@@ -2959,25 +2997,37 @@ public override void WarningHandlerExt(Tiff tif, object clientData, string metho
29592997 using var tiff = Tiff . ClientOpen ( "in-memory" , "r" , tiffStream , new TiffStream ( ) ) ;
29602998 if ( tiff == null ) return ( 1 , null ) ; // Default to single frame if can't read
29612999
2962- // Read frame-0 fields before NumberOfDirectories(), which may move the active directory.
2963- FieldValue [ ] bitsPerSampleField = tiff . GetField ( TiffTag . BITSPERSAMPLE ) ;
2964- FieldValue [ ] samplesPerPixelField = tiff . GetField ( TiffTag . SAMPLESPERPIXEL ) ;
2965-
2966- // BitsPerSample defaults to 1 and SamplesPerPixel defaults to 1 per the TIFF spec.
2967- int bitsPerSample = bitsPerSampleField != null ? bitsPerSampleField [ 0 ] . ToInt ( ) : 1 ;
2968- int samplesPerPixel = samplesPerPixelField != null ? samplesPerPixelField [ 0 ] . ToInt ( ) : 1 ;
2969- int bitsPerPixel = bitsPerSample * samplesPerPixel ;
3000+ // Read frame-0 depth before NumberOfDirectories(), which may move the active directory.
3001+ int ? bitsPerPixel = ReadDirectoryBitsPerPixel ( tiff ) ;
29703002
29713003 int frameCount = tiff . NumberOfDirectories ( ) ;
29723004
2973- return ( frameCount , bitsPerPixel > 0 ? bitsPerPixel : ( int ? ) null ) ;
3005+ return ( frameCount , bitsPerPixel ) ;
29743006 }
29753007 catch
29763008 {
29773009 return ( 1 , null ) ; // Default to single frame / unknown depth on any error
29783010 }
29793011 }
29803012
3013+ /// <summary>
3014+ /// Reads the original bits per pixel (BitsPerSample x SamplesPerPixel) of the TIFF directory
3015+ /// that is currently active on <paramref name="tiff"/>.
3016+ /// </summary>
3017+ /// <returns>The bits per pixel, or <c>null</c> if it cannot be determined.</returns>
3018+ private static int ? ReadDirectoryBitsPerPixel ( Tiff tiff )
3019+ {
3020+ FieldValue [ ] bitsPerSampleField = tiff . GetField ( TiffTag . BITSPERSAMPLE ) ;
3021+ FieldValue [ ] samplesPerPixelField = tiff . GetField ( TiffTag . SAMPLESPERPIXEL ) ;
3022+
3023+ // BitsPerSample defaults to 1 and SamplesPerPixel defaults to 1 per the TIFF spec.
3024+ int bitsPerSample = bitsPerSampleField != null ? bitsPerSampleField [ 0 ] . ToInt ( ) : 1 ;
3025+ int samplesPerPixel = samplesPerPixelField != null ? samplesPerPixelField [ 0 ] . ToInt ( ) : 1 ;
3026+ int bitsPerPixel = bitsPerSample * samplesPerPixel ;
3027+
3028+ return bitsPerPixel > 0 ? bitsPerPixel : ( int ? ) null ;
3029+ }
3030+
29813031 private Lazy < IReadOnlyList < Image > > OpenTiffToImageSharp ( )
29823032 {
29833033 return new Lazy < IReadOnlyList < Image > > ( ( ) =>
@@ -3011,6 +3061,8 @@ private IReadOnlyList<Image> InternalLoadTiff()
30113061 // Disable warning messages
30123062 Tiff . SetErrorHandler ( new DisableErrorHandler ( ) ) ;
30133063 List < Image > images = new ( ) ;
3064+ // Original source depth per decoded frame.
3065+ List < int ? > framesBitsPerPixel = new ( ) ;
30143066 // open a TIFF stored in the stream
30153067 using ( Tiff tiff = Tiff . ClientOpen ( "in-memory" , "r" , tiffStream , new TiffStream ( ) ) )
30163068 {
@@ -3026,6 +3078,9 @@ private IReadOnlyList<Image> InternalLoadTiff()
30263078 continue ;
30273079 }
30283080
3081+ // Capture this page's original depth before ReadRGBAImage decodes it to 32bpp.
3082+ int ? frameBitsPerPixel = ReadDirectoryBitsPerPixel ( tiff ) ;
3083+
30293084 var ( width , height , horizontalResolution , verticalResolution ) = SetWidthHeight ( tiff , i , ref imageWidth , ref imageHeight , ref imageXResolution , ref imageYResolution ) ;
30303085
30313086 // Read the image into the memory buffer
@@ -3036,18 +3091,20 @@ private IReadOnlyList<Image> InternalLoadTiff()
30363091 }
30373092
30383093 var bits = PrepareByteArray ( raster , width , height , 32 ) ;
3039-
3094+
30403095 var image = Image . LoadPixelData < Rgba32 > ( bits , width , height ) ;
30413096
30423097 image . Metadata . HorizontalResolution = horizontalResolution ;
30433098 image . Metadata . VerticalResolution = verticalResolution ;
30443099 images . Add ( image ) ;
3100+ framesBitsPerPixel . Add ( frameBitsPerPixel ) ;
30453101
30463102 //Note1: it might be some case that the bytes of current Image is smaller/bigger than the original tiff
30473103 //Note2: 'yield return' make it super slow
30483104 }
30493105
30503106 }
3107+ _framesOriginalBitsPerPixel = framesBitsPerPixel ;
30513108 return images ;
30523109 }
30533110
@@ -3386,14 +3443,14 @@ private void SetPixelColor(int x, int y, Color color)
33863443
33873444 private void LoadAndResizeImage ( AnyBitmap original , int width , int height )
33883445 {
3389- //this prevent case when original is changed before Lazy is loaded
3390- Binary = original . Binary ;
3446+ // Capture the source's decoded image up front so we are independent of the original's lifetime.
3447+ Image sourceImage = original . GetFirstInternalImage ( ) ;
33913448
33923449 _lazyImage = new Lazy < IReadOnlyList < Image > > ( ( ) =>
33933450 {
3394-
3395- var image = Image . Load < Rgba32 > ( Binary ) ;
3396- image . Mutate ( img => img . Resize ( width , height ) ) ;
3451+ // Resize a clone of the already-decoded image so its pixel type (and therefore its
3452+ // color depth) is preserved.
3453+ var image = sourceImage . Clone ( img => img . Resize ( width , height ) ) ;
33973454
33983455 //update Binary
33993456 using var memoryStream = new MemoryStream ( ) ;
0 commit comments