/* * png.i -- $Id$ * yorick interface to libpng image compression (www.libpng.org) */ if (!is_void(plug_in)) plug_in, "yorz"; func png_read (filename, &depth, &nfo, type=, quiet=) /* DOCUMENT image = png_read(filename) * or image = png_read(filename, depth, nfo) * * Read png file FILENAME. The returned IMAGE is either an array * of char or short, unless type= is specified (see below). * The IMAGE may have a leading dimension of 2 if it is gray+alpha, * 3 if it is rgb, or 4 if it is rgba. * In the second form, DEPTH and NFO must be simple variable references. * NFO is set to a pointer array to descriptive information by png_read: * *nfo(1) = PLTE 3-by-N char array of palette rgb values * *nfo(2) = tRNS char array of alpha (opacity) values corresponding * to PLTE or single gray or rgb short value (transparent) * *nfo(3) = bKGD single gray or rgb short value * note that bKGD and the single value tRNS have the same * range and meaning as a pixel value, in particular, * for a pseudocolor image, they represent a palette index * *nfo(4) = pCAL [x0,x1,max,eqtype,p0,p1,p2,p3,...] physical pixel value * relation between pixel value and physical value * array of double values (see below for meaning) * *nfo(5) = pCAL [calibration_name, unit_name] string pair * *nfo(6) = sCAL [wide,high,sunit] physical scale of pixels as scanned * or printed, sunit 1.0 for meters or 2.0 for radians * *nfo(7) = pHYs long [n_xpix,n_ypix,per_meter] values * n_xpix,n_ypix are pixels per unit, * per_meter is 0 for aspect ratio only, 1 for meters * *nfo(8) = tEXt (or zTXt or iTXt) 2-by-N string array of (key,text) * *nfo(9) = tIME string value image modification time * any or all of these NFO values may be nil. The four character * designators (e.g. PLTE) are the png chunk names for the corresponding * information. * * pCAL array of doubles has following meaning: * max = 2^depth-1 * original = long( floor( (image(i)*(x1-x0)+long(max)/2) / max ) ) + x0 * image(i) = long( floor( ((original-x0)*max+long(x1-x0)/2) / (x1-x0) ) ) * eqtype = 0 physical = p0 + p1*original/(x1-x0) * eqtype = 1 physical = p0 + p1*exp(p2*original/(x1-x0)) * eqtype = 2 physical = p0 + p1*p2^(original/(x1-x0)) * eqtype = 3 physical = p0 + p1*sinh(p2*(original-p3)/(x1-x0)) * * If the type= keyword is non-nil and non-zero, the returned value * is as if png_scale(image, nfo, type=type), which scales the raw image * according to the information in pCAL, or is a no-op if pCAL does * not exist. * * SEE ALSO: png_write, png_scale */ { dnwh = array(long, 8); nfo = array(pointer, 9); image = &[]; emsg = string(0); rslt = _png_read(filename, dnwh, nfo, image, emsg); if (rslt) { if (rslt == 1) error, "unable to open "+filename; else if (rslt == 2) error, "PNG ERROR: png_create_read_struct_2 failed"; error, "PNG ERROR: "+emsg; } if (dnwh(8) && !quiet) { write, format="PNG %ld warnings: %s\n", dnwh(8), emsg; } depth = dnwh(1); if (is_void(type)) image = *image; else image = png_scale(*image, nfo, type=type); return image; } func png_write (filename, image, depth, nfo, palette=, alpha=, bkgd=, pcal=, pcals=, scal=, phys=, text=, time=, trns=, quiet=) /* DOCUMENT png_write, filename, image * or png_write, filename, image, depth, nfo * * Write png file FILENAME containing IMAGE at the specified DEPTH. * The default DEPTH is 8 bits. For grayscale IMAGE, 1<=DEPTH<=16, * otherwise depth is 8 or 16. If NFO is specified, it is an * array of pointers as described in the help for png_read. You can * optionally specify the same information as keywords: * palette=[[r0,g0,b0],[r1,g1,b1],...] * alpha=[a0,a1,...] if image is simple 2D and palette specified * trns=value if image is gray (no palette) * [r,g,b] if image is color * illegal if image has alpha channel * bkgd=value or [r,g,b] suggested background color * note that bkgd and trns have the same range and meaning as a * pixel value, in particular, for a pseudocolor, a palette index * pcal=[x0,x1,max,eqtype,p0,p1,p2,p3,...] * pcals=[calibration_name, unit_name] as for pCAL (see png_read) * scal=[wide,high,sunit] as for sCAL (see png_read) * phys=[n_xpix,n_ypix,per_meter] as for pHYs (see png_read) * text=[[key1,text1],[key1,text1],...] * recognized keys are: Title, Author, Description, Copyright, * Creation Time, Software, Disclaimer, Warning, Source (a device), * and Comment * time=string modification time (timestamp() is default) * When both NFO and keywords are supplied, the keywords override any * corresponding value in nfo. * * If IMAGE has a data type other than short or char, a default pCAL * will be supplied if it is a simple grayscale (2D) image. If DEPTH * is not supplied, it defaults to 8 if IMAGE is type char and/or if * a palette is supplied, or to 16 otherwise. * * SEE ALSO: png_read, png_map */ { /* eventually, do conversion and even select pcal automatically */ if (structof(image(1)+0) != long) error, "image must be converted to integer data type"; dnwh = array(long, 8); dims = dimsof(image); nchan = (dims(1)==2); if (dims(1)==3) nchan = dims(2); if (nchan<1 || nchan>4) error, "image must have 1-4 channels (gray, gray+A, RGB, or RGBA)"; dnwh(2) = nchan; dnwh(3:4) = dims(-1:0); if (structof(nfo)==pointer && numberof(nfo)==9) { if (is_void(palette)) eq_nocopy, palette, *nfo(1); if (nfo(2)) { if (nchan==1 && palette) { if (is_void(alpha)) eq_nocopy, alpha, *nfo(2); } else { if (is_void(trns)) eq_nocopy, trns, *nfo(2); } } if (is_void(bkgd)) eq_nocopy, bkgd, *nfo(3); if (is_void(pcal)) eq_nocopy, pcal, *nfo(4); if (is_void(pcals)) eq_nocopy, pcals, *nfo(5); if (is_void(scal)) eq_nocopy, scal, *nfo(6); if (is_void(phys)) eq_nocopy, phys, *nfo(7); if (is_void(text)) eq_nocopy, text, *nfo(8); if (is_void(time)) eq_nocopy, time, *nfo(9); } else if (!is_void(nfo)) { error, "illegal nfo format"; } nfo = array(pointer, 9); pseudo = (nchan==1 && !is_void(palette)); dep = 0; if (!is_void(pcal)) { mx = long(pcal(3)); if (mx>65535 || mx<1) error, "pcal(3) mx parameter too big or too small"; else if (mx > 255) dep = 16; else if (mx > 15) dep = 8; else if (mx > 3) dep = 4; else if (mx > 1) dep = 2; else dep = 1; x0 = long(pcal(1)); x1 = long(pcal(2)); eqtype = long(pcal(4)); p = double(pcal(5:8)); } istruct = structof(image); if (!depth) { if (dep) depth = (istruct==char)? min(dep,8) : dep; else if (istruct==char || pseudo) depth = 8; else depth = 16; } dnwh(1) = depth; /* eventually, this should pay attention to pcal */ if (depth > 8) { if (istruct != short) image = short(image); } else { if (istruct != char) image = char(image); } npal = 0; if (!is_void(palette)) { if (nchan==2) error, "palette illegal for gray+alpha (2 channel) image"; dims = dimsof(palette); if (!numberof(dims) || anyof(dims(1:2)!=[2,3]) || dims(3)>256 || max(palette)>255 || min(palette)<0 || structof(palette+0)!=long) error, "illegal palette format or length"; if (structof(palette)!=char) palette = char(palette); npal = dims(3); nfo(1) = &palette; } if (!is_void(alpha)) { if (nchan!=1 || !npal) error, "alpha illegal for non-pseudocolor image"; dims = dimsof(alpha); if (!numberof(dims) || dims(1)!=1 || max(alpha)>255 || min(alpha)<0 || structof(alpha+0)!=long) error, "illegal alpha format or length"; if (structof(alpha)!=char) alpha = char(alpha); dnwh(7) = 1; nfo(2) = α } if (!is_void(trns)) { if (nchan==2 || nchan==4) error, "trns illegal when image pixels have alpha channel"; else if (nchan==1 && npal) error, "trns illegal for pseudocolor image, use alpha="; else if (nchan==1 && numberof(trns)==1) trns = trns(1)(1,-:1:3); dims = dimsof(trns); if (!numberof(dims) || dims(1)!=1 || dims(2)!=3 || min(trns)<0 || structof(trns+0)!=long) error, "illegal trns format or length"; trns = long(trns); nfo(2) = &trns; } if (!is_void(bkgd)) { if (nchan < 3) bkgd = bkgd(1)(1,-:1:3); dims = dimsof(bkgd); if (!numberof(dims) || dims(1)!=1 || dims(2)!=3 || min(bkgd)<0 || structof(bkgd+0)!=long) error, "illegal bkgd format or length"; bkgd = long(bkgd); nfo(3) = &bkgd; } if (!is_void(pcal)) { pcal = double(pcal); dims = dimsof(pcal); if (!numberof(dims) || dims(1)!=1 || dims(2)!=8 || anyof(pcal(1:4)!=long(pcal(1:4)))) error, "illegal pcal format or length"; if (pcal(4)<1 || pcal(4)>4) error, "unknown eqtype in pcal"; nfo(4) = &pcal; } if (!is_void(pcals)) { dims = dimsof(pcals); if (!numberof(dims) || dims(1)!=1 || dims(2)!=2 || structof(pcals)!=string) error, "illegal pcals format or length"; nfo(5) = &pcals; } if (!is_void(scal)) { scal = double(scal); dims = dimsof(scal); if (!numberof(dims) || dims(1)!=1 || dims(2)!=3 || scal(3)!=long(scal(3))) error, "illegal scal format or length"; nfo(6) = &scal; } if (!is_void(phys)) { phys = phys+0; dims = dimsof(phys); if (!numberof(dims) || dims(1)!=1 || dims(2)!=3 || structof(phys)!=long || anyof(phys(1:2)<=0)) error, "illegal phys format or length"; nfo(7) = &phys; } ntxt = 0; if (!is_void(text)) { dims = dimsof(text); if (!numberof(dims) || dims(1)!=2 || dims(2)!=2 || structof(text)!=string) error, "illegal text or nfo(8) format or length"; ntxt = dims(3); nfo(8) = &text; } if (!is_void(time)) { time = time+0; dims = dimsof(time); if (!numberof(dims) || dims(1)!=1 || dims(2)!=6 || structof(time)!=long || anyof(time<0)) error, "illegal time format or length"; nfo(9) = &time; } dnwh(5) = npal; dnwh(6) = ntxt; emsg = string(0); rslt = _png_write(filename, dnwh, nfo, &image, emsg); if (rslt) { if (rslt == 1) error, "bad inputs or unable to create "+filename; else if (rslt == 2) error, "PNG ERROR: png_create_write_struct_2 failed"; error, "PNG ERROR: "+emsg; } if (dnwh(8) && !quiet) { write, format="PNG %ld warnings: %s\n", dnwh(8), emsg; } } func png_scale (image, nfo, type=) /* DOCUMENT image = png_scale(raw_image, nfo, type=type) * scales RAW_IMAGE to type TYPE (char, short, int, long, float, or * double, according to the pCAL information in NFO. The NFO * parameter may be either the array of pointers returned by * png_read, or an array of reals as for *nfo(4) (see png_read). * * SEE ALSO: png_map, png_read, png_write */ { if (structof(nfo) == pointer) nfo = *nfo(4); if (is_void(nfo)) return image; dx = nfo(2) - nfo(1); x0 = long(nfo(1)); x1 = long(nfo(2)); mx = long(nfo(3)); eq = long(nfo(4)); p = double(nfo(5:0)); if (eq<0 || eq>3) error, "unknown equation type"; sample = image(1); image = long(image); if (sizeof(sample) == 1) image &= ~0xff; else if (sizeof(sample) == 2) image &= ~0xffff; else if (anyof((image>mx) | (image<0))) error, "image out of scaling range"; if (mx<1 || mx>65535 || ((mx+1)&mx)) error, "max=2^depth-1 has impossible value"; if (x1 > x0) { x1 -= x0; } else if (x1 < x0) { /* adjust so that x1 > 0 in all cases */ x1 = x0 - x1; x0 -= x1; /* i.e.- original x1 */ image = mx - image; } else { error, "x0 == x1 is impossible scaling"; } /* here is formula from PNG Extensions document (x1>0): * d = image*(x1%mx); * image = image*(x1/mx) + d/mx + (d%mx + mx/2)/mx + x0; */ q = x1 / mx; r = x1 & mx; /* mx = 2^n - 1 */ if (r) { r *= image; r = r/mx + ((r&mx)+(mx>>1))/mx; } if (q) image *= q; image += r + x0; if (is_void(type)) { if (!eq && !p(1) && p(2)==dx) { if (x0>=0 && x1<256) type = char; else if (x0>-32768 && x1+x0<32768) type = short; else type = long; } else { type = double; } } sample = type(0); if (structof(sample+0) != long) { dx = 1./dx; if (!eq) image *= dx; else if (eq == 1) image = exp(p(3)*dx * image); else if (eq == 2) image = p(3) ^ (dx * image); else image = sinh(p(3)*dx * (image-p(4))); image = p(1) + p(2)*image; } return type(image); } func png_map (image, nfo) /* DOCUMENT image = png_map(full_image, nfo) * maps FULL_IMAGE to png-stored values, according to the * pCAL information in NFO. * The NFO parameter may be either the array of pointers as returned by * png_read, or an array of reals as for *nfo(4) (see png_read). * You can use png_pcal to compute an NFO mapping tailored to IMAGE. * * SEE ALSO: png_pcal, png_scale, png_read, png_write */ { if (!is_void(cmax)) image = min(image,cmax); if (!is_void(cmin)) image = max(image,cmin); if (structof(nfo) == pointer) nfo = *nfo(4); dx = nfo(2) - nfo(1); x0 = long(nfo(1)); x1 = long(nfo(2)); mx = long(nfo(3)); eq = long(nfo(4)); p = double(nfo(5:0)); if (eq<0 || eq>3) error, "unknown equation type"; if (x0 == x1) error, "x0 == x1 is impossible scaling"; if (mx<1 || mx>65535 || ((mx+1)&mx)) error, "max=2^depth-1 has impossible value"; if (eq || p(1) || p(2)!=dx) { /* pre-clip image to map inside interval [x0,x1] */ x = nfo(1:2); if (!eq) x /= dx; else if (eq == 1) x = exp(p(3)/dx * x); else if (eq == 2) x = p(3) ^ (x/dx); else image = sinh(p(3)/dx * (x-p(4))); x = p(1) + p(2)*x; image = min(max(x), max(min(x),image)); /* then convert image to integer values in interval [x0,x1] */ image = (image - p(1)) * (1./p(2)); if (!eq) image *= dx; else if (eq == 1) image = (dx/p(3)) * _png_log(image); else if (eq == 2) image = (dx/_png_log(p(3))) * _png_log(image); else image = (dx/p(3)) * asinh(image) + p(4); image = long(image); } if (x1 > x0) { x1 -= x0; } else { /* adjust so that x1 > 0 in all cases */ x1 = x0 - x1; x0 -= x1; /* i.e.- original x1 */ } image = min(x1, max(0,image-x0)); if (x1 <= 0xffff) image = (image*mx + (x1>>1)) / x1; else image = long((double(image)*mx + (x1>>1)) / x1); return (mx <= 255)? char(image) : short(image); } func png_pcal (image, depth, cmin=, cmax=, res=, log=) /* DOCUMENT pcal = png_pcal(image) * or pcal = png_pcal(image, depth) * * KEYWORDS: cmin=, cmax=, res=, log= * cmin, cmax clip image to these minimum and maximum values * res image "resolution", or minimum step size * log non-zero forces log map if image all positive * or all negative * * returns 8-element pCAL png mapping for IMAGE, appropriate for * use as pcal= keyword in png_write. The png_map function applies * pcal to produce the as-stored char or short array; the png_scale * function applies pcal to recreate the original IMAGE from the * as-stored array. * * There are three kinds of pCAL mappings: linear, log, and asinh. * Linear and log scales are familiar; the asinh scale is a modified * log scale that can be used for arrays that change sign: * * linear: image = a*stored + b * log: image = b * exp(a*stored) * asinh: image = b * sinh(a*(stored - mx/2)) * * You can specify a bit DEPTH for the stored array, which can be * between 2 and 16 inclusive. For bit depth 1, just threshhold * the image (image>const_thresh). By default, for integer IMAGE, * DEPTH is the smallest depth that covers the range of IMAGE values, * but never more than 16. For float or double IMAGE, the default * DEPTH is always 16. * * If IMAGE has any integer data type, the pCAL mapping will always * be linear; use IMAGE+0.0 if you want a log or asinh map. * * The png pCAL definition allows b<0 in the log scale, so it can * be used for image values that are either all positive or all * negative. In either case, the integer stored values take equal * ratio steps from the minimum to the maximum image values (or * cmin and cmax). For the linear scale, of course, each step in * the stored integer represents an constant increment in the image * value. The asinh scale is a logarithmic ratio scale for stored * values near 0 or mx (the maximum stored integer value), reverting * to a linear scale near the middle of its range where the image * value passes through zero. * * To get the asinh scale, you must specify the res= keyword: * You must specify the smallest step size for the asinh scale by * setting the res= keyword. For a log scale, the res= value will * replace the actual minimum array value or cmin value (or cmax if * image is all negative values), clipping any smaller absolute values. * If mx is large enough to cover the whole scale with the given res= * value in linear steps, a linear scale will be used. * * You can specify log=1 to force log scaling if image is all * positive or all negative. * * SEE ALSO: png_scale, png_write, png_read */ { sample = image(1); in = min(image); ix = max(image); if (!is_void(cmax)) { in = min(in, cmax); ix = min(ix, cmax); } if (!is_void(cmin)) { in = max(in, cmin); ix = max(ix, cmin); } if (depth && (depth<2 || depth>16)) error, "depth must be 2<=depth<=16 (for depth=1 just threshhold)"; if (structof(sample+0) == long) { x0 = long(in); x1 = long(ix); if (!depth) for (mx=2,depth=1 ; mx>x1-x0 ; mx<<1,depth++); mx = (1<<depth) - 1; eq = 0; p = [0., dx, 0., 0.]; } else { if (!depth) depth = 16; mx = (1<<depth) - 1; x0 = 0; x1 = mx; if (res && mx*abs(res)>=(ix-in)) { eq = 0; } else if (in*ix > 0.) { if (res && abs(res)<min(abs(in),abs(ix))) { if (in > 0.) in = abs(res); else ix = -abs(res); } lrat = _png_log(ix/in); if (in!=ix && (log || abs(lrat)>_png_log(32.))) { eq = 1; p = [0., in, lrat/mx, 0.]; } else { eq = 0; } } else { if (res) { eq = 3; /* sinh(0.5*p3)/sinh(p3/mx) = max(abs(x))/res * roughly (0.5-1/mx)*p3 = log(axmax/res) * know axmax/abs(res) > mx */ p4 = 0.5*mx; p = _png_eq3(p4, max(abs(in),abs(ix))/abs(res)); p2 = abs(res) / sinh(p); p3 = mx * p; p = [0., p2, p3, p4]; } else { eq = 0; } } if (!eq) p = [double(in), ((in==ix)? mx : double(ix-in)/mx), 0., 0.]; } return grow(double([x0, x1, mx, eq]), p); } func _png_eq3(a, b) { /* solve sinh(a*x)/sinh(x) = b for x assuming b>a>1 */ x = _png_log(b)/(a-1.); /* overestimate of root */ for (i=1,err=1. ; i<50 && err>1e-9 ; i++) { s = sinh(x); bs = b*s; y = a*x - asinh(bs); yp = a - b*sqrt((1.+s*s)/(1.+bs*bs)); if (yp <= 0.) error, "eq=3 root solve lost"; dx = y/yp; x -= dx; err = abs(dx)/x; } return x; } if (is_void(_png_log)) _png_log = log; extern _png_read; extern _png_write;