using System; using System.Collections.Generic; using System.Text; using System.IO; namespace Orvid.Graphics.ImageFormats { public class BmpImage : ImageFormat { public override void Save(Image i, Stream dest) { BmpInternals.BmpEncoder b = new BmpInternals.BmpEncoder(); b.Encode(i, dest); } public override Image Load(Stream s) { BmpInternals.BmpDecoder b = new BmpInternals.BmpDecoder(); return b.Decode(s); } // Please note, everything below this // point was originally from the ImageTools // Library, available here: // http://imagetools.codeplex.com/ // // // The source has been modified for use in this library. // // This disclaimer was last // modified on August 9, 2011. #region Internals private static class BmpInternals { #region BmpDecoder public class BmpDecoder { /// /// 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; private Stream _stream; private BmpFileHeader _fileHeader; private BmpInfoHeader _infoHeader; /// /// Decodes the image from the specified _stream and sets /// the data to image. /// /// 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 (Nothing in Visual Basic). /// - or - /// is null (Nothing in Visual Basic). /// public Image Decode(Stream stream) { _stream = stream; try { ReadFileHeader(); ReadInfoHeader(); int colorMapSize = -1; if (_infoHeader.ClrUsed == 0) { if (_infoHeader.BitsPerPixel == 1 || _infoHeader.BitsPerPixel == 4 || _infoHeader.BitsPerPixel == 8) { colorMapSize = (int)Math.Pow(2, _infoHeader.BitsPerPixel) * 4; } } else { colorMapSize = _infoHeader.ClrUsed * 4; } byte[] palette = null; if (colorMapSize > 0) { palette = new byte[colorMapSize]; _stream.Read(palette, 0, colorMapSize); } byte[] imageData = new byte[_infoHeader.Width * _infoHeader.Height * 4]; switch (_infoHeader.Compression) { case BmpCompression.RGB: if (_infoHeader.HeaderSize != 40) { throw new Exception("Header Size value '" + _infoHeader.HeaderSize.ToString() + "' is not valid."); } if (_infoHeader.BitsPerPixel == 32) { ReadRgb32(imageData, _infoHeader.Width, _infoHeader.Height); } else if (_infoHeader.BitsPerPixel == 24) { ReadRgb24(imageData, _infoHeader.Width, _infoHeader.Height); } else if (_infoHeader.BitsPerPixel == 16) { ReadRgb16(imageData, _infoHeader.Width, _infoHeader.Height); } else if (_infoHeader.BitsPerPixel <= 8) { ReadRgbPalette(imageData, palette, _infoHeader.ImageSize, _infoHeader.Width, _infoHeader.Height, _infoHeader.BitsPerPixel); } break; default: throw new NotSupportedException("Does not support this kind of bitmap files."); } Image i = new Image(_infoHeader.Width, _infoHeader.Height); int indx = 0; byte r, g, b, a; for (uint y = 0; y < i.Height; y++) { for (uint x = 0; x < i.Width; x++) { r = imageData[indx]; indx++; g = imageData[indx]; indx++; b = imageData[indx]; indx++; a = imageData[indx]; indx++; i.SetPixel(x, y, new Pixel(r, g, b, a)); } } imageData = null; System.GC.Collect(); return i; } catch (IndexOutOfRangeException e) { throw new Exception("Bitmap does not have a valid format.", e); } } private void ReadRgbPalette(byte[] imageData, byte[] colors, int size, int width, int height, int bits) { // Pixels per byte (bits per pixel) int ppb = 8 / bits; int arrayWidth = (width + ppb - 1) / ppb; // Bit mask int mask = (0xFF >> (8 - bits)); byte[] data = new byte[size]; _stream.Read(data, 0, size); // Rows are aligned on 4 byte boundaries int alignment = arrayWidth % 4; if (alignment != 0) { alignment = 4 - alignment; } int offset, row, rowOffset, colOffset, arrayOffset; for (int y = 0; y < height; y++) { rowOffset = y * (arrayWidth + alignment); for (int x = 0; x < arrayWidth; x++) { offset = rowOffset + x; // Revert the y value, because bitmaps are saved from down to top row = Invert(y, height); colOffset = x * ppb; for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) { int colorIndex = ((data[offset]) >> (8 - bits - (shift * bits))) & mask; arrayOffset = (row * width + (colOffset + shift)) * 4; imageData[arrayOffset + 0] = colors[colorIndex * 4 + 2]; imageData[arrayOffset + 1] = colors[colorIndex * 4 + 1]; imageData[arrayOffset + 2] = colors[colorIndex * 4 + 0]; imageData[arrayOffset + 3] = (byte)255; } } } } private void ReadRgb16(byte[] imageData, int width, int height) { byte r, g, b; int scaleR = 256 / 32; int scaleG = 256 / 64; int alignment = 0; byte[] data = GetImageArray(width, height, 2, ref alignment); int offset, row, rowOffset, arrayOffset; for (int y = 0; y < height; y++) { rowOffset = y * (width * 2 + alignment); // Revert the y value, because bitmaps are saved from down to top row = Invert(y, height); for (int x = 0; x < width; x++) { offset = rowOffset + x * 2; short temp = BitConverter.ToInt16(data, offset); r = (byte)(((temp & Rgb16RMask) >> 11) * scaleR); g = (byte)(((temp & Rgb16GMask) >> 5) * scaleG); b = (byte)(((temp & Rgb16BMask)) * scaleR); arrayOffset = (row * width + x) * 4; imageData[arrayOffset + 0] = r; imageData[arrayOffset + 1] = g; imageData[arrayOffset + 2] = b; imageData[arrayOffset + 3] = (byte)255; } } } private void ReadRgb24(byte[] imageData, int width, int height) { int alignment = 0; byte[] data = GetImageArray(width, height, 3, ref alignment); int offset, row, rowOffset, arrayOffset; for (int y = 0; y < height; y++) { rowOffset = y * (width * 3 + alignment); // Revert the y value, because bitmaps are saved from down to top row = Invert(y, height); for (int x = 0; x < width; x++) { offset = rowOffset + x * 3; arrayOffset = (row * width + x) * 4; imageData[arrayOffset + 0] = data[offset + 2]; imageData[arrayOffset + 1] = data[offset + 1]; imageData[arrayOffset + 2] = data[offset + 0]; imageData[arrayOffset + 3] = (byte)255; } } } private void ReadRgb32(byte[] imageData, int width, int height) { int alignment = 0; byte[] data = GetImageArray(width, height, 4, ref alignment); int offset, row, rowOffset, arrayOffset; for (int y = 0; y < height; y++) { rowOffset = y * (width * 4 + alignment); // Revert the y value, because bitmaps are saved from down to top row = Invert(y, height); for (int x = 0; x < width; x++) { offset = rowOffset + x * 4; arrayOffset = (row * width + x) * 4; imageData[arrayOffset + 0] = data[offset + 2]; imageData[arrayOffset + 1] = data[offset + 1]; imageData[arrayOffset + 2] = data[offset + 0]; imageData[arrayOffset + 3] = (byte)255; } } } private static int Invert(int y, int height) { int row = 0; if (height > 0) { row = (height - y - 1); } else { row = y; } return row; } private byte[] GetImageArray(int width, int height, int bytes, ref int alignment) { int dataWidth = width; alignment = (width * bytes) % 4; if (alignment != 0) { alignment = 4 - alignment; } int size = (dataWidth * bytes + alignment) * height; byte[] data = new byte[size]; _stream.Read(data, 0, size); return data; } private void ReadInfoHeader() { byte[] data = new byte[BmpInfoHeader.Size]; _stream.Read(data, 0, BmpInfoHeader.Size); _infoHeader = new BmpInfoHeader(); _infoHeader.HeaderSize = BitConverter.ToInt32(data, 0); _infoHeader.Width = BitConverter.ToInt32(data, 4); _infoHeader.Height = BitConverter.ToInt32(data, 8); _infoHeader.Planes = BitConverter.ToInt16(data, 12); _infoHeader.BitsPerPixel = BitConverter.ToInt16(data, 14); _infoHeader.ImageSize = BitConverter.ToInt32(data, 20); _infoHeader.XPelsPerMeter = BitConverter.ToInt32(data, 24); _infoHeader.YPelsPerMeter = BitConverter.ToInt32(data, 28); _infoHeader.ClrUsed = BitConverter.ToInt32(data, 32); _infoHeader.ClrImportant = BitConverter.ToInt32(data, 36); _infoHeader.Compression = (BmpCompression)BitConverter.ToInt32(data, 16); } private void ReadFileHeader() { byte[] data = new byte[BmpFileHeader.Size]; _stream.Read(data, 0, BmpFileHeader.Size); _fileHeader = new BmpFileHeader(); _fileHeader.Type = BitConverter.ToInt16(data, 0); _fileHeader.FileSize = BitConverter.ToInt32(data, 2); _fileHeader.Reserved = BitConverter.ToInt32(data, 6); _fileHeader.Offset = BitConverter.ToInt32(data, 10); } } #endregion #region BmpEncoder public class BmpEncoder { public void Encode(Image image, Stream stream) { int rowWidth = image.Width; int amount = (image.Width * 3) % 4; if (amount != 0) { rowWidth += 4 - amount; } BinaryWriter writer = new BinaryWriter(stream); BmpFileHeader fileHeader = new BmpFileHeader(); fileHeader.Type = 19778; fileHeader.Offset = 54; fileHeader.FileSize = 54 + image.Height * rowWidth * 3; Write(writer, fileHeader); BmpInfoHeader infoHeader = new BmpInfoHeader(); infoHeader.HeaderSize = 40; infoHeader.Height = image.Height; infoHeader.Width = image.Width; infoHeader.BitsPerPixel = 24; infoHeader.Planes = 1; infoHeader.Compression = BmpCompression.RGB; infoHeader.ImageSize = image.Height * rowWidth * 3; infoHeader.ClrUsed = 0; infoHeader.ClrImportant = 0; Write(writer, infoHeader); WriteImage(writer, image); writer.Flush(); } private static void WriteImage(BinaryWriter writer, Image image) { int amount = (image.Width * 3) % 4, offset = 0; if (amount != 0) { amount = 4 - amount; } byte[] data = ConvertPixelArrayToByteArray(image.Data); for (int y = image.Height - 1; y >= 0; y--) { for (int x = 0; x < image.Width; x++) { offset = (y * image.Width + x) * 4; writer.Write(data[offset + 2]); writer.Write(data[offset + 1]); writer.Write(data[offset + 0]); } for (int i = 0; i < amount; i++) { writer.Write((byte)0); } } } private static byte[] ConvertPixelArrayToByteArray(Pixel[] a) { byte[] b = new byte[a.Length * 4]; int indx = 0; Pixel p; for (uint i = 0; i < a.Length; i++) { p = a[i]; b[indx] = p.R; indx++; b[indx] = p.G; indx++; b[indx] = p.B; indx++; b[indx] = p.A; indx++; } return b; } private static void Write(BinaryWriter writer, BmpFileHeader fileHeader) { writer.Write(fileHeader.Type); writer.Write(fileHeader.FileSize); writer.Write(fileHeader.Reserved); writer.Write(fileHeader.Offset); } private static void Write(BinaryWriter writer, BmpInfoHeader infoHeader) { writer.Write(infoHeader.HeaderSize); writer.Write(infoHeader.Width); writer.Write(infoHeader.Height); writer.Write(infoHeader.Planes); writer.Write(infoHeader.BitsPerPixel); writer.Write((int)infoHeader.Compression); writer.Write(infoHeader.ImageSize); writer.Write(infoHeader.XPelsPerMeter); writer.Write(infoHeader.YPelsPerMeter); writer.Write(infoHeader.ClrUsed); writer.Write(infoHeader.ClrImportant); } } #endregion #region BitmapCompression private enum BmpCompression : int { /// /// Each image row has a multiple of four elements. If the /// row has less elements, zeros will be added at the right side. /// The format depends on the number of bits, stored in the info header. /// If the number of bits are one, four or eight each pixel data is /// a index to the palette. If the number of bits are sixteen, /// twenty-four or thirtee-two each pixel contains a color. /// RGB = 0, /// /// Two bytes are one data record. If the first byte is not zero, the /// next two half bytes will be repeated as much as the value of the first byte. /// If the first byte is zero, the record has different meanings, depending /// on the second byte. If the second byte is zero, it is the end of the row, /// if it is one, it is the end of the image. /// Not supported at the moment. /// RLE8 = 1, /// /// Two bytes are one data record. If the first byte is not zero, the /// next byte will be repeated as much as the value of the first byte. /// If the first byte is zero, the record has different meanings, depending /// on the second byte. If the second byte is zero, it is the end of the row, /// if it is one, it is the end of the image. /// Not supported at the moment. /// RLE4 = 2, /// /// Each image row has a multiple of four elements. If the /// row has less elements, zeros will be added at the right side. /// Not supported at the moment. /// BitFields = 3, /// /// The bitmap contains a JPG image. /// Not supported at the moment. /// JPEG = 4, /// /// The bitmap contains a PNG image. /// Not supported at the moment. /// PNG = 5 } #endregion #region BmpFileHeader private class BmpFileHeader { /// /// Defines of the data structure in the bitmap file. /// public const int Size = 14; /// /// The magic number used to identify the bitmap file: 0x42 0x4D /// (Hex code points for B and M) /// public short Type; /// /// The size of the bitmap file in bytes. /// public int FileSize; /// /// Reserved; actual value depends on the application /// that creates the image. /// public int Reserved; /// /// The offset, i.e. starting address, of the byte where /// the bitmap data can be found. /// public int Offset; } #endregion #region BmpInfoHeader private class BmpInfoHeader { /// /// Defines of the data structure in the bitmap file. /// public const int Size = 40; /// /// The size of this header (40 bytes) /// public int HeaderSize; /// /// The bitmap width in pixels (signed integer). /// public int Width; /// /// The bitmap height in pixels (signed integer). /// public int Height; /// /// The number of color planes being used. Must be set to 1. /// public short Planes; /// /// The number of bits per pixel, which is the color depth of the image. /// Typical values are 1, 4, 8, 16, 24 and 32. /// public short BitsPerPixel; /// /// The compression method being used. /// See the next table for a list of possible values. /// public BmpCompression Compression; /// /// The image size. This is the size of the raw bitmap data (see below), /// and should not be confused with the file size. /// public int ImageSize; /// /// The horizontal resolution of the image. /// (pixel per meter, signed integer) /// public int XPelsPerMeter; /// /// The vertical resolution of the image. /// (pixel per meter, signed integer) /// public int YPelsPerMeter; /// /// The number of colors in the color palette, /// or 0 to default to 2^n. /// public int ClrUsed; /// /// The number of important colors used, /// or 0 when every color is important; generally ignored. /// public int ClrImportant; } #endregion } #endregion } }