// 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 #include #include #include #include #include #include #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 ulong *crctab; static uchar PNGmagic[] = {137,80,78,71,13,10,26,10}; static void put4(uchar *a, ulong 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]; ulong 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= 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(ulong); 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; }