// // 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) }; } } }