|
|
|
@ -1,9 +1,9 @@ |
|
|
|
// Copyright (c) Six Labors.
|
|
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
#nullable disable |
|
|
|
|
|
|
|
using System.Buffers; |
|
|
|
using System.Buffers.Binary; |
|
|
|
using System.Diagnostics.CodeAnalysis; |
|
|
|
using System.Numerics; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using SixLabors.ImageSharp.Common.Helpers; |
|
|
|
@ -58,20 +58,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// </summary>
|
|
|
|
private const int RleDelta = 0x02; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The stream to decode from.
|
|
|
|
/// </summary>
|
|
|
|
private BufferedReadStream stream; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The metadata.
|
|
|
|
/// </summary>
|
|
|
|
private ImageMetadata metadata; |
|
|
|
private ImageMetadata? metadata; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The bitmap specific metadata.
|
|
|
|
/// </summary>
|
|
|
|
private BmpMetadata bmpMetadata; |
|
|
|
private BmpMetadata? bmpMetadata; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The file header containing general information.
|
|
|
|
@ -126,7 +121,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
Image<TPixel> image = null; |
|
|
|
Image<TPixel>? image = null; |
|
|
|
try |
|
|
|
{ |
|
|
|
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); |
|
|
|
@ -142,24 +137,25 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
{ |
|
|
|
if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) |
|
|
|
{ |
|
|
|
this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (this.infoHeader.BitsPerPixel == 24) |
|
|
|
{ |
|
|
|
this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
} |
|
|
|
else if (this.infoHeader.BitsPerPixel == 16) |
|
|
|
{ |
|
|
|
this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
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, |
|
|
|
@ -172,19 +168,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
break; |
|
|
|
|
|
|
|
case BmpCompression.RLE24: |
|
|
|
this.ReadRle24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case BmpCompression.RLE8: |
|
|
|
case BmpCompression.RLE4: |
|
|
|
this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
this.ReadRle(stream, this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case BmpCompression.BitFields: |
|
|
|
case BmpCompression.BI_ALPHABITFIELDS: |
|
|
|
this.ReadBitFields(pixels, inverted); |
|
|
|
this.ReadBitFields(stream, pixels, inverted); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
@ -250,14 +246,16 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// which will be used to determine which bits belong to that channel.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadBitFields<TPixel>(Buffer2D<TPixel> pixels, bool inverted) |
|
|
|
private void ReadBitFields<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, bool inverted) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
if (this.infoHeader.BitsPerPixel == 16) |
|
|
|
{ |
|
|
|
this.ReadRgb16( |
|
|
|
stream, |
|
|
|
pixels, |
|
|
|
this.infoHeader.Width, |
|
|
|
this.infoHeader.Height, |
|
|
|
@ -269,6 +267,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
else |
|
|
|
{ |
|
|
|
this.ReadRgb32BitFields( |
|
|
|
stream, |
|
|
|
pixels, |
|
|
|
this.infoHeader.Width, |
|
|
|
this.infoHeader.Height, |
|
|
|
@ -282,17 +281,17 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data.
|
|
|
|
/// Compressed RLE8 stream is uncompressed by <see cref="UncompressRle8(int, Span{byte}, Span{bool}, Span{bool})"/>
|
|
|
|
/// Compressed RLE4 stream is uncompressed by <see cref="UncompressRle4(int, Span{byte}, Span{bool}, Span{bool})"/>
|
|
|
|
/// Compressed RLE4 stream is uncompressed by <see cref="UncompressRle4(BufferedReadStream, int, Span{byte}, Span{bool}, Span{bool})"/>
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="compression">The compression type. Either RLE4 or RLE8.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadRle<TPixel>(BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted) |
|
|
|
private void ReadRle<TPixel>(BufferedReadStream stream, BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
@ -305,11 +304,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
Span<byte> bufferSpan = buffer.Memory.Span; |
|
|
|
if (compression is BmpCompression.RLE8) |
|
|
|
{ |
|
|
|
this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); |
|
|
|
this.UncompressRle8(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); |
|
|
|
this.UncompressRle4(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); |
|
|
|
} |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
@ -368,11 +367,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// Looks up color values and builds the image from de-compressed RLE24.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadRle24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
private void ReadRle24<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
@ -384,7 +384,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span; |
|
|
|
Span<byte> bufferSpan = buffer.GetSpan(); |
|
|
|
|
|
|
|
this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); |
|
|
|
this.UncompressRle24(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
@ -446,18 +446,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <br/>If first byte is 0, the second byte may have special meaning.
|
|
|
|
/// <br/>Otherwise, the first byte is the length of the run and second byte contains two color indexes.
|
|
|
|
/// </remarks>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="w">The width of the bitmap.</param>
|
|
|
|
/// <param name="buffer">Buffer for uncompressed data.</param>
|
|
|
|
/// <param name="undefinedPixels">Keeps track over skipped and therefore undefined pixels.</param>
|
|
|
|
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
|
|
|
|
private void UncompressRle4(int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels) |
|
|
|
private void UncompressRle4(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels) |
|
|
|
{ |
|
|
|
Span<byte> cmd = stackalloc byte[2]; |
|
|
|
int count = 0; |
|
|
|
|
|
|
|
while (count < buffer.Length) |
|
|
|
{ |
|
|
|
if (this.stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
if (stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); |
|
|
|
} |
|
|
|
@ -478,8 +479,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
break; |
|
|
|
|
|
|
|
case RleDelta: |
|
|
|
int dx = this.stream.ReadByte(); |
|
|
|
int dy = this.stream.ReadByte(); |
|
|
|
int dx = stream.ReadByte(); |
|
|
|
int dy = stream.ReadByte(); |
|
|
|
count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); |
|
|
|
|
|
|
|
break; |
|
|
|
@ -492,7 +493,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
byte[] run = new byte[bytesToRead]; |
|
|
|
|
|
|
|
this.stream.Read(run, 0, run.Length); |
|
|
|
stream.Read(run, 0, run.Length); |
|
|
|
|
|
|
|
int idx = 0; |
|
|
|
for (int i = 0; i < max; i++) |
|
|
|
@ -512,7 +513,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
// Absolute mode data is aligned to two-byte word-boundary.
|
|
|
|
int padding = bytesToRead & 1; |
|
|
|
|
|
|
|
this.stream.Skip(padding); |
|
|
|
stream.Skip(padding); |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
@ -551,18 +552,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <br/>If first byte is 0, the second byte may have special meaning.
|
|
|
|
/// <br/>Otherwise, the first byte is the length of the run and second byte is the color for the run.
|
|
|
|
/// </remarks>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="w">The width of the bitmap.</param>
|
|
|
|
/// <param name="buffer">Buffer for uncompressed data.</param>
|
|
|
|
/// <param name="undefinedPixels">Keeps track of skipped and therefore undefined pixels.</param>
|
|
|
|
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
|
|
|
|
private void UncompressRle8(int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels) |
|
|
|
private void UncompressRle8(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels) |
|
|
|
{ |
|
|
|
Span<byte> cmd = stackalloc byte[2]; |
|
|
|
int count = 0; |
|
|
|
|
|
|
|
while (count < buffer.Length) |
|
|
|
{ |
|
|
|
if (this.stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
if (stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); |
|
|
|
} |
|
|
|
@ -583,8 +585,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
break; |
|
|
|
|
|
|
|
case RleDelta: |
|
|
|
int dx = this.stream.ReadByte(); |
|
|
|
int dy = this.stream.ReadByte(); |
|
|
|
int dx = stream.ReadByte(); |
|
|
|
int dy = stream.ReadByte(); |
|
|
|
count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); |
|
|
|
|
|
|
|
break; |
|
|
|
@ -596,7 +598,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
byte[] run = new byte[length]; |
|
|
|
|
|
|
|
this.stream.Read(run, 0, run.Length); |
|
|
|
stream.Read(run, 0, run.Length); |
|
|
|
|
|
|
|
run.AsSpan().CopyTo(buffer[count..]); |
|
|
|
|
|
|
|
@ -605,7 +607,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
// Absolute mode data is aligned to two-byte word-boundary.
|
|
|
|
int padding = length & 1; |
|
|
|
|
|
|
|
this.stream.Skip(padding); |
|
|
|
stream.Skip(padding); |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
@ -630,18 +632,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <br/>If first byte is 0, the second byte may have special meaning.
|
|
|
|
/// <br/>Otherwise, the first byte is the length of the run and following three bytes are the color for the run.
|
|
|
|
/// </remarks>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="w">The width of the bitmap.</param>
|
|
|
|
/// <param name="buffer">Buffer for uncompressed data.</param>
|
|
|
|
/// <param name="undefinedPixels">Keeps track of skipped and therefore undefined pixels.</param>
|
|
|
|
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
|
|
|
|
private void UncompressRle24(int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels) |
|
|
|
private void UncompressRle24(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels) |
|
|
|
{ |
|
|
|
Span<byte> cmd = stackalloc byte[2]; |
|
|
|
int uncompressedPixels = 0; |
|
|
|
|
|
|
|
while (uncompressedPixels < buffer.Length) |
|
|
|
{ |
|
|
|
if (this.stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
if (stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap."); |
|
|
|
} |
|
|
|
@ -662,8 +665,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
break; |
|
|
|
|
|
|
|
case RleDelta: |
|
|
|
int dx = this.stream.ReadByte(); |
|
|
|
int dy = this.stream.ReadByte(); |
|
|
|
int dx = stream.ReadByte(); |
|
|
|
int dy = stream.ReadByte(); |
|
|
|
uncompressedPixels += RleSkipDelta(uncompressedPixels, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); |
|
|
|
|
|
|
|
break; |
|
|
|
@ -675,7 +678,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
byte[] run = new byte[length * 3]; |
|
|
|
|
|
|
|
this.stream.Read(run, 0, run.Length); |
|
|
|
stream.Read(run, 0, run.Length); |
|
|
|
|
|
|
|
run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]); |
|
|
|
|
|
|
|
@ -684,7 +687,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
// Absolute mode data is aligned to two-byte word-boundary.
|
|
|
|
int padding = run.Length & 1; |
|
|
|
|
|
|
|
this.stream.Skip(padding); |
|
|
|
stream.Skip(padding); |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
@ -693,8 +696,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
{ |
|
|
|
int max = uncompressedPixels + cmd[0]; |
|
|
|
byte blueIdx = cmd[1]; |
|
|
|
byte greenIdx = (byte)this.stream.ReadByte(); |
|
|
|
byte redIdx = (byte)this.stream.ReadByte(); |
|
|
|
byte greenIdx = (byte)stream.ReadByte(); |
|
|
|
byte redIdx = (byte)stream.ReadByte(); |
|
|
|
|
|
|
|
int bufferIdx = uncompressedPixels * 3; |
|
|
|
for (; uncompressedPixels < max; uncompressedPixels++) |
|
|
|
@ -800,6 +803,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads the color palette from the stream.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
@ -808,7 +812,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <param name="bytesPerColorMapEntry">Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
|
|
|
|
/// the bytes per color palette entry's can be 3 bytes instead of 4.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) |
|
|
|
private void ReadRgbPalette<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).
|
|
|
|
@ -833,7 +837,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
if (this.stream.Read(rowSpan) == 0) |
|
|
|
if (stream.Read(rowSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -861,6 +865,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads the 16 bit color palette from the stream.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
@ -868,7 +873,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <param name="redMask">The bitmask for the red channel.</param>
|
|
|
|
/// <param name="greenMask">The bitmask for the green channel.</param>
|
|
|
|
/// <param name="blueMask">The bitmask for the blue channel.</param>
|
|
|
|
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) |
|
|
|
private void ReadRgb16<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int padding = CalculatePadding(width, 2); |
|
|
|
@ -889,7 +894,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(bufferSpan) == 0) |
|
|
|
if (stream.Read(bufferSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -935,11 +940,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads the 24 bit color palette from the stream.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadRgb24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
private void ReadRgb24<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int padding = CalculatePadding(width, 3); |
|
|
|
@ -948,7 +954,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(rowSpan) == 0) |
|
|
|
if (stream.Read(rowSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -967,11 +973,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// Reads the 32 bit color palette from the stream.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadRgb32Fast<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
private void ReadRgb32Fast<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int padding = CalculatePadding(width, 4); |
|
|
|
@ -980,7 +987,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(rowSpan) == 0) |
|
|
|
if (stream.Read(rowSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -1000,11 +1007,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="width">The width of the bitmap.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
private void ReadRgb32Slow<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int padding = CalculatePadding(width, 4); |
|
|
|
@ -1012,7 +1020,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
using IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width); |
|
|
|
Span<byte> rowSpan = row.GetSpan(); |
|
|
|
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan(); |
|
|
|
long currentPosition = this.stream.Position; |
|
|
|
long currentPosition = stream.Position; |
|
|
|
bool hasAlpha = false; |
|
|
|
|
|
|
|
// Loop though the rows checking each pixel. We start by assuming it's
|
|
|
|
@ -1020,7 +1028,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
// actually a BGRA image, and change tactics accordingly.
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(rowSpan) == 0) |
|
|
|
if (stream.Read(rowSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -1049,14 +1057,14 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
} |
|
|
|
|
|
|
|
// Reset our stream for a second pass.
|
|
|
|
this.stream.Position = currentPosition; |
|
|
|
stream.Position = currentPosition; |
|
|
|
|
|
|
|
// Process the pixels in bulk taking the raw alpha component value.
|
|
|
|
if (hasAlpha) |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(rowSpan) == 0) |
|
|
|
if (stream.Read(rowSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -1077,7 +1085,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
// Slow path. We need to set each alpha component value to fully opaque.
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(rowSpan) == 0) |
|
|
|
if (stream.Read(rowSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -1105,6 +1113,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// Decode an 32 Bit Bitmap containing a bitmask for each color channel.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
|
|
|
|
/// <param name="width">The width of the image.</param>
|
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
@ -1113,7 +1122,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <param name="greenMask">The bitmask for the green channel.</param>
|
|
|
|
/// <param name="blueMask">The bitmask for the blue channel.</param>
|
|
|
|
/// <param name="alphaMask">The bitmask for the alpha channel.</param>
|
|
|
|
private void ReadRgb32BitFields<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) |
|
|
|
private void ReadRgb32BitFields<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
@ -1142,7 +1151,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
if (this.stream.Read(bufferSpan) == 0) |
|
|
|
if (stream.Read(bufferSpan) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); |
|
|
|
} |
|
|
|
@ -1228,10 +1237,13 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// Reads the <see cref="BmpInfoHeader"/> from the stream.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadInfoHeader() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
[MemberNotNull(nameof(metadata))] |
|
|
|
[MemberNotNull(nameof(bmpMetadata))] |
|
|
|
private void ReadInfoHeader(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
Span<byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; |
|
|
|
long infoHeaderStart = this.stream.Position; |
|
|
|
long infoHeaderStart = stream.Position; |
|
|
|
|
|
|
|
// Resolution is stored in PPM.
|
|
|
|
this.metadata = new ImageMetadata |
|
|
|
@ -1240,7 +1252,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
}; |
|
|
|
|
|
|
|
// Read the header size.
|
|
|
|
this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); |
|
|
|
stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); |
|
|
|
|
|
|
|
int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); |
|
|
|
if (headerSize is < BmpInfoHeader.CoreSize or > BmpInfoHeader.MaxHeaderSize) |
|
|
|
@ -1249,7 +1261,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
} |
|
|
|
|
|
|
|
// Read the rest of the header.
|
|
|
|
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); |
|
|
|
stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); |
|
|
|
|
|
|
|
BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2; |
|
|
|
if (headerSize == BmpInfoHeader.CoreSize) |
|
|
|
@ -1275,7 +1287,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
if (this.infoHeader.Compression == BmpCompression.BitFields) |
|
|
|
{ |
|
|
|
byte[] bitfieldsBuffer = new byte[12]; |
|
|
|
this.stream.Read(bitfieldsBuffer, 0, 12); |
|
|
|
stream.Read(bitfieldsBuffer, 0, 12); |
|
|
|
Span<byte> data = bitfieldsBuffer.AsSpan(); |
|
|
|
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); |
|
|
|
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); |
|
|
|
@ -1284,7 +1296,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) |
|
|
|
{ |
|
|
|
byte[] bitfieldsBuffer = new byte[16]; |
|
|
|
this.stream.Read(bitfieldsBuffer, 0, 16); |
|
|
|
stream.Read(bitfieldsBuffer, 0, 16); |
|
|
|
Span<byte> data = bitfieldsBuffer.AsSpan(); |
|
|
|
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); |
|
|
|
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); |
|
|
|
@ -1324,12 +1336,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0) |
|
|
|
{ |
|
|
|
// Read color profile.
|
|
|
|
long streamPosition = this.stream.Position; |
|
|
|
long streamPosition = stream.Position; |
|
|
|
byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; |
|
|
|
this.stream.Position = infoHeaderStart + this.infoHeader.ProfileData; |
|
|
|
this.stream.Read(iccProfileData); |
|
|
|
stream.Position = infoHeaderStart + this.infoHeader.ProfileData; |
|
|
|
stream.Read(iccProfileData); |
|
|
|
this.metadata.IccProfile = new IccProfile(iccProfileData); |
|
|
|
this.stream.Position = streamPosition; |
|
|
|
stream.Position = streamPosition; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
@ -1358,10 +1370,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <summary>
|
|
|
|
/// Reads the <see cref="BmpFileHeader"/> from the stream.
|
|
|
|
/// </summary>
|
|
|
|
private void ReadFileHeader() |
|
|
|
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
|
|
|
|
private void ReadFileHeader(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
Span<byte> buffer = stackalloc byte[BmpFileHeader.Size]; |
|
|
|
this.stream.Read(buffer, 0, BmpFileHeader.Size); |
|
|
|
stream.Read(buffer, 0, BmpFileHeader.Size); |
|
|
|
|
|
|
|
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); |
|
|
|
switch (fileTypeMarker) |
|
|
|
@ -1375,7 +1388,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
// Because we only decode the first bitmap in the array, the array header will be ignored.
|
|
|
|
// The bitmap file header of the first image follows the array header.
|
|
|
|
this.stream.Read(buffer, 0, BmpFileHeader.Size); |
|
|
|
stream.Read(buffer, 0, BmpFileHeader.Size); |
|
|
|
this.fileHeader = BmpFileHeader.Parse(buffer); |
|
|
|
if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) |
|
|
|
{ |
|
|
|
@ -1398,12 +1411,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
/// <param name="palette">The color palette.</param>
|
|
|
|
/// <returns>Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
|
|
|
|
/// the bytes per color palette entry's can be 3 bytes instead of 4.</returns>
|
|
|
|
[MemberNotNull(nameof(metadata))] |
|
|
|
[MemberNotNull(nameof(bmpMetadata))] |
|
|
|
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette) |
|
|
|
{ |
|
|
|
this.stream = stream; |
|
|
|
|
|
|
|
this.ReadFileHeader(); |
|
|
|
this.ReadInfoHeader(); |
|
|
|
this.ReadFileHeader(stream); |
|
|
|
this.ReadInfoHeader(stream); |
|
|
|
|
|
|
|
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
|
|
|
|
// If the height is negative, then this is a Windows bitmap whose origin
|
|
|
|
@ -1451,13 +1464,13 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry; |
|
|
|
} |
|
|
|
|
|
|
|
palette = null; |
|
|
|
palette = Array.Empty<byte>(); |
|
|
|
|
|
|
|
if (colorMapSizeBytes > 0) |
|
|
|
{ |
|
|
|
// 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 ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) |
|
|
|
if ((stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) |
|
|
|
{ |
|
|
|
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."); |
|
|
|
@ -1465,21 +1478,21 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
palette = new byte[colorMapSizeBytes]; |
|
|
|
|
|
|
|
if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0) |
|
|
|
if (stream.Read(palette, 0, colorMapSizeBytes) == 0) |
|
|
|
{ |
|
|
|
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
int skipAmount = this.fileHeader.Offset - (int)this.stream.Position; |
|
|
|
if ((skipAmount + (int)this.stream.Position) > this.stream.Length) |
|
|
|
int skipAmount = this.fileHeader.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."); |
|
|
|
} |
|
|
|
|
|
|
|
if (skipAmount > 0) |
|
|
|
{ |
|
|
|
this.stream.Skip(skipAmount); |
|
|
|
stream.Skip(skipAmount); |
|
|
|
} |
|
|
|
|
|
|
|
return bytesPerColorMapEntry; |
|
|
|
|