@@ -71,7 +71,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
7171 /// <summary>
7272 /// The file header containing general information.
7373 /// </summary>
74- private BmpFileHeader fileHeader ;
74+ private BmpFileHeader ? fileHeader ;
7575
7676 /// <summary>
7777 /// Indicates which bitmap file marker was read.
@@ -99,6 +99,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
9999 /// </summary>
100100 private readonly RleSkippedPixelHandling rleSkippedPixelHandling ;
101101
102+ /// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
103+ private readonly bool processedAlphaMask ;
104+
105+ /// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
106+ private readonly bool skipFileHeader ;
107+
108+ /// <inheritdoc cref="BmpDecoderOptions.IsDoubleHeight"/>
109+ private readonly bool isDoubleHeight ;
110+
102111 /// <summary>
103112 /// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
104113 /// </summary>
@@ -109,6 +118,9 @@ public BmpDecoderCore(BmpDecoderOptions options)
109118 this . rleSkippedPixelHandling = options . RleSkippedPixelHandling ;
110119 this . configuration = options . GeneralOptions . Configuration ;
111120 this . memoryAllocator = this . configuration . MemoryAllocator ;
121+ this . processedAlphaMask = options . ProcessedAlphaMask ;
122+ this . skipFileHeader = options . SkipFileHeader ;
123+ this . isDoubleHeight = options . IsDoubleHeight ;
112124 }
113125
114126 /// <inheritdoc />
@@ -132,38 +144,44 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
132144
133145 switch ( this . infoHeader . Compression )
134146 {
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- }
147+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 32 && this . bmpMetadata . InfoHeaderType is BmpInfoHeaderType . WinVersion3 :
148+ this . ReadRgb32Slow ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
149+
150+ break ;
151+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 32 :
152+ this . ReadRgb32Fast ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
153+
154+ break ;
155+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 24 :
156+ this . ReadRgb24 ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
157+
158+ break ;
159+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is 16 :
160+ this . ReadRgb16 ( stream , pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
161+
162+ break ;
163+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is <= 8 && this . processedAlphaMask :
164+ this . ReadRgbPaletteWithAlphaMask (
165+ stream ,
166+ pixels ,
167+ palette ,
168+ this . infoHeader . Width ,
169+ this . infoHeader . Height ,
170+ this . infoHeader . BitsPerPixel ,
171+ bytesPerColorMapEntry ,
172+ inverted ) ;
173+
174+ break ;
175+ case BmpCompression . RGB when this . infoHeader . BitsPerPixel is <= 8 :
176+ this . ReadRgbPalette (
177+ stream ,
178+ pixels ,
179+ palette ,
180+ this . infoHeader . Width ,
181+ this . infoHeader . Height ,
182+ this . infoHeader . BitsPerPixel ,
183+ bytesPerColorMapEntry ,
184+ inverted ) ;
167185
168186 break ;
169187
@@ -839,6 +857,108 @@ private void ReadRgbPalette<TPixel>(BufferedReadStream stream, Buffer2D<TPixel>
839857 }
840858 }
841859
860+ /// <inheritdoc cref="ReadRgbPalette"/>
861+ private void ReadRgbPaletteWithAlphaMask < TPixel > ( BufferedReadStream stream , Buffer2D < TPixel > pixels , byte [ ] colors , int width , int height , int bitsPerPixel , int bytesPerColorMapEntry , bool inverted )
862+ where TPixel : unmanaged, IPixel < TPixel >
863+ {
864+ // Pixels per byte (bits per pixel).
865+ int ppb = 8 / bitsPerPixel ;
866+
867+ int arrayWidth = ( width + ppb - 1 ) / ppb ;
868+
869+ // Bit mask
870+ int mask = 0xFF >> ( 8 - bitsPerPixel ) ;
871+
872+ // Rows are aligned on 4 byte boundaries.
873+ int padding = arrayWidth % 4 ;
874+ if ( padding != 0 )
875+ {
876+ padding = 4 - padding ;
877+ }
878+
879+ Bgra32 [ , ] image = new Bgra32 [ height , width ] ;
880+ using ( IMemoryOwner < byte > row = this . memoryAllocator . Allocate < byte > ( arrayWidth + padding , AllocationOptions . Clean ) )
881+ {
882+ Span < byte > rowSpan = row . GetSpan ( ) ;
883+
884+ for ( int y = 0 ; y < height ; y ++ )
885+ {
886+ int newY = Invert ( y , height , inverted ) ;
887+ if ( stream . Read ( rowSpan ) == 0 )
888+ {
889+ BmpThrowHelper . ThrowInvalidImageContentException ( "Could not read enough data for a pixel row!" ) ;
890+ }
891+
892+ int offset = 0 ;
893+
894+ for ( int x = 0 ; x < arrayWidth ; x ++ )
895+ {
896+ int colOffset = x * ppb ;
897+ for ( int shift = 0 , newX = colOffset ; shift < ppb && newX < width ; shift ++ , newX ++ )
898+ {
899+ int colorIndex = ( ( rowSpan [ offset ] >> ( 8 - bitsPerPixel - ( shift * bitsPerPixel ) ) ) & mask ) * bytesPerColorMapEntry ;
900+
901+ image [ newY , newX ] . FromBgr24 ( Unsafe . As < byte , Bgr24 > ( ref colors [ colorIndex ] ) ) ;
902+ }
903+
904+ offset ++ ;
905+ }
906+ }
907+ }
908+
909+ arrayWidth = width / 8 ;
910+ padding = arrayWidth % 4 ;
911+ if ( padding != 0 )
912+ {
913+ padding = 4 - padding ;
914+ }
915+
916+ for ( int y = 0 ; y < height ; y ++ )
917+ {
918+ int newY = Invert ( y , height , inverted ) ;
919+
920+ for ( int i = 0 ; i < arrayWidth ; i ++ )
921+ {
922+ int x = i * 8 ;
923+ int and = stream . ReadByte ( ) ;
924+ if ( and is - 1 )
925+ {
926+ throw new EndOfStreamException ( ) ;
927+ }
928+
929+ for ( int j = 0 ; j < 8 ; j ++ )
930+ {
931+ SetAlpha ( ref image [ newY , x + j ] , and , j ) ;
932+ }
933+ }
934+
935+ stream . Skip ( padding ) ;
936+ }
937+
938+ for ( int y = 0 ; y < height ; y ++ )
939+ {
940+ int newY = Invert ( y , height , inverted ) ;
941+ Span < TPixel > pixelRow = pixels . DangerousGetRowSpan ( newY ) ;
942+
943+ for ( int x = 0 ; x < width ; x ++ )
944+ {
945+ pixelRow [ x ] . FromBgra32 ( image [ newY , x ] ) ;
946+ }
947+ }
948+ }
949+
950+ /// <summary>
951+ /// Set pixel's alpha with alpha mask.
952+ /// </summary>
953+ /// <param name="pixel">Bgra32 pixel.</param>
954+ /// <param name="mask">alpha mask.</param>
955+ /// <param name="index">bit index of pixel.</param>
956+ private static void SetAlpha ( ref Bgra32 pixel , in int mask , in int index )
957+ {
958+ bool isTransparently = ( mask & ( 0b10000000 >> index ) ) is not 0 ;
959+ pixel . A = isTransparently ? byte . MinValue : byte . MaxValue ;
960+ }
961+
842962 /// <summary>
843963 /// Reads the 16 bit color palette from the stream.
844964 /// </summary>
@@ -1333,6 +1453,11 @@ private void ReadInfoHeader(BufferedReadStream stream)
13331453 this . metadata . VerticalResolution = Math . Round ( UnitConverter . InchToMeter ( ImageMetadata . DefaultVerticalResolution ) ) ;
13341454 }
13351455
1456+ if ( this . isDoubleHeight )
1457+ {
1458+ this . infoHeader . Height >>= 1 ;
1459+ }
1460+
13361461 ushort bitsPerPixel = this . infoHeader . BitsPerPixel ;
13371462 this . bmpMetadata = this . metadata . GetBmpMetadata ( ) ;
13381463 this . bmpMetadata . InfoHeaderType = infoHeaderType ;
@@ -1362,9 +1487,9 @@ private void ReadFileHeader(BufferedReadStream stream)
13621487 // The bitmap file header of the first image follows the array header.
13631488 stream . Read ( buffer , 0 , BmpFileHeader . Size ) ;
13641489 this . fileHeader = BmpFileHeader . Parse ( buffer ) ;
1365- if ( this . fileHeader . Type != BmpConstants . TypeMarkers . Bitmap )
1490+ if ( this . fileHeader . Value . Type != BmpConstants . TypeMarkers . Bitmap )
13661491 {
1367- BmpThrowHelper . ThrowNotSupportedException ( $ "Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{ this . fileHeader . Type } '.") ;
1492+ BmpThrowHelper . ThrowNotSupportedException ( $ "Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{ this . fileHeader . Value . Type } '.") ;
13681493 }
13691494
13701495 break ;
@@ -1387,7 +1512,11 @@ private void ReadFileHeader(BufferedReadStream stream)
13871512 [ MemberNotNull ( nameof ( bmpMetadata ) ) ]
13881513 private int ReadImageHeaders ( BufferedReadStream stream , out bool inverted , out byte [ ] palette )
13891514 {
1390- this . ReadFileHeader ( stream ) ;
1515+ if ( ! this . skipFileHeader )
1516+ {
1517+ this . ReadFileHeader ( stream ) ;
1518+ }
1519+
13911520 this . ReadInfoHeader ( stream ) ;
13921521
13931522 // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
@@ -1411,7 +1540,21 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14111540 switch ( this . fileMarkerType )
14121541 {
14131542 case BmpFileMarkerType . Bitmap :
1414- colorMapSizeBytes = this . fileHeader . Offset - BmpFileHeader . Size - this . infoHeader . HeaderSize ;
1543+ if ( this . fileHeader . HasValue )
1544+ {
1545+ colorMapSizeBytes = this . fileHeader . Value . Offset - BmpFileHeader . Size - this . infoHeader . HeaderSize ;
1546+ }
1547+ else
1548+ {
1549+ colorMapSizeBytes = this . infoHeader . ClrUsed ;
1550+ if ( colorMapSizeBytes is 0 && this . infoHeader . BitsPerPixel is <= 8 )
1551+ {
1552+ colorMapSizeBytes = ColorNumerics . GetColorCountForBitDepth ( this . infoHeader . BitsPerPixel ) ;
1553+ }
1554+
1555+ colorMapSizeBytes *= 4 ;
1556+ }
1557+
14151558 int colorCountForBitDepth = ColorNumerics . GetColorCountForBitDepth ( this . infoHeader . BitsPerPixel ) ;
14161559 bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth ;
14171560
@@ -1442,7 +1585,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14421585 {
14431586 // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
14441587 // Make sure, that we will not read pass the bitmap offset (starting position of image data).
1445- if ( stream . Position > this . fileHeader . Offset - colorMapSizeBytes )
1588+ if ( this . fileHeader . HasValue && stream . Position > this . fileHeader . Value . Offset - colorMapSizeBytes )
14461589 {
14471590 BmpThrowHelper . ThrowInvalidImageContentException (
14481591 $ "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 +1599,12 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14561599 }
14571600 }
14581601
1459- int skipAmount = this . fileHeader . Offset - ( int ) stream . Position ;
1602+ int skipAmount = 0 ;
1603+ if ( this . fileHeader . HasValue )
1604+ {
1605+ skipAmount = this . fileHeader . Value . Offset - ( int ) stream . Position ;
1606+ }
1607+
14601608 if ( ( skipAmount + ( int ) stream . Position ) > stream . Length )
14611609 {
14621610 BmpThrowHelper . ThrowInvalidImageContentException ( "Invalid file header offset found. Offset is greater than the stream length." ) ;
0 commit comments