ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/readpng.b/
implement RImagefile;
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
Point: import Draw;
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "imagefile.m";
include "crc.m";
crc: Crc;
CRCstate: import Crc;
include "filter.m";
inflate: Filter;
Chunk: adt {
size : int;
typ: string;
crc_state: ref CRCstate;
};
Png: adt {
depth: int;
filterbpp: int;
colortype: int;
compressionmethod: int;
filtermethod: int;
interlacemethod: int;
# tRNS
PLTEsize: int;
tRNS: array of byte;
# state for managing unpacking
alpha: int;
done: int;
error: string;
row, rowstep, colstart, colstep: int;
phase: int;
phasecols: int;
phaserows: int;
rowsize: int;
rowbytessofar: int;
thisrow: array of byte;
lastrow: array of byte;
};
init(iomod: Bufio)
{
if(sys == nil)
sys = load Sys Sys->PATH;
if(crc == nil)
crc = load Crc Crc->PATH;
if(inflate == nil)
inflate = load Filter "/dis/lib/inflate.dis";
inflate->init();
bufio = iomod;
}
readmulti(fd: ref Iobuf): (array of ref Rawimage, string)
{
(i, err) := read(fd);
if(i != nil){
a := array[1] of { i };
return (a, err);
}
return (nil, err);
}
read(fd: ref Iobuf): (ref Rawimage, string)
{
chunk := ref Chunk;
png := ref Png;
raw := ref Rawimage;
chunk.crc_state = crc->init(0, int 16rffffffff);
# Check it's a PNG
if (!get_signature(fd))
return (nil, "not a PNG");
# Get the IHDR
if (!get_chunk_header(fd, chunk))
return (nil, "duff header");
if (chunk.typ != "IHDR")
return (nil, "IHDR must come first");
if (chunk.size != 13)
return (nil, "IHDR wrong size");
raw.r.max.x = get_int(fd, chunk.crc_state);
if (raw.r.max.x <= 0)
return (nil, "invalid width");
raw.r.max.y = get_int(fd, chunk.crc_state);
if (raw.r.max.y <= 0)
return (nil, "invalid height");
png.depth = get_byte(fd, chunk.crc_state);
case png.depth {
1 or 2 or 4 or 8 or 16 =>
;
* =>
return (nil, "invalid depth");
}
png.colortype = get_byte(fd, chunk.crc_state);
okcombo : int;
case png.colortype {
0 =>
okcombo = 1;
raw.nchans = 1;
raw.chandesc = RImagefile->CY;
png.alpha = 0;
2 =>
okcombo = (png.depth == 8 || png.depth == 16);
raw.nchans = 3;
raw.chandesc = RImagefile->CRGB;
png.alpha = 0;
3 =>
okcombo = (png.depth != 16);
raw.nchans = 1;
raw.chandesc = RImagefile->CRGB1;
png.alpha = 0;
4 =>
okcombo = (png.depth == 8 || png.depth == 16);
raw.nchans = 1;
raw.chandesc = RImagefile->CY;
png.alpha = 1;
6 =>
okcombo = (png.depth == 8 || png.depth == 16);
raw.nchans = 3;
raw.chandesc = RImagefile->CRGB;
png.alpha = 1;
* =>
return (nil, "invalid colortype");
}
if (!okcombo)
return (nil, "invalid depth/colortype combination");
png.compressionmethod = get_byte(fd, chunk.crc_state);
if (png.compressionmethod != 0)
return (nil, "invalid compression method " + string png.compressionmethod);
png.filtermethod = get_byte(fd, chunk.crc_state);
if (png.filtermethod != 0)
return (nil, "invalid filter method");
png.interlacemethod = get_byte(fd, chunk.crc_state);
if (png.interlacemethod != 0 && png.interlacemethod != 1)
return (nil, "invalid interlace method");
if(0)
sys->print("width %d height %d depth %d colortype %d interlace %d\n",
raw.r.max.x, raw.r.max.y, png.depth, png.colortype, png.interlacemethod);
if (!get_crc_and_check(fd, chunk))
return (nil, "invalid CRC");
# Stash some detail in raw
raw.r.min = Point(0, 0);
raw.transp = 0;
raw.chans = array[raw.nchans] of array of byte;
{
for (r:= 0; r < raw.nchans; r++)
raw.chans[r] = array[raw.r.max.x * raw.r.max.y] of byte;
}
# Get the next chunk
seenPLTE := 0;
seenIDAT := 0;
seenLastIDAT := 0;
inflateFinished := 0;
seenIEND := 0;
seentRNS := 0;
rq: chan of ref Filter->Rq;
png.error = nil;
rq = nil;
while (png.error == nil) {
if (!get_chunk_header(fd, chunk)) {
if (!seenIEND)
png.error = "duff header";
break;
}
if (seenIEND) {
png.error = "rubbish at eof";
break;
}
case (chunk.typ) {
"IEND" =>
seenIEND = 1;
"PLTE" =>
if (seenPLTE) {
png.error = "too many PLTEs";
break;
}
if (seentRNS) {
png.error = "tRNS before PLTE";
break;
}
if (seenIDAT) {
png.error = "PLTE too late";
break;
}
if (chunk.size % 3 || chunk.size < 1 * 3 || chunk.size > 256 * 3) {
png.error = "PLTE strange size";
break;
}
if (png.colortype == 0 || png.colortype == 4) {
png.error = "superfluous PLTE";
break;
}
raw.cmap = array[256 * 3] of byte;
png.PLTEsize = chunk.size / 3;
if (!get_bytes(fd, chunk.crc_state, raw.cmap, chunk.size)) {
png.error = "eof in PLTE";
break;
}
# {
# x: int;
# sys->print("Palette:\n");
# for (x = 0; x < chunk.size; x += 3)
# sys->print("%3d: (%3d, %3d, %3d)\n",
# x / 3, int raw.cmap[x], int raw.cmap[x + 1], int raw.cmap[x + 2]);
# }
seenPLTE = 1;
"tRNS" =>
if (seenIDAT) {
png.error = "tRNS too late";
break;
}
case png.colortype {
0 =>
if (chunk.size != 2) {
png.error = "tRNS wrong size";
break;
}
level := get_ushort(fd, chunk.crc_state);
if (level < 0) {
png.error = "eof in tRNS";
break;
}
if (png.depth != 16) {
raw.transp = 1;
raw.trindex = byte level;
}
2 =>
# a legitimate coding, but we can't use the information
if (!skip_bytes(fd, chunk.crc_state, chunk.size))
png.error = "eof in skipped tRNS chunk";
break;
3 =>
if (!seenPLTE) {
png.error = "tRNS too early";
break;
}
if (chunk.size > png.PLTEsize) {
png.error = "tRNS too big";
break;
}
png.tRNS = array[png.PLTEsize] of byte;
for (x := chunk.size; x < png.PLTEsize; x++)
png.tRNS[x] = byte 255;
if (!get_bytes(fd, chunk.crc_state, png.tRNS, chunk.size)) {
png.error = "eof in tRNS";
break;
}
# {
# sys->print("tRNS:\n");
# for (x = 0; x < chunk.size; x++)
# sys->print("%3d: (%3d)\n", x, int png.tRNS[x]);
# }
if (png.error == nil) {
# analyse the tRNS chunk to see if it contains a single transparent index
# translucent entries are treated as opaque
for (x = 0; x < chunk.size; x++)
if (png.tRNS[x] == byte 0) {
raw.trindex = byte x;
if (raw.transp) {
raw.transp = 0;
break;
}
raw.transp = 1;
}
# if (raw.transp)
# sys->print("selected index %d\n", int raw.trindex);
}
4 or 6 =>
png.error = "tRNS invalid when alpha present";
}
seentRNS = 1;
"IDAT" =>
if (seenLastIDAT) {
png.error = "non contiguous IDATs";
break;
}
if (inflateFinished) {
png.error = "too many IDATs";
break;
}
remaining := 0;
if (!seenIDAT) {
# open channel to inflate filter
if (!processdatainit(png, raw))
break;
rq = inflate->start(nil);
skip_bytes(fd, chunk.crc_state, 2);
remaining = chunk.size - 2;
}
else
remaining = chunk.size;
while (remaining && png.error == nil) {
pick m := <- rq {
Fill =>
# sys->print("Fill(%d) remaining %d\n", len m.buf, remaining);
toget := len m.buf;
if (toget > remaining)
toget = remaining;
if (!get_bytes(fd, chunk.crc_state, m.buf, toget)) {
m.reply <-= -1;
png.error = "eof during IDAT";
break;
}
m.reply <-= toget;
remaining -= toget;
Result =>
# sys->print("Result(%d)\n", len m.buf);
m.reply <-= 0;
processdata(png, raw, m.buf);
Info =>
# sys->print("Info(%s)\n", m.msg);
Finished =>
inflateFinished = 1;
# sys->print("Finished\n");
Error =>
return (nil, "inflate error\n");
}
}
seenIDAT = 1;
* =>
# skip the blighter
if (!skip_bytes(fd, chunk.crc_state, chunk.size))
png.error = "eof in skipped chunk";
}
if (png.error != nil)
break;
if (!get_crc_and_check(fd, chunk))
return (nil, "invalid CRC");
if (chunk.typ != "IDAT" && seenIDAT)
seenLastIDAT = 1;
}
# can only get here if IEND was last chunk, or png.error set
if (png.error == nil && !seenIDAT) {
png.error = "no IDAT!";
inflateFinished = 1;
}
while (rq != nil && !inflateFinished) {
pick m := <-rq {
Fill =>
# sys->print("Fill(%d)\n", len m.buf);
png.error = "eof in zlib stream";
m.reply <-= -1;
inflateFinished = 1;
Result =>
# sys->print("Result(%d)\n", len m.buf);
if (png.error != nil) {
m.reply <-= -1;
inflateFinished = 1;
}
else {
m.reply <-= 0;
processdata(png, raw, m.buf);
}
Info =>
# sys->print("Info(%s)\n", m.msg);
Finished =>
# sys->print("Finished\n");
inflateFinished = 1;
break;
Error =>
png.error = "inflate error\n";
inflateFinished = 1;
}
}
if (png.error == nil && !png.done)
png.error = "insufficient data";
return (raw, png.error);
}
phase2stepping(phase: int): (int, int, int, int)
{
case phase {
0 =>
return (0, 1, 0, 1);
1 =>
return (0, 8, 0, 8);
2 =>
return (0, 8, 4, 8);
3 =>
return (4, 8, 0, 4);
4 =>
return (0, 4, 2, 4);
5 =>
return (2, 4, 0, 2);
6 =>
return (0, 2, 1, 2);
7 =>
return (1, 2, 0, 1);
* =>
return (-1, -1, -1, -1);
}
}
processdatainitphase(png: ref Png, raw: ref Rawimage)
{
(png.row, png.rowstep, png.colstart, png.colstep) = phase2stepping(png.phase);
if (raw.r.max.x > png.colstart)
png.phasecols = (raw.r.max.x - png.colstart + png.colstep - 1) / png.colstep;
else
png.phasecols = 0;
if (raw.r.max.y > png.row)
png.phaserows = (raw.r.max.y - png.row + png.rowstep - 1) / png.rowstep;
else
png.phaserows = 0;
png.rowsize = png.phasecols * (raw.nchans + png.alpha) * png.depth;
png.rowsize = (png.rowsize + 7) / 8;
png.rowsize++; # for the filter byte
png.rowbytessofar = 0;
png.thisrow = array[png.rowsize] of byte;
png.lastrow = array[png.rowsize] of byte;
# sys->print("init phase %d: r (%d, %d, %d) c (%d, %d, %d) (%d)\n",
# png.phase, png.row, png.rowstep, png.phaserows,
# png.colstart, png.colstep, png.phasecols, png.rowsize);
}
processdatainit(png: ref Png, raw: ref Rawimage): int
{
if (raw.nchans != 1&& raw.nchans != 3) {
png.error = "only 1 or 3 channels supported";
return 0;
}
# if (png.interlacemethod != 0) {
# png.error = "only progressive supported";
# return 0;
# }
if (png.colortype == 3 && raw.cmap == nil) {
png.error = "PLTE chunk missing";
return 0;
}
png.done = 0;
png.filterbpp = (png.depth * (raw.nchans + png.alpha) + 7) / 8;
png.phase = png.interlacemethod;
processdatainitphase(png, raw);
return 1;
}
upconvert(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
{
b: byte;
bits := pixels * bpp;
lim := bits / 8;
mask := byte ((1 << bpp) - 1);
outx := 0;
inx := 0;
for (x := 0; x < lim; x++) {
b = in[inx];
for (s := 8 - bpp; s >= 0; s -= bpp) {
pixel := (b >> s) & mask;
ucp := pixel;
for (y := bpp; y < 8; y += bpp)
ucp |= pixel << y;
out[outx] = ucp;
outx += outstride;
}
inx++;
}
residue := (bits % 8) / bpp;
if (residue) {
b = in[inx];
for (s := 8 - bpp; s >= 0; s -= bpp) {
pixel := (b >> s) & mask;
ucp := pixel;
for (y := bpp; y < 8; y += bpp)
ucp |= pixel << y;
out[outx] = ucp;
outx += outstride;
if (--residue <= 0)
break;
}
}
}
# expand (1 or 2 or 4) bit to 8 bit without scaling (for palletized stuff)
expand(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
{
b: byte;
bits := pixels * bpp;
lim := bits / 8;
mask := byte ((1 << bpp) - 1);
outx := 0;
inx := 0;
for (x := 0; x < lim; x++) {
b = in[inx];
for (s := 8 - bpp; s >= 0; s -= bpp) {
out[outx] = (b >> s) & mask;
outx += outstride;
}
inx++;
}
residue := (bits % 8) / bpp;
if (residue) {
b = in[inx];
for (s := 8 - bpp; s >= 0; s -= bpp) {
out[outx] = (b >> s) & mask;
outx += outstride;
if (--residue <= 0)
break;
}
}
}
copybytes(out: array of byte, outstride: int, in: array of byte, instride: int, pixels: int)
{
inx := 0;
outx := 0;
for (x := 0; x < pixels; x++) {
out[outx] = in[inx];
inx += instride;
outx += outstride;
}
}
outputrow(png: ref Png, raw: ref Rawimage, row: array of byte)
{
offset := png.row * raw.r.max.x;
case raw.nchans {
1 =>
case (png.depth) {
* =>
png.error = "depth not supported";
return;
1 or 2 or 4 =>
if (raw.chandesc == RImagefile->CRGB1)
expand(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
else
upconvert(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
8 or 16 =>
# might have an Alpha channel to ignore!
stride := (png.alpha + 1) * png.depth / 8;
copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
}
3 =>
case (png.depth) {
* =>
png.error = "depth not supported (2)";
return;
8 or 16 =>
# split rgb into three channels
bytespc := png.depth / 8;
stride := (3 + png.alpha) * bytespc;
copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
copybytes(raw.chans[1][offset + png.colstart:], png.colstep, row[bytespc:], stride, png.phasecols);
copybytes(raw.chans[2][offset + png.colstart:], png.colstep, row[bytespc * 2:], stride, png.phasecols);
}
}
}
filtersub(png: ref Png)
{
subx := 1;
for (x := int png.filterbpp + 1; x < png.rowsize; x++) {
png.thisrow[x] += png.thisrow[subx];
subx++;
}
}
filterup(png: ref Png)
{
if (png.row == 0)
return;
for (x := 1; x < png.rowsize; x++)
png.thisrow[x] += png.lastrow[x];
}
filteraverage(png: ref Png)
{
for (x := 1; x < png.rowsize; x++) {
a: int;
if (x > png.filterbpp)
a = int png.thisrow[x - png.filterbpp];
else
a = 0;
if (png.row != 0)
a += int png.lastrow[x];
png.thisrow[x] += byte (a / 2);
}
}
filterpaeth(png: ref Png)
{
a, b, c: byte;
p, pa, pb, pc: int;
for (x := 1; x < png.rowsize; x++) {
if (x > png.filterbpp)
a = png.thisrow[x - png.filterbpp];
else
a = byte 0;
if (png.row == 0) {
b = byte 0;
c = byte 0;
} else {
b = png.lastrow[x];
if (x > png.filterbpp)
c = png.lastrow[x - png.filterbpp];
else
c = byte 0;
}
p = int a + int b - int c;
pa = p - int a;
if (pa < 0)
pa = -pa;
pb = p - int b;
if (pb < 0)
pb = -pb;
pc = p - int c;
if (pc < 0)
pc = -pc;
if (pa <= pb && pa <= pc)
png.thisrow[x] += a;
else if (pb <= pc)
png.thisrow[x] += b;
else
png.thisrow[x] += c;
}
}
phaseendcheck(png: ref Png, raw: ref Rawimage): int
{
if (png.row >= raw.r.max.y || png.rowsize <= 1) {
# this phase is over
if (png.phase == 0) {
png.done = 1;
}
else {
png.phase++;
if (png.phase > 7)
png.done = 1;
else
processdatainitphase(png, raw);
}
return 1;
}
return 0;
}
processdata(png: ref Png, raw: ref Rawimage, buf: array of byte)
{
#sys->print("processdata(%d)\n", len buf);
if (png.error != nil)
return;
i := 0;
while (i < len buf) {
if (png.done) {
png.error = "too much data";
return;
}
if (phaseendcheck(png, raw))
continue;
tocopy := (png.rowsize - png.rowbytessofar);
if (tocopy > (len buf - i))
tocopy = len buf - i;
png.thisrow[png.rowbytessofar :] = buf[i : i + tocopy];
i += tocopy;
png.rowbytessofar += tocopy;
if (png.rowbytessofar >= png.rowsize) {
# a new row has arrived
# apply filter here
#sys->print("phase %d row %d\n", png.phase, png.row);
case int png.thisrow[0] {
0 =>
;
1 =>
filtersub(png);
2 =>
filterup(png);
3 =>
filteraverage(png);
4 =>
filterpaeth(png);
* =>
# sys->print("implement filter method %d\n", int png.thisrow[0]);
png.error = "filter method unsupported";
return;
}
# output row
if (png.row >= raw.r.max.y) {
png.error = "too much data";
return;
}
outputrow(png, raw, png.thisrow[1 :]);
png.row += png.rowstep;
save := png.lastrow;
png.lastrow = png.thisrow;
png.thisrow = save;
png.rowbytessofar = 0;
}
}
phaseendcheck(png, raw);
}
get_signature(fd: ref Iobuf): int
{
sig := array[8] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 };
x: int;
for (x = 0; x < 8; x++)
if (fd.getb() != int sig[x])
return 0;
return 1;
}
get_bytes(fd: ref Iobuf, crc_state: ref CRCstate, buf: array of byte, n: int): int
{
if (buf == nil) {
fd.seek(big n, bufio->SEEKRELA);
return 1;
}
if (fd.read(buf, n) != n)
return 0;
if (crc_state != nil)
crc->crc(crc_state, buf, n);
return 1;
}
skip_bytes(fd: ref Iobuf, crc_state: ref CRCstate, n: int): int
{
buf := array[1024] of byte;
while (n) {
thistime: int = 1024;
if (thistime > n)
thistime = n;
if (!get_bytes(fd, crc_state, buf, thistime))
return 0;
n -= thistime;
}
return 1;
}
get_4(fd: ref Iobuf, crc_state: ref CRCstate, signed: int): (int, int)
{
buf := array[4] of byte;
if (!get_bytes(fd, crc_state, buf, 4))
return (0, 0);
if (signed && int buf[0] & 16r80)
return (0, 0);
r:int = (int buf[0] << 24) | (int buf[1] << 16) | (int buf[2] << 8) | (int buf[3]);
# sys->print("got int %d\n", r);
return (1, r);
}
get_int(fd: ref Iobuf, crc_state: ref CRCstate): int
{
ok, r: int;
(ok, r) = get_4(fd, crc_state, 1);
if (ok)
return r;
return -1;
}
get_ushort(fd: ref Iobuf, crc_state: ref CRCstate): int
{
buf := array[2] of byte;
if (!get_bytes(fd, crc_state, buf, 2))
return -1;
return (int buf[0] << 8) | int buf[1];
}
get_crc_and_check(fd: ref Iobuf, chunk: ref Chunk): int
{
crc, ok: int;
(ok, crc) = get_4(fd, nil, 0);
if (!ok)
return 0;
# sys->print("crc: computed %.8ux expected %.8ux\n", chunk.crc_state.crc, crc);
if (chunk.crc_state.crc != crc)
return 1;
return 1;
}
get_byte(fd: ref Iobuf, crc_state: ref CRCstate): int
{
buf := array[1] of byte;
if (!get_bytes(fd, crc_state, buf, 1))
return -1;
# sys->print("got byte %d\n", int buf[0]);
return int buf[0];
}
get_type(fd: ref Iobuf, crc_state: ref CRCstate): string
{
x: int;
buf := array[4] of byte;
if (!get_bytes(fd, crc_state, buf, 4))
return nil;
for (x = 0; x < 4; x++) {
c: int;
c = int buf[x];
if (c == bufio->EOF || (c < 65 || c > 90 && c < 97) || c > 122)
return nil;
}
return string buf;
}
get_chunk_header(fd: ref Iobuf, chunk: ref Chunk): int
{
chunk.size = get_int(fd, nil);
if (chunk.size < 0)
return 0;
crc->reset(chunk.crc_state);
chunk.typ = get_type(fd, chunk.crc_state);
if (chunk.typ == nil)
return 0;
# sys->print("%s(%d)\n", chunk.typ, chunk.size);
return 1;
}