///|
fn write_u32be(buf : FixedArray[Byte], offset : Int, value : UInt) -> Unit {
buf[offset] = (value >> 24).land(0xFFU).to_byte()
buf[offset + 1] = (value >> 16).land(0xFFU).to_byte()
buf[offset + 2] = (value >> 8).land(0xFFU).to_byte()
buf[offset + 3] = value.land(0xFFU).to_byte()
}
///|
fn encode_chunk(chunk_type : FixedArray[Byte], data : Bytes) -> Bytes {
let len = data.length()
let total = 12 + len
let buf = FixedArray::make(total, b'\x00')
// Length
write_u32be(buf, 0, len.reinterpret_as_uint())
// Type
buf[4] = chunk_type[0]
buf[5] = chunk_type[1]
buf[6] = chunk_type[2]
buf[7] = chunk_type[3]
// Data
buf.blit_from_bytes(8, data, 0, len)
// CRC over type + data
let crc_input = FixedArray::make(4 + len, b'\x00')
chunk_type.blit_to(crc_input, len=4)
crc_input.blit_from_bytes(4, data, 0, len)
let crc = @zlib.crc32_fixed(crc_input)
write_u32be(buf, 8 + len, crc)
Bytes::from_array(buf)
}
///|
pub fn encode_png(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(),
)
}
// Build signature
let sig = FixedArray::make(8, b'\x00')
sig[0] = b'\x89'
sig[1] = b'\x50'
sig[2] = b'\x4E'
sig[3] = b'\x47'
sig[4] = b'\x0D'
sig[5] = b'\x0A'
sig[6] = b'\x1A'
sig[7] = b'\x0A'
// IHDR data: 13 bytes
let ihdr_data = FixedArray::make(13, b'\x00')
write_u32be(ihdr_data, 0, width.reinterpret_as_uint())
write_u32be(ihdr_data, 4, height.reinterpret_as_uint())
ihdr_data[8] = b'\x08' // bit depth 8
ihdr_data[9] = b'\x06' // color type 6 (RGBA)
// compression=0, filter=0, interlace=0 already zero
let ihdr_chunk = encode_chunk(
[b'\x49', b'\x48', b'\x44', b'\x52'],
Bytes::from_array(ihdr_data),
)
// Build raw scanlines with adaptive filtering
let stride = width * 4
let bpp = 4
let raw_len = height * (1 + stride)
let raw = FixedArray::make(raw_len, b'\x00')
let mut prev_row = Bytes::new(stride)
for y in 0.. panic() }
assert_eq(decoded.width, original.width)
assert_eq(decoded.height, original.height)
assert_eq(decoded.data, original.data)
}
///|
test "encode_png roundtrip 4x4" {
let width = 4
let height = 4
let buf = FixedArray::make(width * height * 4, b'\x00')
for y in 0.. panic() }
assert_eq(decoded.width, original.width)
assert_eq(decoded.height, original.height)
assert_eq(decoded.data, original.data)
}
///|
test "encode_png invalid dimensions" {
let img : ImageData = { width: 0, height: 1, data: Bytes::new(0) }
let mut got_error = false
try {
let _ = encode_png(img)
} catch {
_ => got_error = true
}
assert_true(got_error)
}
///|
test "encode_png invalid data length" {
let img : ImageData = { width: 2, height: 2, data: Bytes::new(4) }
let mut got_error = false
try {
let _ = encode_png(img)
} catch {
_ => got_error = true
}
assert_true(got_error)
}