Files
Minint/Minint.Infrastructure/Export/BmpExporter.cs

95 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Minint.Core.Services;
namespace Minint.Infrastructure.Export;
/// <summary>
/// Writes a 32-bit BGRA BMP (BITMAPV4HEADER) from an ARGB pixel buffer.
/// BMP rows are bottom-up, so we flip vertically during write.
/// </summary>
public sealed class BmpExporter : IBmpExporter
{
private const int BmpFileHeaderSize = 14;
private const int BitmapV4HeaderSize = 108;
private const int HeadersTotal = BmpFileHeaderSize + BitmapV4HeaderSize;
public void Export(Stream stream, uint[] pixels, int width, int height)
{
ArgumentNullException.ThrowIfNull(stream);
ArgumentNullException.ThrowIfNull(pixels);
if (pixels.Length != width * height)
throw new ArgumentException("Pixel buffer size does not match dimensions.");
int rowBytes = width * 4;
int imageSize = rowBytes * height;
int fileSize = HeadersTotal + imageSize;
using var w = new BinaryWriter(stream, System.Text.Encoding.UTF8, leaveOpen: true);
// BITMAPFILEHEADER (14 bytes)
w.Write((byte)'B');
w.Write((byte)'M');
w.Write(fileSize);
w.Write((ushort)0); // reserved1
w.Write((ushort)0); // reserved2
w.Write(HeadersTotal); // bfOffBits — смещение до пикселей от начала файла
// --- BITMAPV4HEADER (108 байт, см. WinGDI BITMAPV4HEADER) ---
// Поля ниже идут подряд, little-endian. Расширяет BITMAPINFOHEADER полями
// масок каналов (как при BI_BITFIELDS), типом цветового пространства и гаммой.
// bV4Size (4) — размер этой структуры в байтах; обязательно 108 для V4.
w.Write(BitmapV4HeaderSize);
// bV4Width (4) — ширина растра в пикселях.
w.Write(width);
// bV4Height (4) — высота. Значение > 0: строки снизу вверх (первый байт строки — нижняя линия изображения).
w.Write(height);
// bV4Planes (2) — число цветовых плоскостей, для BMP всегда 1.
w.Write((ushort)1);
// bV4BitCount (2) — бит на пиксель; 32 = BGRA по байтам в строке.
w.Write((ushort)32);
// bV4Compression (4) — BI_BITFIELDS (3): цвета кодируются масками bV4*Mask, не таблицей.
w.Write(3);
// bV4SizeImage (4) — размер сырого bitmap в байтах (здесь width*height*4).
w.Write(imageSize);
// bV4XPelsPerMeter, bV4YPelsPerMeter (по 4) — разрешение для метаданных (~2835 ≈ 72 DPI).
w.Write(2835);
w.Write(2835);
// bV4ClrUsed (4) — число используемых индексов палитры; 0 для полного 32 bpp.
w.Write(0);
// bV4ClrImportant (4) — «важных» цветов; 0 = все.
w.Write(0);
// Маски извлечения каналов из DWORD пикселя (порядок записи: R, G, B, A).
// В памяти строки: B, G, R, A (младший байт — B); DWORD при little-endian совпадает с 0xAARRGGBB.
w.Write(0x00FF0000u); // bV4RedMask
w.Write(0x0000FF00u); // bV4GreenMask
w.Write(0x000000FFu); // bV4BlueMask
w.Write(0xFF000000u); // bV4AlphaMask
// bV4CSType (4) — LCS_sRGB: 0x73524742 ('sRGB' в ASCII как little-endian DWORD).
w.Write(0x73524742);
// bV4Endpoints (36) — CIEXYZTRIPLE (координаты для профиля); для sRGB не используем — нули.
w.Write(new byte[36]);
// bV4GammaRed/Green/Blue (по 4 байта) — гамма; 0 = не задана / по умолчанию.
w.Write(new byte[12]);
// Pixel data: BMP is bottom-up, our buffer is top-down
for (int y = height - 1; y >= 0; y--)
{
int rowStart = y * width;
for (int x = 0; x < width; x++)
{
uint argb = pixels[rowStart + x];
byte a = (byte)(argb >> 24);
byte r = (byte)((argb >> 16) & 0xFF);
byte g = (byte)((argb >> 8) & 0xFF);
byte b = (byte)(argb & 0xFF);
w.Write(b);
w.Write(g);
w.Write(r);
w.Write(a);
}
}
}
}