2025-06-23 11:42:47 -07:00

160 lines
5.8 KiB
C#

using System.Runtime.CompilerServices;
namespace AssetRipper.Export.Modules.Textures;
/// <summary>
/// This is really fast, even in comparison to System.Drawing<br/>
/// It could be even faster if it didn't have to flip the pixels in the y direction
/// </summary>
/// <remarks>
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/>
/// </remarks>
internal static class BmpWriter
{
/*
static byte[] bmpData = // All values are little-endian
{
//14 bytes
0x42, 0x4D, // Signature 'BM'
0x9a, 0x00, 0x00, 0x00, // Size: 154 bytes
0x00, 0x00, // Unused
0x00, 0x00, // Unused
0x7a, 0x00, 0x00, 0x00, // Offset to image data, ie 122
//108 bytes
0x6c, 0x00, 0x00, 0x00, // DIB header size (108 bytes)
0x04, 0x00, 0x00, 0x00, // Width (4px)
0x02, 0x00, 0x00, 0x00, // Height (2px)
0x01, 0x00, // Planes (1)
0x20, 0x00, // Bits per pixel (32)
0x03, 0x00, 0x00, 0x00, // Format (bitfield = use bitfields | no compression)
0x20, 0x00, 0x00, 0x00, // Image raw size (32 bytes)
0x13, 0x0B, 0x00, 0x00, // Horizontal print resolution (2835 = 72dpi * 39.3701)
0x13, 0x0B, 0x00, 0x00, // Vertical print resolution (2835 = 72dpi * 39.3701)
0x00, 0x00, 0x00, 0x00, // Colors in palette (none)
0x00, 0x00, 0x00, 0x00, // Important colors (0 = all)
0x00, 0x00, 0xFF, 0x00, // R bitmask (00FF0000)
0x00, 0xFF, 0x00, 0x00, // G bitmask (0000FF00)
0xFF, 0x00, 0x00, 0x00, // B bitmask (000000FF)
0x00, 0x00, 0x00, 0xFF, // A bitmask (FF000000)
0x42, 0x47, 0x52, 0x73, // sRGB color space
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused R entry for color space
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused G entry for color space
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused B entry for color space
0x00, 0x00, 0x00, 0x00, // Unused Gamma X entry for color space
0x00, 0x00, 0x00, 0x00, // Unused Gamma Y entry for color space
0x00, 0x00, 0x00, 0x00, // Unused Gamma Z entry for color space
// Image data: 32 bytes
0xFF, 0x00, 0x00, 0x7F, // Bottom left pixel
0x00, 0xFF, 0x00, 0x7F,
0x00, 0x00, 0xFF, 0x7F,
0xFF, 0xFF, 0xFF, 0x7F, // Bottom right pixel
0xFF, 0x00, 0x00, 0xFF, // Top left pixel
0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF // Top right pixel
};
*/
public static void WriteBmp(byte[] bgra32Data, int width, int height, Stream stream, bool flip = true)
{
ArgumentNullException.ThrowIfNull(bgra32Data);
ArgumentNullException.ThrowIfNull(stream);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(width);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(height);
ThrowIfIncorrectLength(bgra32Data, width, height);
using BinaryWriter writer = new BinaryWriter(stream);
//14 bytes
writer.WriteBytes(0x42, 0x4D); // Signature 'BM'
writer.Write(GetTotalSize(width, height)); // Size of the file
writer.WriteZeroBytes(4); // 2 unused shorts
writer.Write((uint)(14 + (16 + 92))); // offset to image data
//16 bytes
writer.Write((uint)108); // DIB header size (108 bytes)
writer.Write(width); // Width
writer.Write(height); // Height
writer.Write((ushort)1); // Planes (1)
writer.Write((ushort)32); // Bits per pixel (32)
//92 bytes
writer.WriteBytes(0x03, 0x00, 0x00, 0x00); // Format (bitfield = use bitfields | no compression)
writer.Write(GetRawImageSize(width, height)); // Image raw size in bytes
writer.WriteBytes(0x13, 0x0B, 0x00, 0x00); // Horizontal print resolution (2835 = 72dpi * 39.3701)
writer.WriteBytes(0x13, 0x0B, 0x00, 0x00); // Vertical print resolution (2835 = 72dpi * 39.3701)
writer.WriteZeroBytes(4); // Colors in palette (none)
writer.WriteZeroBytes(4); // Important colors (0 = all)
writer.WriteBytes(0x00, 0x00, 0xFF, 0x00); // R bitmask (00FF0000)
writer.WriteBytes(0x00, 0xFF, 0x00, 0x00); // G bitmask (0000FF00)
writer.WriteBytes(0xFF, 0x00, 0x00, 0x00); // B bitmask (000000FF)
writer.WriteBytes(0x00, 0x00, 0x00, 0xFF); // A bitmask (FF000000)
writer.WriteBytes(0x42, 0x47, 0x52, 0x73); // sRGB color space
writer.WriteZeroBytes(12); // Unused R entry for color space
writer.WriteZeroBytes(12); // Unused G entry for color space
writer.WriteZeroBytes(12); // Unused B entry for color space
writer.WriteZeroBytes(4); // Unused Gamma X entry for color space
writer.WriteZeroBytes(4); // Unused Gamma Y entry for color space
writer.WriteZeroBytes(4); // Unused Gamma Z entry for color space
if (flip)
{
writer.WriteFlippedY(bgra32Data, width, height);
}
else
{
writer.Write(bgra32Data.AsSpan());
}
static void ThrowIfIncorrectLength(byte[] bgra32Data, int width, int height, [CallerArgumentExpression(nameof(bgra32Data))] string? paramName = null)
{
if (bgra32Data.Length != GetRawImageSize(width, height))
{
throw new ArgumentException("Length must match 4 * width * height", paramName);
}
}
}
private static void WriteFlippedY(this BinaryWriter writer, byte[] data, int width, int height)
{
for (int r = height - 1; r >= 0; r--)
{
writer.Write(data.AsSpan(r * width * 4, width * 4));
}
}
private static void WriteBytes(this BinaryWriter writer, byte byte0, byte byte1)
{
writer.Write(byte0);
writer.Write(byte1);
}
private static void WriteBytes(this BinaryWriter writer, byte byte0, byte byte1, byte byte2, byte byte3)
{
writer.Write(byte0);
writer.Write(byte1);
writer.Write(byte2);
writer.Write(byte3);
}
private static void WriteZeroBytes(this BinaryWriter writer, int count)
{
for (int i = 0; i < count; i++)
{
writer.Write((byte)0);
}
}
private static int GetTotalSize(int width, int height)
{
return 14 + 108 + GetRawImageSize(width, height);
}
private static int GetRawImageSize(int width, int height)
{
return 4 * width * height;
}
}