6
6
using System . Diagnostics . CodeAnalysis ;
7
7
using System . Numerics ;
8
8
using System . Runtime . CompilerServices ;
9
+ using System . Runtime . InteropServices ;
9
10
using SixLabors . ImageSharp . Common . Helpers ;
10
11
using SixLabors . ImageSharp . IO ;
11
12
using SixLabors . ImageSharp . Memory ;
@@ -71,7 +72,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
71
72
/// <summary>
72
73
/// The file header containing general information.
73
74
/// </summary>
74
- private BmpFileHeader fileHeader ;
75
+ private BmpFileHeader ? fileHeader ;
75
76
76
77
/// <summary>
77
78
/// Indicates which bitmap file marker was read.
@@ -99,6 +100,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
99
100
/// </summary>
100
101
private readonly RleSkippedPixelHandling rleSkippedPixelHandling ;
101
102
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
+
102
112
/// <summary>
103
113
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
104
114
/// </summary>
@@ -109,6 +119,9 @@ public BmpDecoderCore(BmpDecoderOptions options)
109
119
this . rleSkippedPixelHandling = options . RleSkippedPixelHandling ;
110
120
this . configuration = options . GeneralOptions . Configuration ;
111
121
this . memoryAllocator = this . configuration . MemoryAllocator ;
122
+ this . processedAlphaMask = options . ProcessedAlphaMask ;
123
+ this . skipFileHeader = options . SkipFileHeader ;
124
+ this . isDoubleHeight = options . UseDoubleHeight ;
112
125
}
113
126
114
127
/// <inheritdoc />
@@ -132,38 +145,44 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
132
145
133
146
switch ( this . infoHeader . Compression )
134
147
{
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 ) ;
167
186
168
187
break ;
169
188
@@ -839,6 +858,108 @@ private void ReadRgbPalette<TPixel>(BufferedReadStream stream, Buffer2D<TPixel>
839
858
}
840
859
}
841
860
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
+
842
963
/// <summary>
843
964
/// Reads the 16 bit color palette from the stream.
844
965
/// </summary>
@@ -1333,6 +1454,11 @@ private void ReadInfoHeader(BufferedReadStream stream)
1333
1454
this . metadata . VerticalResolution = Math . Round ( UnitConverter . InchToMeter ( ImageMetadata . DefaultVerticalResolution ) ) ;
1334
1455
}
1335
1456
1457
+ if ( this . isDoubleHeight )
1458
+ {
1459
+ this . infoHeader . Height >>= 1 ;
1460
+ }
1461
+
1336
1462
ushort bitsPerPixel = this . infoHeader . BitsPerPixel ;
1337
1463
this . bmpMetadata = this . metadata . GetBmpMetadata ( ) ;
1338
1464
this . bmpMetadata . InfoHeaderType = infoHeaderType ;
@@ -1362,9 +1488,9 @@ private void ReadFileHeader(BufferedReadStream stream)
1362
1488
// The bitmap file header of the first image follows the array header.
1363
1489
stream . Read ( buffer , 0 , BmpFileHeader . Size ) ;
1364
1490
this . fileHeader = BmpFileHeader . Parse ( buffer ) ;
1365
- if ( this . fileHeader . Type != BmpConstants . TypeMarkers . Bitmap )
1491
+ if ( this . fileHeader . Value . Type != BmpConstants . TypeMarkers . Bitmap )
1366
1492
{
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 } '.") ;
1368
1494
}
1369
1495
1370
1496
break ;
@@ -1387,7 +1513,11 @@ private void ReadFileHeader(BufferedReadStream stream)
1387
1513
[ MemberNotNull ( nameof ( bmpMetadata ) ) ]
1388
1514
private int ReadImageHeaders ( BufferedReadStream stream , out bool inverted , out byte [ ] palette )
1389
1515
{
1390
- this . ReadFileHeader ( stream ) ;
1516
+ if ( ! this . skipFileHeader )
1517
+ {
1518
+ this . ReadFileHeader ( stream ) ;
1519
+ }
1520
+
1391
1521
this . ReadInfoHeader ( stream ) ;
1392
1522
1393
1523
// 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
1411
1541
switch ( this . fileMarkerType )
1412
1542
{
1413
1543
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
+
1415
1559
int colorCountForBitDepth = ColorNumerics . GetColorCountForBitDepth ( this . infoHeader . BitsPerPixel ) ;
1416
1560
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth ;
1417
1561
@@ -1442,7 +1586,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
1442
1586
{
1443
1587
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
1444
1588
// 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 )
1446
1590
{
1447
1591
BmpThrowHelper . ThrowInvalidImageContentException (
1448
1592
$ "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
1456
1600
}
1457
1601
}
1458
1602
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
+
1460
1617
if ( ( skipAmount + ( int ) stream . Position ) > stream . Length )
1461
1618
{
1462
1619
BmpThrowHelper . ThrowInvalidImageContentException ( "Invalid file header offset found. Offset is greater than the stream length." ) ;
0 commit comments