mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
160 lines
5.8 KiB
C#
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;
|
|
}
|
|
}
|