|  | /* based on PNG 1.2 specification, July 1999  (see also rfc2083) */ | 
|  | /* Alpha is not supported yet because of lack of industry acceptance and */ | 
|  | /* because Plan9 Image uses premultiplied alpha, so png can't be lossless. */ | 
|  | /* Only 24bit color supported, because 8bit may as well use GIF. */ | 
|  |  | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <draw.h> | 
|  | #include <memdraw.h> | 
|  | #include <ctype.h> | 
|  | #include <bio.h> | 
|  | #include <flate.h> | 
|  | #include "imagefile.h" | 
|  |  | 
|  | enum{	IDATSIZE = 	20000, | 
|  | FilterNone =	0 | 
|  | }; | 
|  |  | 
|  | typedef struct ZlibR{ | 
|  | uchar *data; | 
|  | int width; | 
|  | int nrow, ncol; | 
|  | int row, col;	/* next pixel to send */ | 
|  | int pixwid; | 
|  | } ZlibR; | 
|  |  | 
|  | typedef struct ZlibW{ | 
|  | Biobuf *bo; | 
|  | uchar *buf; | 
|  | uchar *b;	/* next place to write */ | 
|  | uchar *e;	/* past end of buf */ | 
|  | } ZlibW; | 
|  |  | 
|  | static uint32 *crctab; | 
|  | static uchar PNGmagic[] = {137,80,78,71,13,10,26,10}; | 
|  |  | 
|  | static void | 
|  | put4(uchar *a, uint32 v) | 
|  | { | 
|  | a[0] = v>>24; | 
|  | a[1] = v>>16; | 
|  | a[2] = v>>8; | 
|  | a[3] = v; | 
|  | } | 
|  |  | 
|  | static void | 
|  | chunk(Biobuf *bo, char *type, uchar *d, int n) | 
|  | { | 
|  | uchar buf[4]; | 
|  | uint32 crc = 0; | 
|  |  | 
|  | if(strlen(type) != 4) | 
|  | return; | 
|  | put4(buf, n); | 
|  | Bwrite(bo, buf, 4); | 
|  | Bwrite(bo, type, 4); | 
|  | Bwrite(bo, d, n); | 
|  | crc = blockcrc(crctab, crc, type, 4); | 
|  | crc = blockcrc(crctab, crc, d, n); | 
|  | put4(buf, crc); | 
|  | Bwrite(bo, buf, 4); | 
|  | } | 
|  |  | 
|  | static int | 
|  | zread(void *va, void *buf, int n) | 
|  | { | 
|  | ZlibR *z = va; | 
|  | int nrow = z->nrow; | 
|  | int ncol = z->ncol; | 
|  | uchar *b = buf, *e = b+n, *img; | 
|  | int pixels;  /* number of pixels in row that can be sent now */ | 
|  | int i, a, pixwid; | 
|  |  | 
|  | pixwid = z->pixwid; | 
|  | while(b+pixwid <= e){ /* loop over image rows */ | 
|  | if(z->row >= nrow) | 
|  | break; | 
|  | if(z->col==0) | 
|  | *b++ = FilterNone; | 
|  | pixels = (e-b)/pixwid; | 
|  | if(pixels > ncol - z->col) | 
|  | pixels = ncol - z->col; | 
|  | img = z->data + z->width * z->row + pixwid * z->col; | 
|  |  | 
|  | memmove(b, img, pixwid*pixels); | 
|  | if(pixwid == 4){ | 
|  | /* | 
|  | * Convert to non-premultiplied alpha. | 
|  | */ | 
|  | for(i=0; i<pixels; i++, b+=4){ | 
|  | a = b[3]; | 
|  | if(a == 255 || a == 0) | 
|  | ; | 
|  | else{ | 
|  | if(b[0] >= a) | 
|  | b[0] = a; | 
|  | b[0] = (b[0]*255)/a; | 
|  | if(b[1] >= a) | 
|  | b[1] = a; | 
|  | b[1] = (b[1]*255)/a; | 
|  | if(b[2] >= a) | 
|  | b[2] = a; | 
|  | b[2] = (b[2]*255)/a; | 
|  | } | 
|  | } | 
|  | }else | 
|  | b += pixwid*pixels; | 
|  |  | 
|  | z->col += pixels; | 
|  | if(z->col >= ncol){ | 
|  | z->col = 0; | 
|  | z->row++; | 
|  | } | 
|  | } | 
|  | return b - (uchar*)buf; | 
|  | } | 
|  |  | 
|  | static void | 
|  | IDAT(ZlibW *z) | 
|  | { | 
|  | chunk(z->bo, "IDAT", z->buf, z->b - z->buf); | 
|  | z->b = z->buf; | 
|  | } | 
|  |  | 
|  | static int | 
|  | zwrite(void *va, void *buf, int n) | 
|  | { | 
|  | ZlibW *z = va; | 
|  | uchar *b = buf, *e = b+n; | 
|  | int m; | 
|  |  | 
|  | while(b < e){ /* loop over IDAT chunks */ | 
|  | m = z->e - z->b; | 
|  | if(m > e - b) | 
|  | m = e - b; | 
|  | memmove(z->b, b, m); | 
|  | z->b += m; | 
|  | b += m; | 
|  | if(z->b >= z->e) | 
|  | IDAT(z); | 
|  | } | 
|  | return n; | 
|  | } | 
|  |  | 
|  | static Memimage* | 
|  | memRGBA(Memimage *i) | 
|  | { | 
|  | Memimage *ni; | 
|  | char buf[32]; | 
|  | ulong dst; | 
|  |  | 
|  | /* | 
|  | * [A]BGR because we want R,G,B,[A] in big-endian order.  Sigh. | 
|  | */ | 
|  | chantostr(buf, i->chan); | 
|  | if(strchr(buf, 'a')) | 
|  | dst = ABGR32; | 
|  | else | 
|  | dst = BGR24; | 
|  |  | 
|  | if(i->chan == dst) | 
|  | return i; | 
|  |  | 
|  | ni = allocmemimage(i->r, dst); | 
|  | if(ni == nil) | 
|  | return ni; | 
|  | memimagedraw(ni, ni->r, i, i->r.min, nil, i->r.min, S); | 
|  | return ni; | 
|  | } | 
|  |  | 
|  | char* | 
|  | memwritepng(Biobuf *bo, Memimage *r, ImageInfo *II) | 
|  | { | 
|  | uchar buf[200], *h; | 
|  | ulong vgamma; | 
|  | int err, n; | 
|  | ZlibR zr; | 
|  | ZlibW zw; | 
|  | int nrow = r->r.max.y - r->r.min.y; | 
|  | int ncol = r->r.max.x - r->r.min.x; | 
|  | Tm *tm; | 
|  | Memimage *rgb; | 
|  |  | 
|  | rgb = memRGBA(r); | 
|  | if(rgb == nil) | 
|  | return "allocmemimage nil"; | 
|  | crctab = mkcrctab(0xedb88320); | 
|  | if(crctab == nil) | 
|  | sysfatal("mkcrctab error"); | 
|  | deflateinit(); | 
|  |  | 
|  | Bwrite(bo, PNGmagic, sizeof PNGmagic); | 
|  | /* IHDR chunk */ | 
|  | h = buf; | 
|  | put4(h, ncol); h += 4; | 
|  | put4(h, nrow); h += 4; | 
|  | *h++ = 8; /* bit depth = 24 bit per pixel */ | 
|  | *h++ = rgb->chan==BGR24 ? 2 : 6; /* color type = rgb */ | 
|  | *h++ = 0; /* compression method = deflate */ | 
|  | *h++ = 0; /* filter method */ | 
|  | *h++ = 0; /* interlace method = no interlace */ | 
|  | chunk(bo, "IHDR", buf, h-buf); | 
|  |  | 
|  | tm = gmtime(time(0)); | 
|  | h = buf; | 
|  | *h++ = (tm->year + 1900)>>8; | 
|  | *h++ = (tm->year + 1900)&0xff; | 
|  | *h++ = tm->mon + 1; | 
|  | *h++ = tm->mday; | 
|  | *h++ = tm->hour; | 
|  | *h++ = tm->min; | 
|  | *h++ = tm->sec; | 
|  | chunk(bo, "tIME", buf, h-buf); | 
|  |  | 
|  | if(II->fields_set & II_GAMMA){ | 
|  | vgamma = II->gamma*100000; | 
|  | put4(buf, vgamma); | 
|  | chunk(bo, "gAMA", buf, 4); | 
|  | } | 
|  |  | 
|  | if(II->fields_set & II_COMMENT){ | 
|  | strncpy((char*)buf, "Comment", sizeof buf); | 
|  | n = strlen((char*)buf)+1; /* leave null between Comment and text */ | 
|  | strncpy((char*)(buf+n), II->comment, sizeof buf - n); | 
|  | chunk(bo, "tEXt", buf, n+strlen((char*)buf+n)); | 
|  | } | 
|  |  | 
|  | /* image chunks */ | 
|  | zr.nrow = nrow; | 
|  | zr.ncol = ncol; | 
|  | zr.width = rgb->width * sizeof(uint32); | 
|  | zr.data = rgb->data->bdata; | 
|  | zr.row = zr.col = 0; | 
|  | zr.pixwid = chantodepth(rgb->chan)/8; | 
|  | zw.bo = bo; | 
|  | zw.buf = malloc(IDATSIZE); | 
|  | zw.b = zw.buf; | 
|  | zw.e = zw.b + IDATSIZE; | 
|  | err = deflatezlib(&zw, zwrite, &zr, zread, 6, 0); | 
|  | if(zw.b > zw.buf) | 
|  | IDAT(&zw); | 
|  | free(zw.buf); | 
|  | if(err) | 
|  | sysfatal("deflatezlib %s\n", flateerr(err)); | 
|  | chunk(bo, "IEND", nil, 0); | 
|  |  | 
|  | if(r != rgb) | 
|  | freememimage(rgb); | 
|  | return nil; | 
|  | } |