summaryrefslogtreecommitdiff
path: root/image/png/decoder.ha
blob: 4516bb7aac712e857acba510ed8d5d77329e0494 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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;
};