/// <summary> /// Encodes the provided BGRA pixel data to a 24bit BMP image. /// </summary> /// <param name="pixelData">The date to encode.</param> /// <param name="width">The width of the image in the data.</param> /// <param name="height">The height of the image in the data.</param> /// <returns>A 24bit BMP image as bytes.</returns> public static byte[] Encode(byte[] pixelData, int width, int height) { int rowWidth = width; int amount = width * 3 % 4; if (amount != 0) { rowWidth += 4 - amount; } int fileSize = BmpFileHeader.DEFAULT_OFFSET + height * rowWidth * 3; var output = new byte[fileSize]; using (var writer = new BinaryWriter(new MemoryStream(output))) { var fileHeader = new BmpFileHeader { FileSize = fileSize, Height = height, Width = width, BitsPerPixel = 24, Planes = 1, ImageSize = fileSize - BmpFileHeader.DEFAULT_OFFSET, ClrUsed = 0, ClrImportant = 0 }; writer.Write(fileHeader.Type); writer.Write(fileHeader.FileSize); writer.Write(fileHeader.Reserved); writer.Write(fileHeader.Offset); writer.Write(fileHeader.HeaderSize); writer.Write(fileHeader.Width); writer.Write(fileHeader.Height); writer.Write(fileHeader.Planes); writer.Write(fileHeader.BitsPerPixel); writer.Write(fileHeader.Compression); writer.Write(fileHeader.ImageSize); writer.Write(fileHeader.XPelsPerMeter); writer.Write(fileHeader.YPelsPerMeter); writer.Write(fileHeader.ClrUsed); writer.Write(fileHeader.ClrImportant); if (amount != 0) { amount = 4 - amount; } for (int y = height - 1; y >= 0; y--) { for (var x = 0; x < width; x++) { int offset = (y * width + x) * 4; writer.Write(pixelData[offset + 0]); writer.Write(pixelData[offset + 1]); writer.Write(pixelData[offset + 2]); } for (var i = 0; i < amount; i++) { writer.Write((byte)0); } } } return(output); }
/// <summary> /// Decode the provided bmp to a BGRA pixel array. /// Supported formats are: /// - 1, 4, and 8 bit BMP, tested with up to 256 colors. /// - 16bit BMP (untested) /// - 24bit BMP /// - 32bit BMP (untested) /// </summary> /// <param name="bmpData">The bmp file as bytes to decode.</param> /// <param name="fileHeader">The header of the file. Contains information such as the size of the image.</param> /// <returns>An RGBA pixel array extracted from the BMP.</returns> public static byte[] Decode(ReadOnlyMemory <byte> bmpData, out BmpFileHeader fileHeader) { using var reader = new ByteReader(bmpData); fileHeader = new BmpFileHeader { Type = reader.ReadInt16(), FileSize = reader.ReadInt32(), Reserved = reader.ReadInt32(), Offset = reader.ReadInt32(), // Second header HeaderSize = reader.ReadInt32(), Width = reader.ReadInt32(), Height = reader.ReadInt32(), Planes = reader.ReadInt16(), BitsPerPixel = reader.ReadInt16(), Compression = reader.ReadInt32(), ImageSize = reader.ReadInt32(), XPelsPerMeter = reader.ReadInt32(), YPelsPerMeter = reader.ReadInt32(), ClrUsed = reader.ReadInt32(), ClrImportant = reader.ReadInt32() }; if (fileHeader.Compression != 0) { throw new Exception($"Unsupported BMP compression - {fileHeader.Compression}"); } int colorMapSize = -1; if (fileHeader.ClrUsed == 0) { if (fileHeader.BitsPerPixel == 1 || fileHeader.BitsPerPixel == 4 || fileHeader.BitsPerPixel == 8) { colorMapSize = (int)Math.Pow(2, fileHeader.BitsPerPixel) * 4; } } else { colorMapSize = fileHeader.ClrUsed * 4; } ReadOnlySpan <byte> palette = null; if (colorMapSize > 0) { palette = reader.ReadBytes(colorMapSize); } ReadOnlySpan <byte> pixelIn = reader.ReadBytes(fileHeader.ImageSize); var pixelOut = new byte[fileHeader.Width * fileHeader.Height * 4]; if (fileHeader.HeaderSize != 40) { throw new Exception( $"Header Size value '{fileHeader.HeaderSize}' is not valid."); } if (fileHeader.BitsPerPixel == 32) { ReadRgb32(pixelIn, pixelOut, fileHeader.Width, fileHeader.Height); } else if (fileHeader.BitsPerPixel == 24) { ReadRgb24(pixelIn, pixelOut, fileHeader.Width, fileHeader.Height); } else if (fileHeader.BitsPerPixel == 16) { ReadRgb16(pixelIn, pixelOut, fileHeader.Width, fileHeader.Height); } else if (fileHeader.BitsPerPixel <= 8) { ReadRgbPalette( pixelIn, pixelOut, palette, fileHeader.Width, fileHeader.Height, fileHeader.BitsPerPixel); } return(pixelOut); }