66using System . Diagnostics . CodeAnalysis ;
77using System . Numerics ;
88using System . Runtime . CompilerServices ;
9+ using System . Runtime . InteropServices ;
910using SixLabors . ImageSharp . Common . Helpers ;
1011using SixLabors . ImageSharp . IO ;
1112using SixLabors . ImageSharp . Memory ;
@@ -71,7 +72,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
7172 /// <summary>
7273 /// The file header containing general information.
7374 /// </summary>
74- private BmpFileHeader fileHeader ;
75+ private BmpFileHeader ? fileHeader ;
7576
7677 /// <summary>
7778 /// Indicates which bitmap file marker was read.
@@ -99,6 +100,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
99100 /// </summary>
100101 private readonly RleSkippedPixelHandling rleSkippedPixelHandling ;
101102
103+ /// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
104+ private readonly bool processedAlphaMask ;
105+
106+ /// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
107+ private readonly bool skipFileHeader ;
108+
109+ /// <inheritdoc cref="BmpDecoderOptions.UseDoubleHeight"/>
110+ private readonly bool isDoubleHeight ;
111+
102112 /// <summary>
103113 /// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
104114 /// </summary>
@@ -109,6 +119,9 @@ public BmpDecoderCore(BmpDecoderOptions options)
109119 this . rleSkippedPixelHandling = options . RleSkippedPixelHandling ;
110120 this . configuration = options . GeneralOptions . Configuration ;
111121 this . memoryAllocator = this . configuration . MemoryAllocator ;
122+ this . processedAlphaMask = options . ProcessedAlphaMask ;
123+ this . skipFileHeader = options . SkipFileHeader ;
124+ this . isDoubleHeight = options . UseDoubleHeight ;
112125 }
113126
114127 /// <inheritdoc />
@@ -132,38 +145,44 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
132145
133146 switch ( this . infoHeader . Compression )
134147 {
135- case BmpCompression . RGB :
136- if ( this . infoHeader . BitsPerPixel == 32 )
137- {
138- if ( this . bmpMetadata . InfoHeaderType == BmpInfoHeaderType . WinVersion3 )
139- {
140- this . ReadRgb32Slow ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
141- }
142- else
143- {
144- this . ReadRgb32Fast ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
145- }
146- }
147- else if ( this . infoHeader . BitsPerPixel == 24 )
148- {
149- this . ReadRgb24 ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
150- }
151- else if ( this . infoHeader . BitsPerPixel == 16 )
152- {
153- this . ReadRgb16 ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
154- }
155- else if ( this . infoHeader . BitsPerPixel <= 8 )
156- {
157- this . ReadRgbPalette (
158- stream ,
159- pixels ,
160- palette ,
161- this . infoHeader . Width ,
162- this . infoHeader . Height ,
163- this . infoHeader . BitsPerPixel ,
164- bytesPerColorMapEntry ,
165- inverted ) ;
166- }
148+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 32 && this . bmpMetadata . InfoHeaderType is BmpInfoHeaderType . WinVersion3 :
149+ this . ReadRgb32Slow ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
150+
151+ break ;
152+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 32 :
153+ this . ReadRgb32Fast ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
154+
155+ break ;
156+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 24 :
157+ this . ReadRgb24 ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
158+
159+ break ;
160+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 16 :
161+ this . ReadRgb16 ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
162+
163+ break ;
164+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is <= 8 && this . processedAlphaMask :
165+ this . ReadRgbPaletteWithAlphaMask (
166+ stream ,
167+ pixels ,
168+ palette ,
169+ this . infoHeader . Width ,
170+ this . infoHeader . Height ,
171+ this . infoHeader . BitsPerPixel ,
172+ bytesPerColorMapEntry ,
173+ inverted ) ;
174+
175+ break ;
176+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is <= 8 :
177+ this . ReadRgbPalette (
178+ stream ,
179+ pixels ,
180+ palette ,
181+ this . infoHeader . Width ,
182+ this . infoHeader . Height ,
183+ this . infoHeader . BitsPerPixel ,
184+ bytesPerColorMapEntry ,
185+ inverted ) ;
167186
168187 break ;
169188
@@ -839,6 +858,108 @@ private void ReadRgbPalette<TPixel>(BufferedReadStream stream, Buffer2D<TPixel>
839858 }
840859 }
841860
861+ /// <inheritdoc cref="ReadRgbPalette"/>
862+ private void ReadRgbPaletteWithAlphaMask < TPixel > ( BufferedReadStream stream , Buffer2D < TPixel > pixels , byte [ ] colors , int width , int height , int bitsPerPixel , int bytesPerColorMapEntry , bool inverted )
863+ where TPixel : unmanaged, IPixel < TPixel >
864+ {
865+ // Pixels per byte (bits per pixel).
866+ int ppb = 8 / bitsPerPixel ;
867+
868+ int arrayWidth = ( width + ppb - 1 ) / ppb ;
869+
870+ // Bit mask
871+ int mask = 0xFF >> ( 8 - bitsPerPixel ) ;
872+
873+ // Rows are aligned on 4 byte boundaries.
874+ int padding = arrayWidth % 4 ;
875+ if ( padding != 0 )
876+ {
877+ padding = 4 - padding ;
878+ }
879+
880+ Bgra32 [ , ] image = new Bgra32 [ height , width ] ;
881+ using ( IMemoryOwner < byte > row = this . memoryAllocator . Allocate < byte > ( arrayWidth + padding , AllocationOptions . Clean ) )
882+ {
883+ Span < byte > rowSpan = row . GetSpan ( ) ;
884+
885+ for ( int y = 0 ; y < height ; y ++ )
886+ {
887+ int newY = Invert ( y , height , inverted ) ;
888+ if ( stream . Read ( rowSpan ) == 0 )
889+ {
890+ BmpThrowHelper . ThrowInvalidImageContentException ( "Could not read enough data for a pixel row!" ) ;
891+ }
892+
893+ int offset = 0 ;
894+
895+ for ( int x = 0 ; x < arrayWidth ; x ++ )
896+ {
897+ int colOffset = x * ppb ;
898+ for ( int shift = 0 , newX = colOffset ; shift < ppb && newX < width ; shift ++ , newX ++ )
899+ {
900+ int colorIndex = ( ( rowSpan [ offset ] >> ( 8 - bitsPerPixel - ( shift * bitsPerPixel ) ) ) & mask ) * bytesPerColorMapEntry ;
901+
902+ image [ newY , newX ] = Bgra32 . FromBgr24 ( Unsafe . As < byte , Bgr24 > ( ref colors [ colorIndex ] ) ) ;
903+ }
904+
905+ offset ++ ;
906+ }
907+ }
908+ }
909+
910+ arrayWidth = width / 8 ;
911+ padding = arrayWidth % 4 ;
912+ if ( padding != 0 )
913+ {
914+ padding = 4 - padding ;
915+ }
916+
917+ for ( int y = 0 ; y < height ; y ++ )
918+ {
919+ int newY = Invert ( y , height , inverted ) ;
920+
921+ for ( int i = 0 ; i < arrayWidth ; i ++ )
922+ {
923+ int x = i * 8 ;
924+ int and = stream . ReadByte ( ) ;
925+ if ( and is - 1 )
926+ {
927+ throw new EndOfStreamException ( ) ;
928+ }
929+
930+ for ( int j = 0 ; j < 8 ; j ++ )
931+ {
932+ SetAlpha ( ref image [ newY , x + j ] , and , j ) ;
933+ }
934+ }
935+
936+ stream . Skip ( padding ) ;
937+ }
938+
939+ for ( int y = 0 ; y < height ; y ++ )
940+ {
941+ int newY = Invert ( y , height , inverted ) ;
942+ Span < TPixel > pixelRow = pixels . DangerousGetRowSpan ( newY ) ;
943+
944+ for ( int x = 0 ; x < width ; x ++ )
945+ {
946+ pixelRow [ x ] = TPixel . FromBgra32 ( image [ newY , x ] ) ;
947+ }
948+ }
949+ }
950+
951+ /// <summary>
952+ /// Set pixel's alpha with alpha mask.
953+ /// </summary>
954+ /// <param name="pixel">Bgra32 pixel.</param>
955+ /// <param name="mask">alpha mask.</param>
956+ /// <param name="index">bit index of pixel.</param>
957+ private static void SetAlpha ( ref Bgra32 pixel , in int mask , in int index )
958+ {
959+ bool isTransparently = ( mask & ( 0b10000000 >> index ) ) is not 0 ;
960+ pixel . A = isTransparently ? byte . MinValue : byte . MaxValue ;
961+ }
962+
842963 /// <summary>
843964 /// Reads the 16 bit color palette from the stream.
844965 /// </summary>
@@ -1333,6 +1454,11 @@ private void ReadInfoHeader(BufferedReadStream stream)
13331454 this . metadata . VerticalResolution = Math . Round ( UnitConverter . InchToMeter ( ImageMetadata . DefaultVerticalResolution ) ) ;
13341455 }
13351456
1457+ if ( this . isDoubleHeight )
1458+ {
1459+ this . infoHeader . Height >>= 1 ;
1460+ }
1461+
13361462 ushort bitsPerPixel = this . infoHeader . BitsPerPixel ;
13371463 this . bmpMetadata = this . metadata . GetBmpMetadata ( ) ;
13381464 this . bmpMetadata . InfoHeaderType = infoHeaderType ;
@@ -1362,9 +1488,9 @@ private void ReadFileHeader(BufferedReadStream stream)
13621488 // The bitmap file header of the first image follows the array header.
13631489 stream . Read ( buffer , 0 , BmpFileHeader . Size ) ;
13641490 this . fileHeader = BmpFileHeader . Parse ( buffer ) ;
1365- if ( this . fileHeader . Type != BmpConstants . TypeMarkers . Bitmap )
1491+ if ( this . fileHeader . Value . Type != BmpConstants . TypeMarkers . Bitmap )
13661492 {
1367- BmpThrowHelper . ThrowNotSupportedException ( $ "Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{ this . fileHeader . Type } '.") ;
1493+ BmpThrowHelper . ThrowNotSupportedException ( $ "Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{ this . fileHeader . Value . Type } '.") ;
13681494 }
13691495
13701496 break ;
@@ -1387,7 +1513,11 @@ private void ReadFileHeader(BufferedReadStream stream)
13871513 [ MemberNotNull ( nameof ( bmpMetadata ) ) ]
13881514 private int ReadImageHeaders ( BufferedReadStream stream , out bool inverted , out byte [ ] palette )
13891515 {
1390- this . ReadFileHeader ( stream ) ;
1516+ if ( ! this . skipFileHeader )
1517+ {
1518+ this . ReadFileHeader ( stream ) ;
1519+ }
1520+
13911521 this . ReadInfoHeader ( stream ) ;
13921522
13931523 // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
@@ -1411,7 +1541,21 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14111541 switch ( this . fileMarkerType )
14121542 {
14131543 case BmpFileMarkerType . Bitmap :
1414- colorMapSizeBytes = this . fileHeader . Offset - BmpFileHeader . Size - this . infoHeader . HeaderSize ;
1544+ if ( this . fileHeader . HasValue )
1545+ {
1546+ colorMapSizeBytes = this . fileHeader . Value . Offset - BmpFileHeader . Size - this . infoHeader . HeaderSize ;
1547+ }
1548+ else
1549+ {
1550+ colorMapSizeBytes = this . infoHeader . ClrUsed ;
1551+ if ( colorMapSizeBytes is 0 && this . infoHeader . BitsPerPixel is <= 8 )
1552+ {
1553+ colorMapSizeBytes = ColorNumerics . GetColorCountForBitDepth ( this . infoHeader . BitsPerPixel ) ;
1554+ }
1555+
1556+ colorMapSizeBytes *= 4 ;
1557+ }
1558+
14151559 int colorCountForBitDepth = ColorNumerics . GetColorCountForBitDepth ( this . infoHeader . BitsPerPixel ) ;
14161560 bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth ;
14171561
@@ -1442,7 +1586,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14421586 {
14431587 // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
14441588 // Make sure, that we will not read pass the bitmap offset (starting position of image data).
1445- if ( stream . Position > this . fileHeader . Offset - colorMapSizeBytes )
1589+ if ( this . fileHeader . HasValue && stream . Position > this . fileHeader . Value . Offset - colorMapSizeBytes )
14461590 {
14471591 BmpThrowHelper . ThrowInvalidImageContentException (
14481592 $ "Reading the color map would read beyond the bitmap offset. Either the color map size of '{ colorMapSizeBytes } ' is invalid or the bitmap offset.") ;
@@ -1456,7 +1600,20 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14561600 }
14571601 }
14581602
1459- int skipAmount = this . fileHeader . Offset - ( int ) stream . Position ;
1603+ if ( palette . Length > 0 )
1604+ {
1605+ Color [ ] colorTable = new Color [ palette . Length / Unsafe . SizeOf < Bgr24 > ( ) ] ;
1606+ ReadOnlySpan < Bgr24 > rgbTable = MemoryMarshal . Cast < byte , Bgr24 > ( palette ) ;
1607+ Color . FromPixel ( rgbTable , colorTable ) ;
1608+ this . bmpMetadata . ColorTable = colorTable ;
1609+ }
1610+
1611+ int skipAmount = 0 ;
1612+ if ( this . fileHeader . HasValue )
1613+ {
1614+ skipAmount = this . fileHeader . Value . Offset - ( int ) stream . Position ;
1615+ }
1616+
14601617 if ( ( skipAmount + ( int ) stream . Position ) > stream . Length )
14611618 {
14621619 BmpThrowHelper . ThrowInvalidImageContentException ( "Invalid file header offset found. Offset is greater than the stream length." ) ;
0 commit comments