Plan 9 from Bell Labs’s /usr/web/sources/patch/applied/pngupdate/readpng.c.new

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.



#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
#include <flate.h>
#include <draw.h>
#include "imagefile.h"

int debug;

enum{  IDATSIZE=1000000,
	/* filtering algorithms, supposedly increase compression */
	FilterNone =	0,	/* new[x][y] = buf[x][y] */
	FilterSub	=	1,	/* new[x][y] = buf[x][y] + new[x-1][y] */ 
	FilterUp	=	2,	/* new[x][y] = buf[x][y] + new[x][y-1] */ 
	FilterAvg	=	3,	/* new[x][y] = buf[x][y] + (new[x-1][y]+new[x][y-1])/2 */ 
	FilterPaeth=	4,	/* new[x][y] = buf[x][y] + paeth(new[x-1][y],new[x][y-1],new[x-1][y-1]) */
	FilterLast	=	5,
	PropertyBit =	1<<5,
};


typedef struct ZlibW{
	uchar *ch[4]; // Rawimage channels
	int nch;		// number of input chans
	int	chl;		// number of bytes allocated to ch[x]

	uchar *scan;	// new scanline
	uchar *pscan;	// previous scanline
	int scanl;		// scan len
	int scanp;		// scan pos

	int ncol;		// pixels per scanline
	int bpp;		// (bits per pixel) per channel
	int palsize;	// # of color entries
	int row;		// current scanline number
	int nrow;		// number of rows in image
	int pass;		// adam7 pass#, 0 means no adam7
	uchar palette[3*256];
} ZlibW;

typedef struct ZlibR{
	Biobuf *bi;
	uchar *buf;
	uchar *b;	// next byte to decompress
	uchar *e;	// past end of buf
	ZlibW *w;
} ZlibR;

static ulong *crctab;
static uchar PNGmagic[] = {137,80,78,71,13,10,26,10};
static char readerr[] = "ReadPNG: read error: %r";
static char memerr[] = "ReadPNG: malloc failed: %r";

static ulong
get4(uchar *a)
{
	return (a[0]<<24) | (a[1]<<16) | (a[2]<<8) | a[3];
}

static
void
pnginit(void)
{
	static int inited;

	if(inited)
		return;
	inited = 1;
	crctab = mkcrctab(0xedb88320);
	if(crctab == nil)
		sysfatal("mkcrctab error");
	inflateinit();
}

static
void*
pngmalloc(ulong n, int clear)
{
	void *p;

	p = malloc(n);
	if(p == nil)
		sysfatal(memerr);
	if(clear)
		memset(p, 0, n);
	return p;
}

static int
getchunk(Biobuf *b, char *type, uchar *d, int m)
{
	uchar buf[8];
	ulong crc = 0, crc2;
	int n, nr;

	if(Bread(b, buf, 8) != 8)
		return -1;
	n = get4(buf);
	memmove(type, buf+4, 4);
	type[4] = 0;
	if(n > m)
		sysfatal("getchunk needed %d, had %d", n, m);
	nr = Bread(b, d, n);
	if(nr != n)
		sysfatal("getchunk read %d, expected %d", nr, n);
	crc = blockcrc(crctab, crc, type, 4);
	crc = blockcrc(crctab, crc, d, n);
	if(Bread(b, buf, 4) != 4)
		sysfatal("getchunk tlr failed");
	crc2 = get4(buf);
	if(crc != crc2)
		sysfatal("getchunk crc failed");
	return n;
}

static int
zread(void *va)
{
	ZlibR *z = va;
	char type[5];
	int n;

	if(z->b >= z->e){
refill_buffer:
		z->b = z->buf;
		n = getchunk(z->bi, type, z->b, IDATSIZE);
		if(n < 0 || strcmp(type, "IEND") == 0)
			return -1;
		z->e = z->b + n;
		if(!strcmp(type,"PLTE")) {
			if (n < 3 || n > 3*256 || n%3)
				sysfatal("invalid PLTE chunk len %d", n);
			memcpy(z->w->palette, z->b, n);
			z->w->palsize = n/3;
			goto refill_buffer;
		}
		if(type[0] & PropertyBit)
			goto refill_buffer;  /* skip auxiliary chunks for now */
		if(strcmp(type,"IDAT")) {
			sysfatal("unrecognized mandatory chunk %s", type);
			goto refill_buffer;
		}
	}
	return *z->b++;
}

static uchar 
paeth(uchar a, uchar b, uchar c)
{
	int p, pa, pb, pc;
	
	p = (int)a + (int)b - (int)c;
	pa = abs(p - (int)a);
	pb = abs(p - (int)b);
	pc = abs(p - (int)c);

	if(pa <= pb && pa <= pc)
		return a;
	else if(pb <= pc)
		return b;
	return c;
}

static void
unfilter(int alg, uchar *buf, uchar *up, int len, int bypp)
{
	int i;
	switch(alg){
	case FilterNone:
		break;

	case FilterSub:
		for (i = bypp; i < len; ++i)
			buf[i] += buf[i-bypp];
		break;

	case FilterUp:
		for (i = 0; i < len; ++i)
			buf[i] += up[i];
		break;

	case FilterAvg:
		for (i = 0; i < bypp; ++i)
			buf[i] += (0+up[i])/2;
		for (; i < len; ++i)
			buf[i] += (buf[i-bypp]+up[i])/2;
		break;

	case FilterPaeth:
		for (i = 0; i < bypp; ++i)
			buf[i] += paeth(0, up[i], 0);
		for (; i < len; ++i)
			buf[i] += paeth(buf[i-bypp], up[i], up[i-bypp]);
		break;
	default:
		sysfatal("unknown filtering scheme %d\n", alg);
	}
}

static void
convertpix(ZlibW *z, uchar *pixel, uchar *r, uchar *g, uchar *b, uchar *a)
{
	int off;
	switch (z->nch) {
	case 1:	/* gray or indexed */
		pixel[1] = 255;	/* opaque */
	case 2:	/* gray+alpha */
		if (z->bpp < 8)
			pixel[0] >>= 8-z->bpp;
		if (pixel[0] > z->palsize)
			sysfatal("index %d out of bounds %d", pixel[0], z->palsize);
		off = 3*pixel[0];
		*r = z->palette[off];
		*g = z->palette[off+1];
		*b = z->palette[off+2];
		*a = pixel[1];
		break;
	case 3:	/* rgb */
		pixel[3] = 255; /* opaque */
	case 4:	/* rgb+alpha */
		*r = pixel[0];
		*g = pixel[1];
		*b = pixel[2];
		*a = pixel[3];
		break;
	default:
		sysfatal("bad number of channels: %d", z->nch);
	} 
}

struct {
	int xo;
	int yo;
	int Δx;
	int Δy;
} adam7[] = {
	{0,0,1,1},	/* eve alone */
	{0,0,8,8},	/* pass 1 */
	{4,0,8,8},	/* pass 2 */
	{0,4,4,8},	/* pass 3 */
	{2,0,4,4},	/* pass 4 */
	{0,2,2,4},	/* pass 5 */
	{1,0,2,2},	/* pass 6 */
	{0,1,1,2},	/* pass 7 */
};

static void
scan(int len, ZlibW *z)
{
	uchar *p;
	int i, j, bit, n, ch, nch, pd;
	uchar cb;
	uchar pixel[4];

	p = z->scan;
	nch = z->nch;

	unfilter(p[0], p+1, z->pscan+1, len-1, (nch*z->bpp+7)/8);

	ch = 0;
	n = 0;
	cb = 128;
	pd = z->row * z->ncol;
	pd += adam7[z->pass].xo;

	for (i = 0; i < 4; ++i)
		pixel[i] = 0;

	for (i = 1; i < len; ++i)
		for (bit = 128; bit > 0; bit /= 2) {

			pixel[ch] &= ~cb;
			if (p[i] & bit)
				pixel[ch] |= cb;

			cb >>= 1;

			if (++n == z->bpp) {
				cb = 128;
				n = 0;
				ch++;
			}
			if (ch == nch) {
				if (pd < z->chl)
					convertpix(z,pixel,
						z->ch[0]+pd,
						z->ch[1]+pd,
						z->ch[2]+pd,
						z->ch[3]+pd);

				for (j = 0; j < 4; ++j)
					pixel[j] = 0;

				pd += adam7[z->pass].Δx;
				if (pd - z->row*z->ncol >= z->ncol)
					goto out;
				ch = 0;
			}
		}
out: ;
}

static int
scanbytes(ZlibW *z)
{
	int ncol, bpp;
	int xo, Δx;

	bpp = z->bpp * z->nch;
	Δx = adam7[z->pass].Δx;
	xo = adam7[z->pass].xo;
	ncol = z->ncol;

	return 1 + (((ncol-xo-1) / Δx + 1) * bpp + 7) / 8;
}

static int
nextpass(ZlibW *z)
{
		int i;
		z->pass = (z->pass+1)%8;
		z->row = adam7[z->pass].yo;
		for (i = 0; i < z->scanl; ++i)
			z->pscan[i] = 0;
		return scanbytes(z);
}

static int
zwrite(void *va, void *vb, int n)
{
	ZlibW *z = va;
	uchar *buf = vb;
	int i, j, scanl;

	j = z->scanp;

	scanl = scanbytes(z);
	while (scanl < 2)
		nextpass(z);

	for (i = 0; i < n; ++i) {
		z->scan[j++] = buf[i];
		if (j >= scanl) {
			uchar *tp;
			scan(scanl, z);
			tp = z->scan;
			z->scan = z->pscan;
			z->pscan = tp;
			z->row += adam7[z->pass].Δy;
			j = 0;
		}
		if (z->row >= z->nrow) {
			do
				scanl = nextpass(z);
			while (scanl < 2);
		}
	}
	z->scanp = j;

	return n;
}

static Rawimage*
readslave(Biobuf *b)
{
	ZlibR zr;
	ZlibW zw;
	Rawimage *image;
	char type[5];
	uchar *buf, *h;
	int k, n, nrow, ncol, err, bpp, nch;

	zr.w = &zw;

	buf = pngmalloc(IDATSIZE, 0);
	Bread(b, buf, sizeof PNGmagic);
	if(memcmp(PNGmagic, buf, sizeof PNGmagic) != 0)
		sysfatal("bad PNGmagic");

	n = getchunk(b, type, buf, IDATSIZE);
	if(n < 13 || strcmp(type,"IHDR") != 0)
		sysfatal("missing IHDR chunk");
	h = buf;
	ncol = get4(h);  h += 4;
	nrow = get4(h);  h += 4;
	if(ncol <= 0 || nrow <= 0)
		sysfatal("impossible image size nrow=%d ncol=%d", nrow, ncol);
	if(debug)
		fprint(2, "readpng nrow=%d ncol=%d\n", nrow, ncol);

	bpp = *h++;
	nch = 0;
	switch (*h++) {
	case 0:	/* grey */
		nch = 1;
		break;
	case 2:	/* rgb */
		nch = 3;
		break;
	case 3: /* indexed rgb with PLTE */
		nch = 1;
		break;
	case 4:	/* grey+alpha */
		nch = 2;
		break;
	case 6:	/* rgb+alpha */
		nch = 4;
		break;
	default:
		sysfatal("unsupported color scheme %d", h[-1]);
	}

	/* generate default palette for grayscale */
	zw.palsize = 256;
	if (nch < 3 && bpp < 9)
		zw.palsize = 1<<bpp;
	for (k = 0; k < zw.palsize; ++k) {
		zw.palette[3*k] = (k*255)/(zw.palsize-1);
		zw.palette[3*k+1] = (k*255)/(zw.palsize-1);
		zw.palette[3*k+2] = (k*255)/(zw.palsize-1);
	}

	if(*h++ != 0)
		sysfatal("only deflate supported for now [%d]", h[-1]);
	if(*h++ != FilterNone)
		sysfatal("only FilterNone supported for now [%d]", h[-1]);

	zw.pass = 0;
	if (*h == 1)	/* Adam7 interleaving */
		zw.pass = 1;

	image = pngmalloc(sizeof(Rawimage), 1);
	image->r = Rect(0, 0, ncol, nrow);
	image->cmap = nil;
	image->cmaplen = 0;
	image->chanlen = ncol*nrow;
	image->fields = 0;
	image->gifflags = 0;
	image->gifdelay = 0;
	image->giftrindex = 0;
	image->chandesc = CRGB;
	image->nchans = 3;

	zw.chl = ncol*nrow;
	for(k=0; k<4; k++)
		image->chans[k] = zw.ch[k] = pngmalloc(ncol*nrow, 1);

	zr.bi = b;
	zr.buf = buf;
	zr.b = zr.e = buf + IDATSIZE;

	zw.scanp = 0;
	zw.row = 0;
	zw.nrow = nrow;
	zw.ncol = ncol;
	zw.scanl = (nch*ncol*bpp+7)/8+1;
	zw.scan = pngmalloc(zw.scanl, 1);
	zw.pscan = pngmalloc(zw.scanl, 1);
	zw.nch = nch;
	zw.bpp = bpp;

	err = inflatezlib(&zw, zwrite, &zr, zread);

	if(err)
		sysfatal("inflatezlib %s\n", flateerr(err));

	free(image->chans[3]);
	image->chans[3] = nil;
	free(buf);
	free(zw.scan);
	free(zw.pscan);
	return image;
}

Rawimage**
Breadpng(Biobuf *b, int colorspace)
{
	Rawimage *r, **array;
	char buf[ERRMAX];

	buf[0] = '\0';
	if(colorspace != CRGB){
		errstr(buf, sizeof buf);	/* throw it away */
		werrstr("ReadPNG: unknown color space %d", colorspace);
		return nil;
	}
	pnginit();
	array = malloc(2*sizeof(*array));
	if(array==nil)
		return nil;
	errstr(buf, sizeof buf);	/* throw it away */
	r = readslave(b);
	array[0] = r;
	array[1] = nil;
	return array;
}

Rawimage**
readpng(int fd, int colorspace)
{
	Rawimage** a;
	Biobuf b;

	if(Binit(&b, fd, OREAD) < 0)
		return nil;
	a = Breadpng(&b, colorspace);
	Bterm(&b);
	return a;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].