|
|
|
@ -34,29 +34,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
private const int Rgb16BMask = 0x1F; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// RLE8 flag value that indicates following byte has special meaning
|
|
|
|
/// RLE8 flag value that indicates following byte has special meaning.
|
|
|
|
/// </summary>
|
|
|
|
private const int RleCommand = 0x00; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// RLE8 flag value marking end of a scan line
|
|
|
|
/// RLE8 flag value marking end of a scan line.
|
|
|
|
/// </summary>
|
|
|
|
private const int RleEndOfLine = 0x00; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// RLE8 flag value marking end of bitmap data
|
|
|
|
/// RLE8 flag value marking end of bitmap data.
|
|
|
|
/// </summary>
|
|
|
|
private const int RleEndOfBitmap = 0x01; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// RLE8 flag value marking the start of [x,y] offset instruction
|
|
|
|
/// RLE8 flag value marking the start of [x,y] offset instruction.
|
|
|
|
/// </summary>
|
|
|
|
private const int RleDelta = 0x02; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The stream to decode from.
|
|
|
|
/// </summary>
|
|
|
|
private Stream currentStream; |
|
|
|
private Stream stream; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The file header containing general information.
|
|
|
|
@ -163,18 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private static int Invert(int y, int height, bool inverted) |
|
|
|
{ |
|
|
|
int row; |
|
|
|
|
|
|
|
if (!inverted) |
|
|
|
{ |
|
|
|
row = height - y - 1; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
row = y; |
|
|
|
} |
|
|
|
|
|
|
|
return row; |
|
|
|
return (!inverted) ? height - y - 1 : y; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -261,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
|
|
|
|
while (count < buffer.Length) |
|
|
|
{ |
|
|
|
if (this.currentStream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
if (this.stream.Read(cmd, 0, cmd.Length) != 2) |
|
|
|
{ |
|
|
|
throw new Exception("Failed to read 2 bytes from stream"); |
|
|
|
} |
|
|
|
@ -283,27 +272,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
break; |
|
|
|
|
|
|
|
case RleDelta: |
|
|
|
int dx = this.currentStream.ReadByte(); |
|
|
|
int dy = this.currentStream.ReadByte(); |
|
|
|
int dx = this.stream.ReadByte(); |
|
|
|
int dy = this.stream.ReadByte(); |
|
|
|
count += (w * dy) + dx; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
// If the second byte > 2, signals 'absolute mode'
|
|
|
|
// If the second byte > 2, we are in 'absolute mode'
|
|
|
|
// Take this number of bytes from the stream as uncompressed data
|
|
|
|
int length = cmd[1]; |
|
|
|
int copyLength = length; |
|
|
|
|
|
|
|
byte[] run = new byte[length]; |
|
|
|
|
|
|
|
this.stream.Read(run, 0, run.Length); |
|
|
|
|
|
|
|
run.AsSpan().CopyTo(buffer.Slice(count)); |
|
|
|
|
|
|
|
count += run.Length; |
|
|
|
|
|
|
|
// Absolute mode data is aligned to two-byte word-boundary
|
|
|
|
length += length & 1; |
|
|
|
int padding = length & 1; |
|
|
|
|
|
|
|
byte[] run = new byte[length]; |
|
|
|
this.currentStream.Read(run, 0, run.Length); |
|
|
|
for (int i = 0; i < copyLength; i++) |
|
|
|
{ |
|
|
|
buffer[count++] = run[i]; |
|
|
|
} |
|
|
|
this.stream.Skip(padding); |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
@ -348,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
|
|
|
|
using (IManagedByteBuffer row = this.memoryManager.AllocateCleanManagedByteBuffer(arrayWidth + padding)) |
|
|
|
{ |
|
|
|
var color = default(TPixel); |
|
|
|
TPixel color = default; |
|
|
|
var rgba = new Rgba32(0, 0, 0, 255); |
|
|
|
|
|
|
|
Span<byte> rowSpan = row.Span; |
|
|
|
@ -356,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
this.currentStream.Read(row.Array, 0, row.Length()); |
|
|
|
this.stream.Read(row.Array, 0, row.Length()); |
|
|
|
int offset = 0; |
|
|
|
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
|
|
|
|
@ -402,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.currentStream.Read(buffer.Array, 0, stride); |
|
|
|
this.stream.Read(buffer.Array, 0, stride); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
|
|
|
|
@ -440,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
this.stream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
PixelOperations<TPixel>.Instance.PackFromBgr24Bytes(row.Span, pixelSpan, width); |
|
|
|
@ -465,7 +456,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
this.stream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
PixelOperations<TPixel>.Instance.PackFromBgra32Bytes(row.Span, pixelSpan, width); |
|
|
|
@ -478,98 +469,44 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
/// </summary>
|
|
|
|
private void ReadInfoHeader() |
|
|
|
{ |
|
|
|
byte[] data = new byte[BmpInfoHeader.MaxHeaderSize]; |
|
|
|
byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; |
|
|
|
|
|
|
|
// read header size
|
|
|
|
this.currentStream.Read(data, 0, BmpInfoHeader.HeaderSizeSize); |
|
|
|
int headerSize = BitConverter.ToInt32(data, 0); |
|
|
|
if (headerSize < BmpInfoHeader.BitmapCoreHeaderSize) |
|
|
|
this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); |
|
|
|
|
|
|
|
int headerSize = BitConverter.ToInt32(buffer, 0); |
|
|
|
if (headerSize < BmpInfoHeader.CoreSize) |
|
|
|
{ |
|
|
|
throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported."); |
|
|
|
throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); |
|
|
|
} |
|
|
|
|
|
|
|
int skipAmmount = 0; |
|
|
|
int skipAmount = 0; |
|
|
|
if (headerSize > BmpInfoHeader.MaxHeaderSize) |
|
|
|
{ |
|
|
|
skipAmmount = headerSize - BmpInfoHeader.MaxHeaderSize; |
|
|
|
skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; |
|
|
|
headerSize = BmpInfoHeader.MaxHeaderSize; |
|
|
|
} |
|
|
|
|
|
|
|
// read the rest of the header
|
|
|
|
this.currentStream.Read(data, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); |
|
|
|
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); |
|
|
|
|
|
|
|
switch (headerSize) |
|
|
|
if (headerSize == BmpInfoHeader.CoreSize) |
|
|
|
{ |
|
|
|
case BmpInfoHeader.BitmapCoreHeaderSize: |
|
|
|
this.infoHeader = this.ParseBitmapCoreHeader(data); |
|
|
|
break; |
|
|
|
case BmpInfoHeader.BitmapInfoHeaderSize: |
|
|
|
this.infoHeader = this.ParseBitmapInfoHeader(data); |
|
|
|
break; |
|
|
|
default: |
|
|
|
if (headerSize > BmpInfoHeader.BitmapInfoHeaderSize) |
|
|
|
{ |
|
|
|
this.infoHeader = this.ParseBitmapInfoHeader(data); |
|
|
|
break; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported."); |
|
|
|
} |
|
|
|
// 12 bytes
|
|
|
|
this.infoHeader = BmpInfoHeader.ParseCore(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
// skip the remaining header because we can't read those parts
|
|
|
|
this.currentStream.Skip(skipAmmount); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Parses the <see cref="BmpInfoHeader"/> from the stream, assuming it uses the BITMAPCOREHEADER format.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="data">Header bytes read from the stream</param>
|
|
|
|
/// <returns>Parsed header</returns>
|
|
|
|
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183372.aspx"/>
|
|
|
|
private BmpInfoHeader ParseBitmapCoreHeader(byte[] data) |
|
|
|
{ |
|
|
|
return new BmpInfoHeader |
|
|
|
else if (headerSize >= BmpInfoHeader.Size) |
|
|
|
{ |
|
|
|
HeaderSize = BitConverter.ToInt32(data, 0), |
|
|
|
Width = BitConverter.ToUInt16(data, 4), |
|
|
|
Height = BitConverter.ToUInt16(data, 6), |
|
|
|
Planes = BitConverter.ToInt16(data, 8), |
|
|
|
BitsPerPixel = BitConverter.ToInt16(data, 10), |
|
|
|
|
|
|
|
// the rest is not present in the core header
|
|
|
|
ImageSize = 0, |
|
|
|
XPelsPerMeter = 0, |
|
|
|
YPelsPerMeter = 0, |
|
|
|
ClrUsed = 0, |
|
|
|
ClrImportant = 0, |
|
|
|
Compression = BmpCompression.RGB |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Parses the <see cref="BmpInfoHeader"/> from the stream, assuming it uses the BITMAPINFOHEADER format.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="data">Header bytes read from the stream</param>
|
|
|
|
/// <returns>Parsed header</returns>
|
|
|
|
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx"/>
|
|
|
|
private BmpInfoHeader ParseBitmapInfoHeader(byte[] data) |
|
|
|
{ |
|
|
|
return new BmpInfoHeader |
|
|
|
// >= 40 bytes
|
|
|
|
this.infoHeader = BmpInfoHeader.Parse(buffer.AsSpan(0, 40)); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
HeaderSize = BitConverter.ToInt32(data, 0), |
|
|
|
Width = BitConverter.ToInt32(data, 4), |
|
|
|
Height = BitConverter.ToInt32(data, 8), |
|
|
|
Planes = BitConverter.ToInt16(data, 12), |
|
|
|
BitsPerPixel = BitConverter.ToInt16(data, 14), |
|
|
|
ImageSize = BitConverter.ToInt32(data, 20), |
|
|
|
XPelsPerMeter = BitConverter.ToInt32(data, 24), |
|
|
|
YPelsPerMeter = BitConverter.ToInt32(data, 28), |
|
|
|
ClrUsed = BitConverter.ToInt32(data, 32), |
|
|
|
ClrImportant = BitConverter.ToInt32(data, 36), |
|
|
|
Compression = (BmpCompression)BitConverter.ToInt32(data, 16) |
|
|
|
}; |
|
|
|
throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); |
|
|
|
} |
|
|
|
|
|
|
|
// skip the remaining header because we can't read those parts
|
|
|
|
this.stream.Skip(skipAmount); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -577,15 +514,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
/// </summary>
|
|
|
|
private void ReadFileHeader() |
|
|
|
{ |
|
|
|
byte[] data = new byte[BmpFileHeader.Size]; |
|
|
|
byte[] buffer = new byte[BmpFileHeader.Size]; |
|
|
|
|
|
|
|
this.currentStream.Read(data, 0, BmpFileHeader.Size); |
|
|
|
this.stream.Read(buffer, 0, BmpFileHeader.Size); |
|
|
|
|
|
|
|
this.fileHeader = new BmpFileHeader( |
|
|
|
type: BitConverter.ToInt16(data, 0), |
|
|
|
fileSize: BitConverter.ToInt32(data, 2), |
|
|
|
reserved: BitConverter.ToInt32(data, 6), |
|
|
|
offset: BitConverter.ToInt32(data, 10)); |
|
|
|
this.fileHeader = BmpFileHeader.Parse(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -593,66 +526,59 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
|
/// </summary>
|
|
|
|
private void ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette) |
|
|
|
{ |
|
|
|
this.currentStream = stream; |
|
|
|
|
|
|
|
try |
|
|
|
this.stream = stream; |
|
|
|
|
|
|
|
this.ReadFileHeader(); |
|
|
|
this.ReadInfoHeader(); |
|
|
|
|
|
|
|
// 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
|
|
|
|
// is the upper-left corner and not the lower-left. The inverted flag
|
|
|
|
// indicates a lower-left origin.Our code will be outputting an
|
|
|
|
// upper-left origin pixel array.
|
|
|
|
inverted = false; |
|
|
|
if (this.infoHeader.Height < 0) |
|
|
|
{ |
|
|
|
this.ReadFileHeader(); |
|
|
|
this.ReadInfoHeader(); |
|
|
|
|
|
|
|
// 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
|
|
|
|
// is the upper-left corner and not the lower-left.The inverted flag
|
|
|
|
// indicates a lower-left origin.Our code will be outputting an
|
|
|
|
// upper-left origin pixel array.
|
|
|
|
inverted = false; |
|
|
|
if (this.infoHeader.Height < 0) |
|
|
|
{ |
|
|
|
inverted = true; |
|
|
|
this.infoHeader.Height = -this.infoHeader.Height; |
|
|
|
} |
|
|
|
inverted = true; |
|
|
|
this.infoHeader.Height = -this.infoHeader.Height; |
|
|
|
} |
|
|
|
|
|
|
|
int colorMapSize = -1; |
|
|
|
int colorMapSize = -1; |
|
|
|
|
|
|
|
if (this.infoHeader.ClrUsed == 0) |
|
|
|
{ |
|
|
|
if (this.infoHeader.BitsPerPixel == 1 || |
|
|
|
this.infoHeader.BitsPerPixel == 4 || |
|
|
|
this.infoHeader.BitsPerPixel == 8) |
|
|
|
{ |
|
|
|
colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
if (this.infoHeader.ClrUsed == 0) |
|
|
|
{ |
|
|
|
if (this.infoHeader.BitsPerPixel == 1 || |
|
|
|
this.infoHeader.BitsPerPixel == 4 || |
|
|
|
this.infoHeader.BitsPerPixel == 8) |
|
|
|
{ |
|
|
|
colorMapSize = this.infoHeader.ClrUsed * 4; |
|
|
|
colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
colorMapSize = this.infoHeader.ClrUsed * 4; |
|
|
|
} |
|
|
|
|
|
|
|
palette = null; |
|
|
|
palette = null; |
|
|
|
|
|
|
|
if (colorMapSize > 0) |
|
|
|
if (colorMapSize > 0) |
|
|
|
{ |
|
|
|
// 256 * 4
|
|
|
|
if (colorMapSize > 1024) |
|
|
|
{ |
|
|
|
// 256 * 4
|
|
|
|
if (colorMapSize > 1024) |
|
|
|
{ |
|
|
|
throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); |
|
|
|
} |
|
|
|
|
|
|
|
palette = new byte[colorMapSize]; |
|
|
|
|
|
|
|
this.currentStream.Read(palette, 0, colorMapSize); |
|
|
|
throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.infoHeader.Width > int.MaxValue || this.infoHeader.Height > int.MaxValue) |
|
|
|
{ |
|
|
|
throw new ArgumentOutOfRangeException( |
|
|
|
$"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " |
|
|
|
+ $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'"); |
|
|
|
} |
|
|
|
palette = new byte[colorMapSize]; |
|
|
|
|
|
|
|
this.stream.Read(palette, 0, colorMapSize); |
|
|
|
} |
|
|
|
catch (IndexOutOfRangeException e) |
|
|
|
|
|
|
|
if (this.infoHeader.Width > int.MaxValue || this.infoHeader.Height > int.MaxValue) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Bitmap does not have a valid format.", e); |
|
|
|
throw new ArgumentOutOfRangeException( |
|
|
|
$"The input bmp '{this.infoHeader.Width}x{this.infoHeader.Height}' is " |
|
|
|
+ $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|