//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageSharp.Formats
{
using System;
using System.IO;
///
/// Performs the bmp decoding operation.
///
internal sealed class BmpDecoderCore
{
///
/// The mask for the red part of the color for 16 bit rgb bitmaps.
///
private const int Rgb16RMask = 0x00007C00;
///
/// The mask for the green part of the color for 16 bit rgb bitmaps.
///
private const int Rgb16GMask = 0x000003E0;
///
/// The mask for the blue part of the color for 16 bit rgb bitmaps.
///
private const int Rgb16BMask = 0x0000001F;
///
/// The stream to decode from.
///
private Stream currentStream;
///
/// The file header containing general information.
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
///
private BmpFileHeader fileHeader;
///
/// The info header containing detailed information about the bitmap.
///
private BmpInfoHeader infoHeader;
///
/// Decodes the image from the specified this._stream and sets
/// the data to image.
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The image, where the data should be set to.
/// Cannot be null (Nothing in Visual Basic).
/// The stream, where the image should be
/// decoded from. Cannot be null (Nothing in Visual Basic).
///
/// is null.
/// - or -
/// is null.
///
public void Decode(Image image, Stream stream)
where TColor : struct, IPackedPixel
where TPacked : struct
{
this.currentStream = stream;
try
{
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.
bool inverted = false;
if (this.infoHeader.Height < 0)
{
inverted = true;
this.infoHeader.Height = -this.infoHeader.Height;
}
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
{
colorMapSize = this.infoHeader.ClrUsed * 4;
}
byte[] palette = null;
if (colorMapSize > 0)
{
// 256 * 4
if (colorMapSize > 1024)
{
throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
}
palette = new byte[colorMapSize];
this.currentStream.Read(palette, 0, colorMapSize);
}
if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight)
{
throw new ArgumentOutOfRangeException(
$"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is "
+ $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'");
}
image.InitPixels(this.infoHeader.Width, this.infoHeader.Height);
using (PixelAccessor pixels = image.Lock())
{
switch (this.infoHeader.Compression)
{
case BmpCompression.RGB:
if (this.infoHeader.HeaderSize != 40)
{
throw new ImageFormatException($"Header Size value '{this.infoHeader.HeaderSize}' is not valid.");
}
if (this.infoHeader.BitsPerPixel == 32)
{
this.ReadRgb32(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel == 24)
{
this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel <= 8)
{
this.ReadRgbPalette(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted);
}
break;
default:
throw new NotSupportedException("Does not support this kind of bitmap files.");
}
}
}
catch (IndexOutOfRangeException e)
{
throw new ImageFormatException("Bitmap does not have a valid format.", e);
}
}
///
/// Returns the y- value based on the given height.
///
/// The y- value representing the current row.
/// The height of the bitmap.
/// Whether the bitmap is inverted.
/// The representing the inverted value.
private static int Invert(int y, int height, bool inverted)
{
int row;
if (!inverted)
{
row = height - y - 1;
}
else
{
row = y;
}
return row;
}
///
/// Calculates the amount of bytes to pad a row.
///
/// The image width.
/// The pixel component count.
///
/// The .
///
private static int CalculatePadding(int width, int componentCount)
{
int padding = (width * componentCount) % 4;
if (padding != 0)
{
padding = 4 - padding;
}
return padding;
}
///
/// Reads the color palette from the stream.
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The to assign the palette to.
/// The containing the colors.
/// The width of the bitmap.
/// The height of the bitmap.
/// The number of bits per pixel.
/// Whether the bitmap is inverted.
private void ReadRgbPalette(PixelAccessor pixels, byte[] colors, int width, int height, int bits, bool inverted)
where TColor : struct, IPackedPixel
where TPacked : struct
{
// Pixels per byte (bits per pixel)
int ppb = 8 / bits;
int arrayWidth = (width + ppb - 1) / ppb;
// Bit mask
int mask = 0xFF >> (8 - bits);
// Rows are aligned on 4 byte boundaries
int padding = arrayWidth % 4;
if (padding != 0)
{
padding = 4 - padding;
}
byte[] row = new byte[arrayWidth + padding];
TColor color = default(TColor);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
this.currentStream.Read(row, 0, row.Length);
int offset = 0;
for (int x = 0; x < arrayWidth; x++)
{
int colOffset = x * ppb;
for (int shift = 0; shift < ppb && (x + shift) < width; shift++)
{
int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int newX = colOffset + shift;
// Stored in b-> g-> r order.
color.PackFromBytes(colors[colorIndex + 2], colors[colorIndex + 1], colors[colorIndex], 255);
pixels[newX, newY] = color;
}
offset++;
}
}
}
///
/// Reads the 16 bit color palette from the stream
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The to assign the palette to.
/// The width of the bitmap.
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRgb16(PixelAccessor pixels, int width, int height, bool inverted)
where TColor : struct, IPackedPixel
where TPacked : struct
{
// We divide here as we will store the colors in our floating point format.
const int ScaleR = 8; // 256/32
const int ScaleG = 4; // 256/64
const int ComponentCount = 2;
TColor color = default(TColor);
using (PixelRow row = new PixelRow(width, ComponentOrder.XYZ))
{
for (int y = 0; y < height; y++)
{
row.Read(this.currentStream);
int newY = Invert(y, height, inverted);
int offset = 0;
for (int x = 0; x < width; x++)
{
short temp = BitConverter.ToInt16(row.Bytes, offset);
byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR);
byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG);
byte b = (byte)((temp & Rgb16BMask) * ScaleR);
color.PackFromBytes(r, g, b, 255);
pixels[x, newY] = color;
offset += ComponentCount;
}
}
}
}
///
/// Reads the 24 bit color palette from the stream
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The to assign the palette to.
/// The width of the bitmap.
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRgb24(PixelAccessor pixels, int width, int height, bool inverted)
where TColor : struct, IPackedPixel
where TPacked : struct
{
int padding = CalculatePadding(width, 3);
using (PixelRow row = new PixelRow(width, ComponentOrder.ZYX, padding))
{
for (int y = 0; y < height; y++)
{
row.Read(this.currentStream);
int newY = Invert(y, height, inverted);
pixels.CopyFrom(row, newY);
}
}
}
///
/// Reads the 32 bit color palette from the stream
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The to assign the palette to.
/// The width of the bitmap.
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRgb32(PixelAccessor pixels, int width, int height, bool inverted)
where TColor : struct, IPackedPixel
where TPacked : struct
{
int padding = CalculatePadding(width, 4);
using (PixelRow row = new PixelRow(width, ComponentOrder.ZYXW, padding))
{
for (int y = 0; y < height; y++)
{
row.Read(this.currentStream);
int newY = Invert(y, height, inverted);
pixels.CopyFrom(row, newY);
}
}
}
///
/// Reads the from the stream.
///
private void ReadInfoHeader()
{
byte[] data = new byte[BmpInfoHeader.Size];
this.currentStream.Read(data, 0, BmpInfoHeader.Size);
this.infoHeader = new BmpInfoHeader
{
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)
};
}
///
/// Reads the from the stream.
///
private void ReadFileHeader()
{
byte[] data = new byte[BmpFileHeader.Size];
this.currentStream.Read(data, 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)
};
}
}
}