A streaming PNG and APNG decoder for MoonBit: arbitrary input chunks, whole rows into bounded output slices, two rows of memory.
moon add gaato/png///|
/// The rows of a PNG image given in `chunks`, one after another.
fn rows_of(chunks : Array[Bytes]) -> Bytes raise {
let decoder = @png.Decoder()
let rows = Buffer()
let mut out = FixedArray::make(0, b'\x00')
for i, chunk in chunks {
let mut input = chunk[:]
for ;; {
// A slice must hold at least one row: `room_needed` says how long.
if out.length() < decoder.room_needed() {
out = FixedArray::make(decoder.room_needed() * 16, b'\x00')
}
let status = decoder.step_into(input, out, end=i == chunks.length() - 1)
input = input[decoder.last_consumed():]
match status {
Rows =>
rows.write_bytes(Bytes::from_array(out[:decoder.last_produced()]))
Done => return rows.to_bytes()
NeedMoreInput => break
_ => ()
}
}
}
rows.to_bytes()
}
///|
test "decode a 2 x 2 grayscale image in two pieces" {
let png = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x02\x00\x00\x00\x02\x08\x00\x00\x00\x00\x57\xdd\x52\xf8\x00\x00\x00\x0e\x49\x44\x41\x54\x78\xda\x63\x60\xf8\xcf\xd0\xe0\x00\x00\x05\x42\x01\xc0\x6d\x5e\x78\xa0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
let rows = rows_of([png[:30].to_owned(), png[30:].to_owned()])
assert_eq(rows, b"\x00\xff\x80\x40")
}///|
test "decode a whole image and read it as RGBA" {
// 2 x 1, indexed-color, 2 bits a pixel: red at half alpha, then blue.
let png = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x02\x00\x00\x00\x01\x02\x03\x00\x00\x00\x89\x4c\x97\x19\x00\x00\x00\x06\x50\x4c\x54\x45\xff\x00\x00\x00\x00\xff\x6c\xa1\xfd\x8e\x00\x00\x00\x01\x74\x52\x4e\x53\x80\xad\x5e\x5b\x46\x00\x00\x00\x0a\x49\x44\x41\x54\x78\xda\x63\x10\x00\x00\x00\x12\x00\x11\x08\xde\xbd\xc3\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
assert_true(@png.is_signature(png))
let image = @png.decode(png, max_output=1 << 20)
let rgba = FixedArray::make(image.width * 4, b'\x00')
image.converter.rgba8(image.row(0), image.width, rgba.mut_view())
assert_eq(Bytes::from_array(rgba[:]), b"\xff\x00\x00\x80\x00\x00\xff\xff")
}///|
test "report the text chunks" {
let png = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00\x3a\x7e\x9b\x55\x00\x00\x00\x0e\x74\x45\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x73\x6f\x6d\x65\x6f\x6e\x65\xbf\x83\xd1\xc0\x00\x00\x00\x0a\x49\x44\x41\x54\x78\xda\x63\x60\x00\x00\x00\x02\x00\x01\xe5\x27\xde\xfc\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
let decoder = @png.Decoder(report=["tEXt"])
let out = FixedArray::make(256, b'\x00')
let mut rest = png[:]
let texts = []
for ;; {
match decoder.step_into(rest, out, end=true) {
Metadata =>
texts.push(
"\{decoder.metadata_type()}: \{Bytes::from_array(out[:decoder.last_produced()])}",
)
Done => break
_ => ()
}
rest = rest[decoder.last_consumed():]
}
inspect(
texts.join("\n"),
content=(
#|tEXt: b"Author\x00someone"
),
)
}pub struct Converter {
// private fields
}fn Converter::Converter(color_type : ColorType, bit_depth : Int, palette? : ArrayView[Byte], transparency? : Transparency) -> Converterfn Converter::expanded(self : Converter, row : ArrayView[Byte], pixels : Int, into : MutArrayView[Byte]) -> Unitfn Converter::rgba16(self : Converter, row : ArrayView[Byte], pixels : Int, into : MutArrayView[Byte]) -> Unitfn Converter::rgba8(self : Converter, row : ArrayView[Byte], pixels : Int, into : MutArrayView[Byte]) -> Unitpub struct Decoder {
// private fields
}fn step(self : Decoder, input : BytesView, output : MutArrayView[Byte], end~ : Bool) -> Status raise CodecErrorfn Decoder::step(self : Decoder, input : BytesView, output : MutArrayView[Byte], end~ : Bool) -> Status raise CodecErrorfn Decoder::step_into(self : Decoder, input : BytesView, output : FixedArray[Byte], end~ : Bool) -> Status raise CodecErrorfn adam7_columns(pass : Int, width : Int) -> Intfn adam7_dx(pass : Int) -> Intfn adam7_dy(pass : Int) -> Intfn adam7_rows(pass : Int, height : Int) -> Intfn adam7_x(pass : Int) -> Intfn adam7_y(pass : Int) -> Intfn is_signature(data : BytesView) -> Boolfn scatter(row : ArrayView[Byte], pixels : Int, bits : Int, x : Int, dx : Int, into : MutArrayView[Byte]) -> UnitInstall
Download zipA streaming PNG and APNG decoder for MoonBit: arbitrary input chunks, whole rows into bounded output slices, two rows of memory.