@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System ;
using System.Buffers ;
using System.Buffers.Binary ;
using System.IO ;
using System.Numerics ;
using System.Runtime.CompilerServices ;
using SixLabors.ImageSharp.Common.Helpers ;
using SixLabors.ImageSharp.Memory ;
@ -14,7 +16,7 @@ using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Performs the bmp decoding operation.
/// Performs the bit ma p decoding operation.
/// </summary>
/// <remarks>
/// A useful decoding source example can be found at <see href="https://dxr.mozilla.org/mozilla-central/source/image/decoders/nsBMPDecoder.cpp"/>
@ -22,19 +24,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal sealed class BmpDecoderCore
{
/// <summary>
/// The mask for the red part of the color for 16 bit rgb bitmaps.
/// The default mask for the red part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16RMask = 0x7C00 ;
private const int Default Rgb16RMask = 0x7C00 ;
/// <summary>
/// The mask for the green part of the color for 16 bit rgb bitmaps.
/// The default mask for the green part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16GMask = 0x3E0 ;
private const int Default Rgb16GMask = 0x3E0 ;
/// <summary>
/// The mask for the blue part of the color for 16 bit rgb bitmaps.
/// The default mask for the blue part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16BMask = 0x1F ;
private const int Default Rgb16BMask = 0x1F ;
/// <summary>
/// RLE8 flag value that indicates following byte has special meaning.
@ -62,10 +64,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private Stream stream ;
/// <summary>
/// The metadata
/// The metadata.
/// </summary>
private ImageMetaData metaData ;
/// <summary>
/// The bmp specific metadata.
/// </summary>
private BmpMetaData bmpMetaData ;
/// <summary>
/// The file header containing general information.
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
@ -85,7 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options</param>
/// <param name="options">The options. </param>
public BmpDecoderCore ( Configuration configuration , IBmpDecoderOptions options )
{
this . configuration = configuration ;
@ -119,7 +126,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpCompression . RGB :
if ( this . infoHeader . BitsPerPixel = = 3 2 )
{
this . ReadRgb32 ( pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
if ( this . bmpMetaData . InfoHeaderType = = BmpInfoHeaderType . WinVersion3 )
{
this . ReadRgb32Slow ( pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
}
else
{
this . ReadRgb32Fast ( pixels , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
}
}
else if ( this . infoHeader . BitsPerPixel = = 2 4 )
{
@ -146,6 +160,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this . ReadRle8 ( pixels , palette , this . infoHeader . Width , this . infoHeader . Height , inverted ) ;
break ;
case BmpCompression . BitFields :
this . ReadBitFields ( pixels , inverted ) ;
break ;
default :
throw new NotSupportedException ( "Does not support this kind of bitmap files." ) ;
}
@ -199,12 +219,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Performs final shifting from a 5bit value to an 8bit one.
/// Decodes a bitmap containing BITFIELDS Compression type. For each color channel, there will be bitmask
/// which will be used to determine which bits belong to that channel.
/// </summary>
/// <param name="value">The masked and shifted value</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue ( int value ) = > ( byte ) ( ( value < < 3 ) | ( value > > 2 ) ) ;
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 )
where TPixel : struct , IPixel < TPixel >
{
if ( this . infoHeader . BitsPerPixel = = 1 6 )
{
this . ReadRgb16 (
pixels ,
this . infoHeader . Width ,
this . infoHeader . Height ,
inverted ,
this . infoHeader . RedMask ,
this . infoHeader . GreenMask ,
this . infoHeader . BlueMask ) ;
}
else
{
this . ReadRgb32BitFields (
pixels ,
this . infoHeader . Width ,
this . infoHeader . Height ,
inverted ,
this . infoHeader . RedMask ,
this . infoHeader . GreenMask ,
this . infoHeader . BlueMask ,
this . infoHeader . AlphaMask ) ;
}
}
/// <summary>
/// Looks up color values and builds the image from de-compressed RLE8 data.
@ -240,12 +287,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Produce uncompressed bitmap data from RLE8 stream
/// Produce uncompressed bitmap data from RLE8 stream.
/// </summary>
/// <remarks>
/// RLE8 is a 2-byte run-length encoding
/// <br/>If first byte is 0, the second byte may have special meaning
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run
/// RLE8 is a 2-byte run-length encoding.
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run.
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
@ -382,20 +429,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 16 bit color palette from the stream
/// Reads the 16 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 ReadRgb16 < TPixel > ( Buffer2D < TPixel > pixels , int width , int height , bool inverted )
/// <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 )
where TPixel : struct , IPixel < TPixel >
{
int padding = CalculatePadding ( width , 2 ) ;
int stride = ( width * 2 ) + padding ;
TPixel color = default ;
int rightShiftRedMask = CalculateRightShift ( ( uint ) redMask ) ;
int rightShiftGreenMask = CalculateRightShift ( ( uint ) greenMask ) ;
int rightShiftBlueMask = CalculateRightShift ( ( uint ) blueMask ) ;
// Each color channel contains either 5 or 6 Bits values.
int redMaskBits = CountBits ( ( uint ) redMask ) ;
int greenMaskBits = CountBits ( ( uint ) greenMask ) ;
int blueMaskBits = CountBits ( ( uint ) blueMask ) ;
using ( IManagedByteBuffer buffer = this . memoryAllocator . AllocateManagedByteBuffer ( stride ) )
{
for ( int y = 0 ; y < height ; y + + )
@ -409,10 +468,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
short temp = BitConverter . ToInt16 ( buffer . Array , offset ) ;
var rgb = new Rgb24 (
GetBytesFrom5BitValue ( ( temp & Rgb16RMask ) > > 1 0 ) ,
GetBytesFrom5BitValue ( ( temp & Rgb16GMask ) > > 5 ) ,
GetBytesFrom5BitValue ( temp & Rgb16BMask ) ) ;
// Rescale values, so the values range from 0 to 255.
int r = ( redMaskBits = = 5 ) ? GetBytesFrom5BitValue ( ( temp & redMask ) > > rightShiftRedMask ) : GetBytesFrom6BitValue ( ( temp & redMask ) > > rightShiftRedMask ) ;
int g = ( greenMaskBits = = 5 ) ? GetBytesFrom5BitValue ( ( temp & greenMask ) > > rightShiftGreenMask ) : GetBytesFrom6BitValue ( ( temp & greenMask ) > > rightShiftGreenMask ) ;
int b = ( blueMaskBits = = 5 ) ? GetBytesFrom5BitValue ( ( temp & blueMask ) > > rightShiftBlueMask ) : GetBytesFrom6BitValue ( ( temp & blueMask ) > > rightShiftBlueMask ) ;
var rgb = new Rgb24 ( ( byte ) r , ( byte ) g , ( byte ) b ) ;
color . FromRgb24 ( rgb ) ;
pixelRow [ x ] = color ;
@ -423,7 +483,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 24 bit color palette from the stream
/// Performs final shifting from a 5bit value to an 8bit one.
/// </summary>
/// <param name="value">The masked and shifted value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue ( int value ) = > ( byte ) ( ( value < < 3 ) | ( value > > 2 ) ) ;
/// <summary>
/// Performs final shifting from a 6bit value to an 8bit one.
/// </summary>
/// <param name="value">The masked and shifted value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom6BitValue ( int value ) = > ( byte ) ( ( value < < 2 ) | ( value > > 4 ) ) ;
/// <summary>
/// Reads the 24 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
@ -452,14 +528,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 32 bit color palette from the stream
/// Reads the 32 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 ReadRgb32 < TPixel > ( Buffer2D < TPixel > pixels , int width , int height , bool inverted )
private void ReadRgb32Fast < TPixel > ( Buffer2D < TPixel > pixels , int width , int height , bool inverted )
where TPixel : struct , IPixel < TPixel >
{
int padding = CalculatePadding ( width , 4 ) ;
@ -480,6 +556,228 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
/// <summary>
/// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel.
/// 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="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 )
where TPixel : struct , IPixel < TPixel >
{
int padding = CalculatePadding ( width , 4 ) ;
using ( IManagedByteBuffer row = this . memoryAllocator . AllocatePaddedPixelRowBuffer ( width , 4 , padding ) )
using ( IMemoryOwner < Bgra32 > bgraRow = this . memoryAllocator . Allocate < Bgra32 > ( width ) )
{
Span < Bgra32 > bgraRowSpan = bgraRow . GetSpan ( ) ;
long currentPosition = this . stream . Position ;
bool hasAlpha = false ;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for ( int y = 0 ; y < height ; y + + )
{
this . stream . Read ( row ) ;
PixelOperations < Bgra32 > . Instance . FromBgra32Bytes (
this . configuration ,
row . GetSpan ( ) ,
bgraRowSpan ,
width ) ;
// Check each pixel in the row to see if it has an alpha value.
for ( int x = 0 ; x < width ; x + + )
{
Bgra32 bgra = bgraRowSpan [ x ] ;
if ( bgra . A > 0 )
{
hasAlpha = true ;
break ;
}
}
if ( hasAlpha )
{
break ;
}
}
// Reset our stream for a second pass.
this . stream . Position = currentPosition ;
// Process the pixels in bulk taking the raw alpha component value.
if ( hasAlpha )
{
for ( int y = 0 ; y < height ; y + + )
{
this . stream . Read ( row ) ;
int newY = Invert ( y , height , inverted ) ;
Span < TPixel > pixelSpan = pixels . GetRowSpan ( newY ) ;
PixelOperations < TPixel > . Instance . FromBgra32Bytes (
this . configuration ,
row . GetSpan ( ) ,
pixelSpan ,
width ) ;
}
return ;
}
// Slow path. We need to set each alpha component value to fully opaque.
for ( int y = 0 ; y < height ; y + + )
{
this . stream . Read ( row ) ;
PixelOperations < Bgra32 > . Instance . FromBgra32Bytes (
this . configuration ,
row . GetSpan ( ) ,
bgraRowSpan ,
width ) ;
int newY = Invert ( y , height , inverted ) ;
Span < TPixel > pixelSpan = pixels . GetRowSpan ( newY ) ;
for ( int x = 0 ; x < width ; x + + )
{
Bgra32 bgra = bgraRowSpan [ x ] ;
bgra . A = byte . MaxValue ;
ref TPixel pixel = ref pixelSpan [ x ] ;
pixel . FromBgra32 ( bgra ) ;
}
}
}
}
/// <summary>
/// Decode an 32 Bit Bitmap containing a bitmask for each color channel.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
/// <param name="inverted">Whether the bitmap is inverted.</param>
/// <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>
/// <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 )
where TPixel : struct , IPixel < TPixel >
{
TPixel color = default ;
int padding = CalculatePadding ( width , 4 ) ;
int stride = ( width * 4 ) + padding ;
int rightShiftRedMask = CalculateRightShift ( ( uint ) redMask ) ;
int rightShiftGreenMask = CalculateRightShift ( ( uint ) greenMask ) ;
int rightShiftBlueMask = CalculateRightShift ( ( uint ) blueMask ) ;
int rightShiftAlphaMask = CalculateRightShift ( ( uint ) alphaMask ) ;
int bitsRedMask = CountBits ( ( uint ) redMask ) ;
int bitsGreenMask = CountBits ( ( uint ) greenMask ) ;
int bitsBlueMask = CountBits ( ( uint ) blueMask ) ;
int bitsAlphaMask = CountBits ( ( uint ) alphaMask ) ;
float invMaxValueRed = 1.0f / ( 0xFFFFFFFF > > ( 3 2 - bitsRedMask ) ) ;
float invMaxValueGreen = 1.0f / ( 0xFFFFFFFF > > ( 3 2 - bitsGreenMask ) ) ;
float invMaxValueBlue = 1.0f / ( 0xFFFFFFFF > > ( 3 2 - bitsBlueMask ) ) ;
uint maxValueAlpha = 0xFFFFFFFF > > ( 3 2 - bitsAlphaMask ) ;
float invMaxValueAlpha = 1.0f / maxValueAlpha ;
bool unusualBitMask = false ;
if ( bitsRedMask > 8 | | bitsGreenMask > 8 | | bitsBlueMask > 8 | | invMaxValueAlpha > 8 )
{
unusualBitMask = true ;
}
using ( IManagedByteBuffer buffer = this . memoryAllocator . AllocateManagedByteBuffer ( stride ) )
{
for ( int y = 0 ; y < height ; y + + )
{
this . stream . Read ( buffer . Array , 0 , stride ) ;
int newY = Invert ( y , height , inverted ) ;
Span < TPixel > pixelRow = pixels . GetRowSpan ( newY ) ;
int offset = 0 ;
for ( int x = 0 ; x < width ; x + + )
{
uint temp = BitConverter . ToUInt32 ( buffer . Array , offset ) ;
if ( unusualBitMask )
{
uint r = ( uint ) ( temp & redMask ) > > rightShiftRedMask ;
uint g = ( uint ) ( temp & greenMask ) > > rightShiftGreenMask ;
uint b = ( uint ) ( temp & blueMask ) > > rightShiftBlueMask ;
float alpha = alphaMask ! = 0 ? invMaxValueAlpha * ( ( uint ) ( temp & alphaMask ) > > rightShiftAlphaMask ) : 1.0f ;
var vector4 = new Vector4 (
r * invMaxValueRed ,
g * invMaxValueGreen ,
b * invMaxValueBlue ,
alpha ) ;
color . FromVector4 ( vector4 ) ;
}
else
{
byte r = ( byte ) ( ( temp & redMask ) > > rightShiftRedMask ) ;
byte g = ( byte ) ( ( temp & greenMask ) > > rightShiftGreenMask ) ;
byte b = ( byte ) ( ( temp & blueMask ) > > rightShiftBlueMask ) ;
byte a = alphaMask ! = 0 ? ( byte ) ( ( temp & alphaMask ) > > rightShiftAlphaMask ) : ( byte ) 2 5 5 ;
color . FromRgba32 ( new Rgba32 ( r , g , b , a ) ) ;
}
pixelRow [ x ] = color ;
offset + = 4 ;
}
}
}
}
/// <summary>
/// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right).
/// </summary>
/// <param name="n">The color bit mask.</param>
/// <returns>Number of bits to shift right.</returns>
private static int CalculateRightShift ( uint n )
{
int count = 0 ;
while ( n > 0 )
{
if ( ( 1 & n ) = = 0 )
{
count + + ;
}
else
{
break ;
}
n > > = 1 ;
}
return count ;
}
/// <summary>
/// Counts none zero bits.
/// </summary>
/// <param name="n">A color mask.</param>
/// <returns>The none zero bits.</returns>
private static int CountBits ( uint n )
{
int count = 0 ;
while ( n ! = 0 )
{
count + + ;
n & = n - 1 ;
}
return count ;
}
/// <summary>
/// Reads the <see cref="BmpInfoHeader"/> from the stream.
/// </summary>
@ -508,20 +806,54 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// read the rest of the header
this . stream . Read ( buffer , BmpInfoHeader . HeaderSizeSize , headerSize - BmpInfoHeader . HeaderSizeSize ) ;
BmpInfoHeaderType inofHeaderType = BmpInfoHeaderType . WinVersion2 ;
if ( headerSize = = BmpInfoHeader . CoreSize )
{
// 12 bytes
inofHeaderType = BmpInfoHeaderType . WinVersion2 ;
this . infoHeader = BmpInfoHeader . ParseCore ( buffer ) ;
}
else if ( headerSize = = BmpInfoHeader . Os22ShortSize )
{
// 16 bytes
inofHeaderType = BmpInfoHeaderType . Os2Version2Short ;
this . infoHeader = BmpInfoHeader . ParseOs22Short ( buffer ) ;
}
else if ( headerSize > = BmpInfoHeader . Size )
else if ( headerSize = = BmpInfoHeader . SizeV3 )
{
// == 40 bytes
inofHeaderType = BmpInfoHeaderType . WinVersion3 ;
this . infoHeader = BmpInfoHeader . ParseV3 ( buffer ) ;
// if the info header is BMP version 3 and the compression type is BITFIELDS,
// color masks for each color channel follow the info header.
if ( this . infoHeader . Compression = = BmpCompression . BitFields )
{
byte [ ] bitfieldsBuffer = new byte [ 1 2 ] ;
this . stream . Read ( bitfieldsBuffer , 0 , 1 2 ) ;
Span < byte > data = bitfieldsBuffer . AsSpan < byte > ( ) ;
this . infoHeader . RedMask = BinaryPrimitives . ReadInt32LittleEndian ( data . Slice ( 0 , 4 ) ) ;
this . infoHeader . GreenMask = BinaryPrimitives . ReadInt32LittleEndian ( data . Slice ( 4 , 4 ) ) ;
this . infoHeader . BlueMask = BinaryPrimitives . ReadInt32LittleEndian ( data . Slice ( 8 , 4 ) ) ;
}
}
else if ( headerSize = = BmpInfoHeader . AdobeV3Size )
{
// == 52 bytes
inofHeaderType = BmpInfoHeaderType . AdobeVersion3 ;
this . infoHeader = BmpInfoHeader . ParseAdobeV3 ( buffer , withAlpha : false ) ;
}
else if ( headerSize = = BmpInfoHeader . AdobeV3WithAlphaSize )
{
// == 56 bytes
inofHeaderType = BmpInfoHeaderType . AdobeVersion3WithAlpha ;
this . infoHeader = BmpInfoHeader . ParseAdobeV3 ( buffer , withAlpha : true ) ;
}
else if ( headerSize > = BmpInfoHeader . SizeV4 )
{
// >= 40 bytes
this . infoHeader = BmpInfoHeader . Parse ( buffer ) ;
// >= 108 bytes
inofHeaderType = headerSize = = BmpInfoHeader . SizeV4 ? BmpInfoHeaderType . WinVersion4 : BmpInfoHeaderType . WinVersion5 ;
this . infoHeader = BmpInfoHeader . ParseV4 ( buffer ) ;
}
else
{
@ -548,13 +880,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this . metaData = meta ;
short bitsPerPixel = this . infoHeader . BitsPerPixel ;
BmpMetaData bmpMetaData = this . metaData . GetFormatMetaData ( BmpFormat . Instance ) ;
this . bmpMetaData = this . metaData . GetFormatMetaData ( BmpFormat . Instance ) ;
this . bmpMetaData . InfoHeaderType = inofHeaderType ;
// We can only encode at these bit rates so far.
if ( bitsPerPixel . Equals ( ( short ) BmpBitsPerPixel . Pixel24 )
| | bitsPerPixel . Equals ( ( short ) BmpBitsPerPixel . Pixel32 ) )
{
bmpMetaData . BitsPerPixel = ( BmpBitsPerPixel ) bitsPerPixel ;
this . bmpMetaData . BitsPerPixel = ( BmpBitsPerPixel ) bitsPerPixel ;
}
// skip the remaining header because we can't read those parts