Browse Source

Make the BMP Decoder Core can skip the file header

Make BmpDecoderCore support AlphaMask.
link #687

Signed-off-by: 舰队的偶像-岛风酱! <frg2089@outlook.com>
pull/2579/head
舰队的偶像-岛风酱! 2 years ago
parent
commit
f62e2baac8
No known key found for this signature in database GPG Key ID: 71F5B3A2B181950C
  1. 226
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 24
      src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs

226
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -71,7 +71,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <summary>
/// The file header containing general information.
/// </summary>
private BmpFileHeader fileHeader;
private BmpFileHeader? fileHeader;
/// <summary>
/// Indicates which bitmap file marker was read.
@ -99,6 +99,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// </summary>
private readonly RleSkippedPixelHandling rleSkippedPixelHandling;
/// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
private readonly bool processedAlphaMask;
/// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
private readonly bool skipFileHeader;
/// <inheritdoc cref="BmpDecoderOptions.IsDoubleHeight"/>
private readonly bool isDoubleHeight;
/// <summary>
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary>
@ -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;
}
/// <inheritdoc />
@ -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
}
}
/// <inheritdoc cref="ReadRgbPalette"/>
private void ReadRgbPaletteWithAlphaMask<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<byte> row = this.memoryAllocator.Allocate<byte>(arrayWidth + padding, AllocationOptions.Clean))
{
Span<byte> 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<byte, Bgr24>(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<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
for (int x = 0; x < width; x++)
{
pixelRow[x].FromBgra32(image[newY, x]);
}
}
}
/// <summary>
/// Set pixel's alpha with alpha mask.
/// </summary>
/// <param name="pixel">Bgra32 pixel.</param>
/// <param name="mask">alpha mask.</param>
/// <param name="index">bit index of pixel.</param>
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;
}
/// <summary>
/// Reads the 16 bit color palette from the stream.
/// </summary>
@ -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.");

24
src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs

@ -16,4 +16,28 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions
/// which can occur during decoding run length encoded bitmaps.
/// </summary>
public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; }
/// <summary>
/// Gets a value indicating whether the additional AlphaMask is processed at decoding time.
/// </summary>
/// <remarks>
/// It will be used at IcoDecoder.
/// </remarks>
internal bool ProcessedAlphaMask { get; init; }
/// <summary>
/// Gets a value indicating whether to skip loading the BMP file header.
/// </summary>
/// <remarks>
/// It will be used at IcoDecoder.
/// </remarks>
internal bool SkipFileHeader { get; init; }
/// <summary>
/// Gets a value indicating whether the height is double of true height.
/// </summary>
/// <remarks>
/// It will be used at IcoDecoder.
/// </remarks>
internal bool IsDoubleHeight { get; init; }
}

Loading…
Cancel
Save