diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index bed489752..9bea6867a 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -71,7 +71,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
///
/// The file header containing general information.
///
- private BmpFileHeader fileHeader;
+ private BmpFileHeader? fileHeader;
///
/// Indicates which bitmap file marker was read.
@@ -99,6 +99,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
///
private readonly RleSkippedPixelHandling rleSkippedPixelHandling;
+ ///
+ private readonly bool processedAlphaMask;
+
+ ///
+ private readonly bool skipFileHeader;
+
+ ///
+ private readonly bool isDoubleHeight;
+
///
/// Initializes a new instance of the class.
///
@@ -109,6 +118,9 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
+ this.processedAlphaMask = options.ProcessedAlphaMask;
+ this.skipFileHeader = options.SkipFileHeader;
+ this.isDoubleHeight = options.IsDoubleHeight;
}
///
@@ -132,38 +144,44 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
switch (this.infoHeader.Compression)
{
- case BmpCompression.RGB:
- if (this.infoHeader.BitsPerPixel == 32)
- {
- if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3)
- {
- this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
- }
- else
- {
- this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
- }
- }
- else if (this.infoHeader.BitsPerPixel == 24)
- {
- this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
- }
- else if (this.infoHeader.BitsPerPixel == 16)
- {
- this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
- }
- else if (this.infoHeader.BitsPerPixel <= 8)
- {
- this.ReadRgbPalette(
- stream,
- pixels,
- palette,
- this.infoHeader.Width,
- this.infoHeader.Height,
- this.infoHeader.BitsPerPixel,
- bytesPerColorMapEntry,
- inverted);
- }
+ case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
+ this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
+
+ break;
+ case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32:
+ this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
+
+ break;
+ case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24:
+ this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
+
+ break;
+ case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16:
+ this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
+
+ break;
+ case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask:
+ this.ReadRgbPaletteWithAlphaMask(
+ stream,
+ pixels,
+ palette,
+ this.infoHeader.Width,
+ this.infoHeader.Height,
+ this.infoHeader.BitsPerPixel,
+ bytesPerColorMapEntry,
+ inverted);
+
+ break;
+ case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8:
+ this.ReadRgbPalette(
+ stream,
+ pixels,
+ palette,
+ this.infoHeader.Width,
+ this.infoHeader.Height,
+ this.infoHeader.BitsPerPixel,
+ bytesPerColorMapEntry,
+ inverted);
break;
@@ -839,6 +857,108 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
}
+ ///
+ private void ReadRgbPaletteWithAlphaMask(BufferedReadStream stream, Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
+ where TPixel : unmanaged, IPixel
+ {
+ // Pixels per byte (bits per pixel).
+ int ppb = 8 / bitsPerPixel;
+
+ int arrayWidth = (width + ppb - 1) / ppb;
+
+ // Bit mask
+ int mask = 0xFF >> (8 - bitsPerPixel);
+
+ // Rows are aligned on 4 byte boundaries.
+ int padding = arrayWidth % 4;
+ if (padding != 0)
+ {
+ padding = 4 - padding;
+ }
+
+ Bgra32[,] image = new Bgra32[height, width];
+ using (IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean))
+ {
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ if (stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
+ int offset = 0;
+
+ for (int x = 0; x < arrayWidth; x++)
+ {
+ int colOffset = x * ppb;
+ for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
+ {
+ int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
+
+ image[newY, newX].FromBgr24(Unsafe.As(ref colors[colorIndex]));
+ }
+
+ offset++;
+ }
+ }
+ }
+
+ arrayWidth = width / 8;
+ padding = arrayWidth % 4;
+ if (padding != 0)
+ {
+ padding = 4 - padding;
+ }
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+
+ for (int i = 0; i < arrayWidth; i++)
+ {
+ int x = i * 8;
+ int and = stream.ReadByte();
+ if (and is -1)
+ {
+ throw new EndOfStreamException();
+ }
+
+ for (int j = 0; j < 8; j++)
+ {
+ SetAlpha(ref image[newY, x + j], and, j);
+ }
+ }
+
+ stream.Skip(padding);
+ }
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.DangerousGetRowSpan(newY);
+
+ for (int x = 0; x < width; x++)
+ {
+ pixelRow[x].FromBgra32(image[newY, x]);
+ }
+ }
+ }
+
+ ///
+ /// Set pixel's alpha with alpha mask.
+ ///
+ /// Bgra32 pixel.
+ /// alpha mask.
+ /// bit index of pixel.
+ private static void SetAlpha(ref Bgra32 pixel, in int mask, in int index)
+ {
+ bool isTransparently = (mask & (0b10000000 >> index)) is not 0;
+ pixel.A = isTransparently ? byte.MinValue : byte.MaxValue;
+ }
+
///
/// Reads the 16 bit color palette from the stream.
///
@@ -1333,6 +1453,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution));
}
+ if (this.isDoubleHeight)
+ {
+ this.infoHeader.Height >>= 1;
+ }
+
ushort bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
@@ -1362,9 +1487,9 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// The bitmap file header of the first image follows the array header.
stream.Read(buffer, 0, BmpFileHeader.Size);
this.fileHeader = BmpFileHeader.Parse(buffer);
- if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap)
+ if (this.fileHeader.Value.Type != BmpConstants.TypeMarkers.Bitmap)
{
- BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'.");
+ BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Value.Type}'.");
}
break;
@@ -1387,7 +1512,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
[MemberNotNull(nameof(bmpMetadata))]
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette)
{
- this.ReadFileHeader(stream);
+ if (!this.skipFileHeader)
+ {
+ this.ReadFileHeader(stream);
+ }
+
this.ReadInfoHeader(stream);
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
@@ -1411,7 +1540,21 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
switch (this.fileMarkerType)
{
case BmpFileMarkerType.Bitmap:
- colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
+ if (this.fileHeader.HasValue)
+ {
+ colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
+ }
+ else
+ {
+ colorMapSizeBytes = this.infoHeader.ClrUsed;
+ if (colorMapSizeBytes is 0 && this.infoHeader.BitsPerPixel is <= 8)
+ {
+ colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
+ }
+
+ colorMapSizeBytes *= 4;
+ }
+
int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;
@@ -1442,7 +1585,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
- if (stream.Position > this.fileHeader.Offset - colorMapSizeBytes)
+ if (this.fileHeader.HasValue && stream.Position > this.fileHeader.Value.Offset - colorMapSizeBytes)
{
BmpThrowHelper.ThrowInvalidImageContentException(
$"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 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
}
- int skipAmount = this.fileHeader.Offset - (int)stream.Position;
+ int skipAmount = 0;
+ if (this.fileHeader.HasValue)
+ {
+ skipAmount = this.fileHeader.Value.Offset - (int)stream.Position;
+ }
+
if ((skipAmount + (int)stream.Position) > stream.Length)
{
BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length.");
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs
index b3387ce80..17d37cab2 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs
@@ -16,4 +16,28 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions
/// which can occur during decoding run length encoded bitmaps.
///
public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; }
+
+ ///
+ /// Gets a value indicating whether the additional AlphaMask is processed at decoding time.
+ ///
+ ///
+ /// It will be used at IcoDecoder.
+ ///
+ internal bool ProcessedAlphaMask { get; init; }
+
+ ///
+ /// Gets a value indicating whether to skip loading the BMP file header.
+ ///
+ ///
+ /// It will be used at IcoDecoder.
+ ///
+ internal bool SkipFileHeader { get; init; }
+
+ ///
+ /// Gets a value indicating whether the height is double of true height.
+ ///
+ ///
+ /// It will be used at IcoDecoder.
+ ///
+ internal bool IsDoubleHeight { get; init; }
}