//#define COSMOSDEBUG using System; using System.IO; using System.Security; namespace Cosmos.System.Graphics { /// /// Bitmap class, used to represent image of the type of Bitmap. See also: . /// public class Bitmap : Image { /// /// Create new instance of class. /// /// Image width (greater then 0). /// Image height (greater then 0). /// Color depth. public Bitmap(uint Width, uint Height, ColorDepth colorDepth) : base(Width, Height, colorDepth) { rawData = new int[Width * Height]; } /// /// Create a bitmap from a byte array representing the pixels. /// /// Width of the bitmap. /// Height of the bitmap. /// Byte array which includes the values for each pixel. /// Format of pixel data. /// Thrwon if color depth is not 32. /// Thrown if bitmap size is bigger than Int32.MaxValue. /// Thrown on fatal error (contact support). /// Thrown on memory error. /// Thrown on fatal error (contact support). public Bitmap(uint Width, uint Height, byte[] pixelData, ColorDepth colorDepth) : base(Width, Height, colorDepth) { rawData = new int[Width * Height]; if (colorDepth != ColorDepth.ColorDepth32 && colorDepth != ColorDepth.ColorDepth24) { Global.mDebugger.Send("Only color depths 24 and 32 are supported!"); throw new NotImplementedException("Only color depths 24 and 32 are supported!"); } for (int i = 0; i < rawData.Length; i++) { if (colorDepth == ColorDepth.ColorDepth32) { rawData[i] = BitConverter.ToInt32(new byte[] { pixelData[(i * 4)], pixelData[(i * 4) + 1], pixelData[(i * 4) + 2], pixelData[(i * 4) + 3] }, 0); } else { rawData[i] = BitConverter.ToInt32(new byte[] { 0, pixelData[(i * 3)], pixelData[(i * 3) + 1], pixelData[(i * 3) + 2] }, 0); } } } /// /// Create new instance of the class, with a specified path to a BMP file. /// /// Path to file. /// /// /// Thrown if path is invalid. /// Memory error. /// /// /// /// /// Thrown if path is null. /// Memory error. /// /// /// Thrown on fatal error (contact support). /// Thrown on IO error. /// /// /// Thrown on fatal error (contact support). /// The path refers to non-file. /// /// /// Thrown if the stream is closed. /// /// /// Thrown if header is not from a BMP. /// Info header size has the wrong value. /// Number of planes is not 1. Can not read file. /// Total Image Size is smaller than pure image size. /// /// /// Thrown if pixelsize is other then 32 / 24 or the file compressed. /// Thrown if the caller does not have permissions to read / write the file. /// Thrown if the file cannot be found. /// Thrown if the specified path is invalid. /// Thrown if the specified path is exceed the system-defined max length. public Bitmap(string path) : this(path, ColorOrder.BGR) { } /// /// Create new instance of the class, with a specified path to a BMP file. /// /// Path to file. /// Order of colors in each pixel. /// /// /// Thrown if path is invalid. /// Memory error. /// /// /// /// /// Thrown if path is null. /// Memory error. /// /// /// Thrown on fatal error (contact support). /// Thrown on IO error. /// /// /// Thrown on fatal error (contact support). /// The path refers to non-file. /// /// /// Thrown if the stream is closed. /// /// /// Thrown if header is not from a BMP. /// Info header size has the wrong value. /// Number of planes is not 1. Can not read file. /// Total Image Size is smaller than pure image size. /// /// /// Thrown if pixelsize is other then 32 / 24 or the file compressed. /// Thrown if the caller does not have permissions to read / write the file. /// Thrown if the file cannot be found. /// Thrown if the specified path is invalid. /// Thrown if the specified path is exceed the system-defined max length. public Bitmap(string path, ColorOrder colorOrder = ColorOrder.BGR) : base(0, 0, ColorDepth.ColorDepth32) //Call the image constructor with wrong values { using (var fs = new FileStream(path, FileMode.Open)) { CreateBitmap(fs, colorOrder); } } /// /// Create new inctanse of the class, with a specified image data byte array. /// /// byte array. /// Thrown if imageData is null / memory error. /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on IO error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// /// /// Thrown if header is not from a BMP. /// Info header size has the wrong value. /// Number of planes is not 1. /// Total Image Size is smaller than pure image size. /// /// /// Thrown if pixelsize is other then 32 / 24 or the file compressed. public Bitmap(byte[] imageData) : this(imageData, ColorOrder.BGR) //Call the image constructor with wrong values { } /// /// Create new inctanse of the class, with a specified image data byte array. /// /// byte array. /// Order of colors in each pixel. /// Thrown if imageData is null / memory error. /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on IO error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// /// /// Thrown if header is not from a BMP. /// Info header size has the wrong value. /// Number of planes is not 1. /// Total Image Size is smaller than pure image size. /// /// /// Thrown if pixelsize is other then 32 / 24 or the file compressed. public Bitmap(byte[] imageData, ColorOrder colorOrder = ColorOrder.BGR) : base(0, 0, ColorDepth.ColorDepth32) //Call the image constructor with wrong values { using (var ms = new MemoryStream(imageData)) { CreateBitmap(ms, colorOrder); } } // For more information about the format: https://docs.microsoft.com/en-us/previous-versions/ms969901(v=msdn.10)?redirectedfrom=MSDN /// /// Create bitmap from stream. /// /// Stream. /// Order of colors in each pixel. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on IO error. /// /// /// Thrown on fatal error (contact support). /// The stream does not support seeking. /// /// /// Thrown if the stream is closed. /// /// /// Thrown if header is not from a BMP. /// Info header size has the wrong value. /// Number of planes is not 1. Can not read file. /// Total Image Size is smaller than pure image size. /// /// /// Thrown if pixelsize is other then 32 / 24 or the file compressed. private void CreateBitmap(Stream stream, ColorOrder colorOrder) { #region BMP Header Byte[] _int = new byte[4]; Byte[] _short = new byte[2]; //Assume that we are using the BMP (Windows) V3 header format //reading magic number to identify if BMP file (BM as string - 42 4D as Hex) - bytes 0 -> 2 stream.Read(_short, 0, 2); if ("42-4D" != BitConverter.ToString(_short)) { throw new Exception("Header is not from a BMP"); } //read size of BMP file - byte 2 -> 6 stream.Read(_int, 0, 4); uint fileSize = BitConverter.ToUInt32(_int, 0); stream.Position = 10; //read header - bytes 10 -> 14 is the offset of the bitmap image data stream.Read(_int, 0, 4); uint pixelTableOffset = BitConverter.ToUInt32(_int, 0); //now reading size of BITMAPINFOHEADER should be 40 - bytes 14 -> 18 stream.Read(_int, 0, 4); uint infoHeaderSize = BitConverter.ToUInt32(_int, 0); if (infoHeaderSize != 40 && infoHeaderSize != 56 && infoHeaderSize != 124) // 124 - is BITMAPV5INFOHEADER, 56 - is BITMAPV3INFOHEADER, where we ignore the additional values see https://web.archive.org/web/20150127132443/https://forums.adobe.com/message/3272950 { throw new Exception("Info header size has the wrong value!"); } //now reading width of image in pixels - bytes 18 -> 22 stream.Read(_int, 0, 4); uint imageWidth = BitConverter.ToUInt32(_int, 0); //now reading height of image in pixels - byte 22 -> 26 stream.Read(_int, 0, 4); uint imageHeight = BitConverter.ToUInt32(_int, 0); //now reading number of planes should be 1 - byte 26 -> 28 stream.Read(_short, 0, 2); ushort planes = BitConverter.ToUInt16(_short, 0); if (planes != 1) { throw new Exception("Number of planes is not 1! Can not read file!"); } //now reading size of bits per pixel (1, 4, 8, 24, 32) - bytes 28 - 30 stream.Read(_short, 0, 2); ushort pixelSize = BitConverter.ToUInt16(_short, 0); //TODO: Be able to handle other pixel sizes if (!(pixelSize == 32 || pixelSize == 24)) { throw new NotImplementedException("Can only handle 32bit or 24bit bitmaps!"); } //now reading compression type - bytes 30 -> 34 stream.Read(_int, 0, 4); uint compression = BitConverter.ToUInt32(_int, 0); //TODO: Be able to handle compressed files if (compression != 0 && compression != 3) //3 is BI_BITFIELDS again ignore for now is for Adobe Images { //Global.mDebugger.Send("Can only handle uncompressed files!"); throw new NotImplementedException("Can only handle uncompressed files!"); } //now reading total image data size(including padding) - bytes 34 -> 38 stream.Read(_int, 0, 4); uint totalImageSize = BitConverter.ToUInt32(_int, 0); if (totalImageSize == 0) { totalImageSize = (uint)((((imageWidth * pixelSize) + 31) & ~31) >> 3) * imageHeight; // Look at the link above for the explanation Global.mDebugger.SendInternal("Calcualted image size: " + totalImageSize); } #endregion BMP Header //Set the bitmap to have the correct values Width = imageWidth; Height = imageHeight; Depth = (ColorDepth)pixelSize; Global.mDebugger.SendInternal("Width: " + Width); Global.mDebugger.SendInternal("Height: " + Height); Global.mDebugger.SendInternal("Depth: " + pixelSize); rawData = new int[Width * Height]; #region Pixel Table //Calculate padding int paddingPerRow; int pureImageSize = (int)(imageWidth * imageHeight * pixelSize / 8); if (totalImageSize != 0) { int remainder = (int)totalImageSize - pureImageSize; if (remainder < 0) { throw new Exception("Total Image Size is smaller than pure image size"); } paddingPerRow = remainder / (int)imageHeight; pureImageSize = (int)totalImageSize; } else { //total image size is 0 if it is not compressed paddingPerRow = 0; } //Read data stream.Position = (int)pixelTableOffset; int position = 0; Byte[] pixelData = new byte[pureImageSize]; stream.Read(pixelData, 0, pureImageSize); Byte[] pixel = new byte[4]; //All must have the same size for (int y = 0; y < imageHeight; y++) { for (int x = 0; x < imageWidth; x++) { if (pixelSize == 32) { pixel[0] = pixelData[position++]; pixel[1] = pixelData[position++]; pixel[2] = pixelData[position++]; pixel[3] = pixelData[position++]; } else { if(colorOrder == ColorOrder.BGR) { pixel[3] = pixelData[position++]; pixel[2] = pixelData[position++]; pixel[1] = pixelData[position++]; pixel[0] = 0; } else { pixel[0] = pixelData[position++]; pixel[1] = pixelData[position++]; pixel[2] = pixelData[position++]; pixel[3] = 0; } } rawData[x + (imageHeight - (y + 1)) * imageWidth] = BitConverter.ToInt32(pixel, 0); //This bits should be A, R, G, B but order is switched } position += paddingPerRow; } #endregion Pixel Table } /// /// Save image as bmp file. /// /// Path to the file. /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown on memory error. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on IO error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). public void Save(string path) { using (FileStream fs = File.Open(path, FileMode.Create)) { Save(fs, ImageFormat.bmp); } } /// /// Save image to stream. /// /// Stream. /// Image format. /// Thrown on memory error. /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown on fatal error (contact support). /// Thrown on memory error. /// Thrown on memory error. /// Thrown on memory error. /// Thrown on IO error. /// Thrown if the stream does not support writing. /// Thrown if the stream is closed. public void Save(Stream stream, ImageFormat imageFormat) { //Calculate padding int padding = 4 - (((int)Width * (int)Depth) % 32) / 8; if (padding == 4) { padding = 0; } Byte[] file = new Byte[54 /*header*/ + Width * Height * (uint)Depth / 8 + padding * Height]; //Writes all bytes at the end into the stream, rather than a few every time int position = 0; //Set signature byte[] data = BitConverter.GetBytes(0x4D42); Array.Copy(data, 0, file, position, 2); position += 2; //Write apporiximate file size data = BitConverter.GetBytes(54 /*header*/ + Width * Height * (uint)Depth / 8 /*assume that it is full bytes */); Array.Copy(data, 0, file, position, 4); position += 4; //Leave bytes 6 -> 10 empty data = new Byte[] { 0, 0, 0, 0 }; Array.Copy(data, 0, file, position, 4); position += 4; //Offset to start of image data uint offset = 54; data = BitConverter.GetBytes(offset); Array.Copy(data, 0, file, position, 4); position += 4; //Write size of bitmapinfoheader data = BitConverter.GetBytes(40); Array.Copy(data, 0, file, position, 4); position += 4; //Width in pixels data = BitConverter.GetBytes(Width); Array.Copy(data, 0, file, position, 4); position += 4; //Height in pixels data = BitConverter.GetBytes(Height); Array.Copy(data, 0, file, position, 4); position += 4; //Number of planes(1) data = BitConverter.GetBytes(1); Array.Copy(data, 0, file, position, 2); position += 2; //Bits per pixel data = BitConverter.GetBytes((int)Depth); Array.Copy(data, 0, file, position, 2); position += 2; //Compression type data = BitConverter.GetBytes(0); Array.Copy(data, 0, file, position, 4); position += 4; //Size of image data in bytes data = BitConverter.GetBytes(Width * Height * (uint)Depth / 8); Array.Copy(data, 0, file, position, 4); position += 4; //Horizontal resolution in meters (is not accurate) data = BitConverter.GetBytes(Width / 40); Array.Copy(data, 0, file, position, 4); position += 4; //Vertical resolution in meters (is not accurate) data = BitConverter.GetBytes(Height / 40); Array.Copy(data, 0, file, position, 0); position += 4; //Number of colors in image /zero data = BitConverter.GetBytes(0); Array.Copy(data, 0, file, position, 0); position += 4; //number of important colors in image / zero data = BitConverter.GetBytes(0); Array.Copy(data, 0, file, position, 4); position += 4; //Finished header //Copy image data position = (int)offset; int byteNum = (int)Depth / 8; byte[] imageData = new byte[Width * Height * byteNum + padding * Height]; int imageDataPoint = 0; int cOffset = 4 - (int)Depth / 8; for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { data = BitConverter.GetBytes(rawData[x + (Height - (y + 1)) * Width]); for (int i = 0; i < byteNum; i++) { imageData[imageDataPoint++] = data[i + cOffset]; } } imageDataPoint += padding; } Array.Copy(imageData, 0, file, position, imageData.Length); stream.Write(file, 0, file.Length); } } }