summaryrefslogtreecommitdiff
path: root/image/png/decoder.ha
diff options
context:
space:
mode:
authorLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:11:21 +0200
committerLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:51:35 +0200
commitae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch)
tree5f462459ae4b47d22114eed717d1382d08cf4dfe /image/png/decoder.ha
Initial commit (import old repo)HEADmain
Diffstat (limited to 'image/png/decoder.ha')
-rw-r--r--image/png/decoder.ha171
1 files changed, 171 insertions, 0 deletions
diff --git a/image/png/decoder.ha b/image/png/decoder.ha
new file mode 100644
index 0000000..4516bb7
--- /dev/null
+++ b/image/png/decoder.ha
@@ -0,0 +1,171 @@
+use bufio;
+use bytes;
+use compress::zlib;
+use io;
+
+export type decoder = struct {
+ vtable: io::stream,
+ src: io::handle,
+ inflate: zlib::reader,
+ ihdr: *ihdr,
+ filter: (filter | void),
+ bpp: size, // bytes per pixel, rounded up
+ buffered: size,
+ cr: []u8,
+ pr: []u8,
+};
+
+// Returns a new image decoder, from which raw pixel data may be read via
+// [[io::read]]. The input should be a compressed IDAT data stream, normally
+// obtained via an [[idat_reader]].
+//
+// The user must provide a pixel buffer of suitable size to store two scanlines
+// for image filtering, or larger for interlaced images; see [[decoder_bufsiz]]
+// to determine the appropriate length.
+//
+// The header is borrowed from the input and should remain valid until the
+// decoder is no longer in use.
+//
+// The user must call [[io::close]] afterwards to free resources allocated for
+// the decompressor. The call never returns an error.
+export fn newdecoder(
+ src: io::handle,
+ ihdr: *ihdr,
+ buf: []u8,
+) (decoder | error) = {
+ assert(len(buf) == decoder_bufsiz(ihdr),
+ "Incorrect buffer length provided for PNG decoder");
+ bytes::zero(buf);
+ return decoder {
+ vtable = &decoder_vtable,
+ src = src,
+ inflate = zlib::decompress(src)?,
+ ihdr = ihdr,
+ filter = void,
+ bpp = bytesperpixel(ihdr),
+ buffered = 0,
+ cr = buf[..len(buf) / 2],
+ pr = buf[len(buf) / 2..],
+ };
+};
+
+const decoder_vtable: io::vtable = io::vtable {
+ reader = &decoder_read,
+ closer = &decoder_close,
+ ...
+};
+
+fn decoder_read(st: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
+ let dec = st: *decoder;
+ assert(dec.vtable == &decoder_vtable);
+
+ if (dec.buffered != 0) {
+ return decoder_copy(dec, buf);
+ };
+
+ if (dec.filter is void) {
+ const ft = match (bufio::read_byte(&dec.inflate)?) {
+ case io::EOF =>
+ if (!(io::read(dec.src, &[0u8])? is io::EOF)) {
+ // Extra data following zlib stream
+ return wraperror(invalid);
+ };
+ return io::EOF;
+ case let b: u8 =>
+ yield b: filter;
+ };
+ if (ft > filter::PAETH) {
+ return wraperror(unsupported);
+ };
+ dec.filter = ft;
+ };
+
+ if (dec.ihdr.interlace == interlace::ADAM7) {
+ abort(); // TODO
+ };
+
+ // Read one scanline
+ match (io::readall(&dec.inflate, dec.cr)) {
+ case io::EOF =>
+ return wraperror(invalid);
+ case let err: io::error =>
+ if (err is io::underread) {
+ return wraperror(invalid);
+ };
+ return err;
+ case size => void;
+ };
+
+ applyfilter(dec);
+ dec.buffered = len(dec.cr);
+ return decoder_copy(dec, buf);
+};
+
+fn decoder_close(st: *io::stream) (void | io::error) = {
+ let dec = st: *decoder;
+ assert(dec.vtable == &decoder_vtable);
+ io::close(&dec.inflate)?;
+};
+
+// Returns the size of a buffer suitable to store decoder state for this image.
+// The computed size is twice the length of a scanline in bytes, unless the
+// image is interlaced, in which case the buffer size is the full length
+// necessary to store the complete image.
+export fn decoder_bufsiz(ihdr: *ihdr) size = {
+ let sampledepth = ihdr.bitdepth: uint;
+ const samples: uint = switch (ihdr.colortype) {
+ case colortype::GRAYSCALE =>
+ yield 1;
+ case colortype::RGB =>
+ yield 3;
+ case colortype::PLTE =>
+ yield 1;
+ case colortype::GRAYALPHA =>
+ yield 2;
+ case colortype::RGBA =>
+ yield 4;
+ };
+ const width = ihdr.width: uint, height = ihdr.height: uint;
+
+ let scanline = width * samples * sampledepth; // in bits
+ if (scanline % 8 != 0) {
+ // Pad to nearest byte
+ scanline += 8 - (scanline % 8);
+ };
+
+ if (ihdr.interlace == interlace::ADAM7) {
+ return scanline * height;
+ };
+
+ return (scanline / 8) * 2; // Two scanlines
+};
+
+fn bytesperpixel(ihdr: *ihdr) size = {
+ let sampledepth = ihdr.bitdepth: uint;
+ const samples: uint = switch (ihdr.colortype) {
+ case colortype::GRAYSCALE =>
+ yield 1;
+ case colortype::RGB =>
+ yield 3;
+ case colortype::PLTE =>
+ yield 1;
+ case colortype::GRAYALPHA =>
+ yield 2;
+ case colortype::RGBA =>
+ yield 4;
+ };
+ return (sampledepth * samples + 7) / 8;
+};
+
+// Copies pending pixel data into a user buffer.
+fn decoder_copy(dec: *decoder, buf: []u8) size = {
+ let max = dec.buffered;
+ if (len(buf) < max) {
+ max = len(buf);
+ };
+ assert(max > 0);
+ buf[..max] = dec.cr[..max];
+ dec.cr[..len(dec.cr)-max] = dec.cr[max..];
+ dec.buffered -= max;
+ return max;
+};