blob: 24a61a3661314cc2b551e34b1adf07a4a77bb586 [file] [log] [blame]
/* 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;
}