shithub: fork

Download patch

ref: 11e7bab1406c7f39c9b195367ecb5e36ddf3d472
parent: 550e01b368c3256a0d4558995dfbfccb13b7c362
author: qwx <qwx@sciops.net>
date: Sun Sep 17 08:57:50 EDT 2023

add png(readpng.c): raise memory limit and old wip mTRS chunks for indexed rgb24

--- /dev/null
+++ b/sys/src/cmd/jpg/readpng.c
@@ -1,0 +1,555 @@
+#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 = 64*1024*1024,
+
+	/* filtering algorithms */
+	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 ZlibR ZlibR;
+typedef struct ZlibW ZlibW;
+
+struct ZlibW
+{
+	uchar *data;		/* Rawimage data */
+	int ndata;
+	int noutchan;
+	int chandesc;
+	int nchan;
+
+	uchar *scan;		/* new scanline */
+	uchar *lastscan;	/* previous scan line */
+	int scanlen;		/* scan line length */
+	int scanpos;		/* scan position */
+
+	int dx;			/* width of image */
+	int dy;			/* height of image */
+	int bpc;			/* bits per channel (per pixel) */
+	int y;				/* current scan line */
+	int pass;			/* adam7 pass#; 0 means no adam7 */
+	uchar palette[3*256];	/* color palette */
+	int palsize;		/* number of palette entries */
+	uchar apalette[256];	/* optional alpha values for palette */
+	int hasapal;
+};
+
+struct ZlibR
+{
+	Biobuf *io;		/* input buffer */
+	uchar *buf;		/* malloc'ed staging buffer */
+	uchar *p;			/* next byte to decompress */
+	uchar *e;			/* end of buffer */
+	ZlibW *w;
+};
+
+static ulong *crctab;
+static uchar PNGmagic[] = { 137, 'P', 'N', 'G', '\r', '\n', 26, '\n'};
+
+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 = mallocz(n, clear);
+	if(p == nil)
+		sysfatal("malloc: %r");
+	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->p >= z->e){
+	Again:
+		z->p = z->buf;
+		z->e = z->p;
+		n = getchunk(z->io, type, z->p, IDATSIZE);
+		if(n < 0 || strcmp(type, "IEND") == 0)
+			return -1;
+		z->e = z->p + 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->p, n);
+			z->w->palsize = 256;
+			goto Again;
+		}
+		if(type[0] & PropertyBit){
+			/* FIXME: non-3 formats are unhandled */
+			if(strcmp(type,"tRNS"))
+				goto Again;  /* skip auxiliary chunks fornow */
+			if(z->w->chandesc != CRGBA32 || z->w->nchan != 1)
+				goto Again;	/* unimplemented */
+			if(n > z->w->palsize)
+				sysfatal("invalid tRNS chunk len %d", n);
+			memcpy(z->w->apalette, z->p, n);
+			z->w->hasapal = 1;
+			goto Again;
+		}
+		if(strcmp(type,"IDAT")){
+			sysfatal("unrecognized mandatory chunk %s", type);
+			goto Again;
+		}
+	}
+	return *z->p++;
+}
+
+static uchar 
+paeth(uchar a, uchar b, uchar c)
+{
+	int p, pa, pb, pc;
+
+	p = a + b - c;
+	pa = abs(p - a);
+	pb = abs(p - b);
+	pc = abs(p - 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){
+	default:
+		fprint(2, "unknown filtering scheme %d\n", 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;
+	}
+}
+
+struct {
+	int x;
+	int y;
+	int dx;
+	int dy;
+} 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)
+{
+	int chan, i, j, nbit, off, val;
+	uchar pixel[4], *p, *w;
+
+	unfilter(z->scan[0], z->scan+1, z->lastscan+1, len-1, (z->nchan*z->bpc+7)/8);
+
+	/*
+	 * loop over raw bits extracting pixel values and converting to 8-bit
+	 */
+	nbit = 0;
+	chan = 0;
+	val = 0;
+	off = z->y*z->dx + adam7[z->pass].x;
+	w = z->data + z->noutchan*off;
+	p = z->scan+1;	/* skip alg byte */
+	len--;
+	for(i=0; i<len*8; i++){
+		val <<= 1;
+		if(p[i>>3] & (1<<(7-(i&7))))
+			val++;
+		if(++nbit == z->bpc){
+			/* finished the value */
+			pixel[chan++] = (val*255)/((1<<z->bpc)-1);
+			val = 0;
+			nbit = 0;
+			if(chan == z->nchan){
+				/* finished the pixel */
+				if(off < z->dx*z->dy){
+					if(z->nchan < 3 && z->palsize){
+						j = pixel[0];
+						if(z->bpc < 8)
+							j >>= 8-z->bpc;
+						if(j >= z->palsize)
+							sysfatal("index %d >= palette size %d", j, z->palsize);
+						pixel[3] = z->hasapal ?  z->apalette[j] : pixel[1];	/* alpha */
+						pixel[0] = z->palette[3*j];
+						pixel[1] = z->palette[3*j+1];
+						pixel[2] = z->palette[3*j+2];
+					}
+					switch(z->chandesc){
+					case CYA16:
+					//	print("%.2x%.2x ", pixel[0], pixel[1]);
+						*w++ = pixel[1];
+						*w++ += (pixel[0]*pixel[1])/255;
+						break;
+					case CRGBA32:
+					//	print("%.2x%.2x%.2x%.2x ", pixel[0], pixel[1], pixel[2], pixel[3]);
+						*w++ += pixel[3];
+						if(z->hasapal){
+							*w++ = pixel[2];
+							*w++ = pixel[1];
+							*w++ = pixel[0];
+							break;
+						}
+						*w++ += (pixel[2]*pixel[3])/255;
+						*w++ += (pixel[1]*pixel[3])/255;
+						*w++ += (pixel[0]*pixel[3])/255;
+						break;
+					case CRGB24:
+						*w++ = pixel[2];
+						*w++ = pixel[1];
+					case CY:
+						*w++ = pixel[0];
+						break;
+					}
+					w += (adam7[z->pass].dx-1)*z->noutchan;
+				}
+				off += adam7[z->pass].dx;
+				if(off >= (z->y+1)*z->dx){
+					/* finished the line */
+					return;
+				}
+				chan = 0;
+			}
+		}
+	}
+	sysfatal("scan line too short");
+}
+
+static int
+scanbytes(ZlibW *z)
+{
+	int bits, n, adx, dx;
+
+	if(adam7[z->pass].y >= z->dy || adam7[z->pass].x >= z->dx)
+		return 0;
+	adx = adam7[z->pass].dx;
+	dx = z->dx - adam7[z->pass].x;
+	if(dx <= 0)
+		n = 1;
+	else
+		n = (dx+adx-1)/adx;
+	if(n != 1 + (z->dx - (adam7[z->pass].x+1)) / adam7[z->pass].dx){
+		fprint(2, "%d/%d != 1+(%d-1)/%d = %d\n",
+			z->dx - adam7[z->pass].x - 1 + adx, adx,
+			z->dx - (adam7[z->pass].x+1), adam7[z->pass].dx,
+			1 + (z->dx - (adam7[z->pass].x+1)) / adam7[z->pass].dx);
+	}
+	bits = n*z->bpc*z->nchan;
+	return 1 + (bits+7)/8;
+}
+
+static int
+nextpass(ZlibW *z)
+{
+	int len;
+
+	memset(z->lastscan, 0, z->scanlen);
+	do{
+		z->pass = (z->pass+1)%8;
+		z->y = adam7[z->pass].y;
+		len = scanbytes(z);
+	}while(len < 2);
+	return len;
+}
+
+static int
+zwrite(void *vz, void *vbuf, int n)
+{
+	int oldn, m, len;
+	uchar *buf, *t;
+	ZlibW *z;
+
+	z = vz;
+	buf = vbuf;
+	oldn = n;
+
+	len = scanbytes(z);
+	if(len < 2)
+		len = nextpass(z);
+
+	while(n > 0){
+		m = len - z->scanpos;
+		if(m > n){
+			/* save final partial line */
+			memmove(z->scan+z->scanpos, buf, n);
+			z->scanpos += n;
+			break;
+		}
+
+		/* fill line */
+		memmove(z->scan+z->scanpos, buf, m);
+		buf += m;
+		n -= m;
+
+		/* process line */
+		scan(len, z);
+		t = z->scan;
+		z->scan = z->lastscan;
+		z->lastscan = t;
+
+		z->scanpos = 0;
+		z->y += adam7[z->pass].dy;
+		if(z->y >= z->dy)
+			len = nextpass(z);
+	}
+	return oldn;
+}
+
+static Rawimage*
+readslave(Biobuf *b)
+{
+	char type[5];
+	int bpc, colorfmt, dx, dy, err, n, nchan, nout, useadam7;
+	uchar *buf, *h;
+	Rawimage *image;
+	ZlibR zr;
+	ZlibW zw;
+
+	buf = pngmalloc(IDATSIZE, 0);
+	if(Bread(b, buf, sizeof PNGmagic) != sizeof PNGmagic
+	|| 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;
+	dx = get4(h);
+	h += 4;
+	dy = get4(h);
+	h += 4;
+	if(dx <= 0 || dy <= 0)
+		sysfatal("impossible image size %dx%d", dx, dy);
+	if(debug)
+		fprint(2, "readpng %dx%d\n", dx, dy);
+
+	bpc = *h++;
+	colorfmt = *h++;
+	nchan = 0;
+	if(*h++ != 0)
+		sysfatal("only deflate supported for now [%d]", h[-1]);
+	if(*h++ != FilterNone)
+		sysfatal("only FilterNone supported for now [%d]", h[-1]);
+	useadam7 = *h++;
+	USED(h);
+
+	image = pngmalloc(sizeof(Rawimage), 1);
+	image->r = Rect(0, 0, dx, dy);
+	nout = 0;
+	switch(colorfmt){
+	case 0:	/* grey */
+		if(bpc != 1 && bpc != 2 && bpc != 4 && bpc != 8 && bpc != 16)
+			sysfatal("invalid greyscale bpc %d", bpc);
+		image->nchans = 1;
+		image->chandesc = CY;
+		nout = 1;
+		nchan = 1;
+		break;
+	case 2:	/* rgb */
+		if(bpc != 8 && bpc != 16)
+			sysfatal("invalid rgb bpc %d", bpc);
+		image->nchans = 1;
+		image->chandesc = CRGB24;
+		nout = 3;
+		nchan = 3;
+		break;
+	case 3: /* indexed rgb with PLTE */
+		if(bpc != 1 && bpc != 2 && bpc != 4 && bpc != 8)
+			sysfatal("invalid indexed rgb bpc %d", bpc);
+		/* FIXME: conditially set CRGBA32? maybe pngmalloc directly to zw buffer, resize it
+		 * as needed, and set it before returning? or perhaps alloc maximal size,
+		 * and if there's no valid tRNS chunk, shrink it before returning image;
+		 * so, alloc max, but use minimal params before inflating... */
+		/* FIXME: support other formats */
+		image->nchans = 1;
+		image->chandesc = CRGBA32;
+		nout = 4;
+		nchan = 1;
+		break;
+	case 4:	/* grey+alpha */
+		if(bpc != 8 && bpc != 16)
+			sysfatal("invalid grey+alpha bpc %d", bpc);
+		image->nchans = 1;
+		image->chandesc = CYA16;
+		nout = 2;
+		nchan = 2;
+		break;
+	case 6:	/* rgb+alpha */
+		if(bpc != 8 && bpc != 16)
+			sysfatal("invalid rgb+alpha bpc %d", bpc);
+		image->nchans = 1;
+		image->chandesc = CRGBA32;
+		nout = 4;
+		nchan = 4;
+		break;
+	default:
+		sysfatal("unsupported color scheme %d", h[-1]);
+	}
+	image->chanlen = dx*dy*nout;
+	image->chans[0] = pngmalloc(image->chanlen, 0);
+	memset(image->chans[0], 0, image->chanlen);
+
+	memset(&zr, 0, sizeof zr);
+	zr.w = &zw;
+	zr.io = b;
+	zr.buf = buf;
+
+	memset(&zw, 0, sizeof zw);
+	if(useadam7)
+		zw.pass = 1;
+	zw.data = image->chans[0];
+	zw.ndata = image->chanlen;
+	zw.chandesc = image->chandesc;
+	zw.noutchan = nout;
+	memset(zw.apalette, 0xff, sizeof zw.apalette);
+
+	zw.dx = dx;
+	zw.dy = dy;
+	zw.scanlen = (nchan*dx*bpc+7)/8+1;
+	zw.scan = pngmalloc(zw.scanlen, 1);
+	zw.lastscan = pngmalloc(zw.scanlen, 1);
+	zw.nchan = nchan;
+	zw.bpc = bpc;
+
+	err = inflatezlib(&zw, zwrite, &zr, zread);
+
+	if(err)
+		sysfatal("inflatezlib %s\n", flateerr(err));
+
+	free(buf);
+	free(zw.scan);
+	free(zw.lastscan);
+	return image;
+}
+
+Rawimage**
+Breadpng(Biobuf *b, int colorspace)
+{
+	Rawimage **array, *r;
+
+	if(colorspace != CRGB){
+		werrstr("ReadPNG: unknown color space %d", colorspace);
+		return nil;
+	}
+	pnginit();
+	array = malloc(2*sizeof(*array));
+	if(array==nil)
+		return nil;
+	r = readslave(b);
+	array[0] = r;
+	array[1] = nil;
+	return array;
+}
+
+Rawimage**
+readpng(int fd, int colorspace)
+{
+	Biobuf b;
+	Rawimage **a;
+
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+	a = Breadpng(&b, colorspace);
+	Bterm(&b);
+	return a;
+}