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