///|
fn write_u32le(buf : FixedArray[Byte], offset : Int, value : UInt) -> Unit {
buf[offset] = value.land(0xFFU).to_byte()
buf[offset + 1] = (value >> 8).land(0xFFU).to_byte()
buf[offset + 2] = (value >> 16).land(0xFFU).to_byte()
buf[offset + 3] = (value >> 24).land(0xFFU).to_byte()
}
///|
fn write_u16le(buf : FixedArray[Byte], offset : Int, value : Int) -> Unit {
buf[offset] = (value & 0xFF).to_byte()
buf[offset + 1] = ((value >> 8) & 0xFF).to_byte()
}
///|
fn write_i32le(buf : FixedArray[Byte], offset : Int, value : Int) -> Unit {
write_u32le(buf, offset, value.reinterpret_as_uint())
}
///|
pub fn encode_bmp(img : ImageData) -> Bytes raise EncodeError {
let width = img.width
let height = img.height
if width <= 0 || height <= 0 {
raise InvalidDimensions(
"width and height must be positive: " +
width.to_string() +
"x" +
height.to_string(),
)
}
let expected_len = width * height * 4
if img.data.length() != expected_len {
raise InvalidData(
"expected " +
expected_len.to_string() +
" bytes, got " +
img.data.length().to_string(),
)
}
let row_size = width * 4
let pixel_data_size = row_size * height
let file_size = 54 + pixel_data_size
let pixel_offset = 54
let buf = FixedArray::make(file_size, b'\x00')
// File header (14 bytes)
buf[0] = b'\x42' // 'B'
buf[1] = b'\x4D' // 'M'
write_u32le(buf, 2, file_size.reinterpret_as_uint())
write_u32le(buf, 10, pixel_offset.reinterpret_as_uint())
// DIB header (BITMAPINFOHEADER, 40 bytes)
write_u32le(buf, 14, 40U)
write_i32le(buf, 18, width)
write_i32le(buf, 22, height)
write_u16le(buf, 26, 1) // planes
write_u16le(buf, 28, 32) // bits per pixel
write_u32le(buf, 34, pixel_data_size.reinterpret_as_uint())
// Pixel data (bottom-up, BGRA)
for y in 0..