Skip to content

Commit f62e2ba

Browse files
committed
Make the BMP Decoder Core can skip the file header
Make BmpDecoderCore support AlphaMask. link #687 Signed-off-by: 舰队的偶像-岛风酱! <frg2089@outlook.com>
1 parent 467850f commit f62e2ba

2 files changed

Lines changed: 211 additions & 39 deletions

File tree

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 187 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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.");

src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,28 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions
1616
/// which can occur during decoding run length encoded bitmaps.
1717
/// </summary>
1818
public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; }
19+
20+
/// <summary>
21+
/// Gets a value indicating whether the additional AlphaMask is processed at decoding time.
22+
/// </summary>
23+
/// <remarks>
24+
/// It will be used at IcoDecoder.
25+
/// </remarks>
26+
internal bool ProcessedAlphaMask { get; init; }
27+
28+
/// <summary>
29+
/// Gets a value indicating whether to skip loading the BMP file header.
30+
/// </summary>
31+
/// <remarks>
32+
/// It will be used at IcoDecoder.
33+
/// </remarks>
34+
internal bool SkipFileHeader { get; init; }
35+
36+
/// <summary>
37+
/// Gets a value indicating whether the height is double of true height.
38+
/// </summary>
39+
/// <remarks>
40+
/// It will be used at IcoDecoder.
41+
/// </remarks>
42+
internal bool IsDoubleHeight { get; init; }
1943
}

0 commit comments

Comments
 (0)