--- /dev/null
+<head>
+<script>
+//
+// PNG drawing library for JavaScript.
+// Copyright (C) 1999 by Roger E Critchlow Jr,
+// Santa Fe, New Mexico, USA.
+//
+// Licensed under the Academic Free License version 2.1
+//
+// The home page for Pnglets is http://www.elf.org/pnglets,
+// a copy of the AFL may be found at http://www.opensource.org/licenses/afl-2.1.php,
+// Pnglets were inspired by and copied from gd1.3, http://www.boutell.com/gd,
+// other parts were inspired by or copied from Tcl/Tk, http://www.scriptics.com,
+// and some algorithms were taken from Foley & van Dam 2nd Edition.
+//
+// Thanks to Alex Vincent for pointing out the advantages of eliminating strict
+// javascript warnings.
+//
+
+// create a new Pnglet of specified width, height, and depth
+// width and height are specified in pixels
+// depth is really the number of palette entries
+function Pnglet(width,height,depth) {
+ this.width = width || 16;
+ this.height = height || 16;
+ this.depth = Math.min(256, depth || 16);
+
+ // pixel data and row filter identifier size
+ this.pix_size = height*(width+1);
+
+ // deflate header, pix_size, block headers, adler32 checksum
+ this.data_size = 2 + this.pix_size + 5*Math.floor((this.pix_size+0xffff-1)/0xffff) + 4;
+
+ // offsets and sizes of Png chunks
+ this.ihdr_offs = 0; // IHDR offset and size
+ this.ihdr_size = 4+4+13+4;
+ this.plte_offs = this.ihdr_offs+this.ihdr_size; // PLTE offset and size
+ this.plte_size = 4+4+3*depth+4;
+ this.trns_offs = this.plte_offs+this.plte_size; // tRNS offset and size
+ this.trns_size = 4+4+depth+4;
+ this.idat_offs = this.trns_offs+this.trns_size; // IDAT offset and size
+ this.idat_size = 4+4+this.data_size+4;
+ this.iend_offs = this.idat_offs+this.idat_size; // IEND offset and size
+ this.iend_size = 4+4+4;
+ this.png_size = this.iend_offs+this.iend_size; // total PNG size
+
+ // array of one byte strings
+ this.png = new Array(this.png_size);
+
+ // functions for initializing data
+ function initialize(png, offs, str) {
+ for (var i = 1; i < arguments.length; i += 1)
+ if (typeof arguments[i].length != "undefined")
+ for (var j = 0; j < arguments[i].length; j += 1)
+ png[offs++] = arguments[i].charAt(j);
+ };
+ function byte2(w) { return String.fromCharCode((w>>8)&255, w&255); };
+ function byte4(w) { return String.fromCharCode((w>>24)&255, (w>>16)&255, (w>>8)&255, w&255); };
+ function byte2lsb(w) { return String.fromCharCode(w&255, (w>>8)&255); };
+
+ // initialize everything to zero byte
+ for (var i = 0; i < this.png_size; i += 1)
+ this.png[i] = String.fromCharCode(0);
+
+ // initialize non-zero elements
+ initialize(this.png, this.ihdr_offs, byte4(this.ihdr_size-12), 'IHDR',
+ byte4(width), byte4(height), String.fromCharCode(8, 3));
+ initialize(this.png, this.plte_offs, byte4(this.plte_size-12), 'PLTE');
+ initialize(this.png, this.trns_offs, byte4(this.trns_size-12), 'tRNS');
+ initialize(this.png, this.idat_offs, byte4(this.idat_size-12), 'IDAT');
+ initialize(this.png, this.iend_offs, byte4(this.iend_size-12), 'IEND');
+
+ // initialize deflate header
+ var header = ((8 + (7<<4)) << 8) | (3 << 6);
+ header += 31 - (header % 31);
+ initialize(this.png, this.idat_offs+8, byte2(header));
+
+ // initialize deflate block headers
+ for (i = 0; i*0xffff < this.pix_size; i += 1) {
+ var size, bits;
+ if (i + 0xffff < this.pix_size) {
+ size = 0xffff;
+ bits = String.fromCharCode(0);
+ } else {
+ size = this.pix_size - i*0xffff;
+ bits = String.fromCharCode(1);
+ }
+ initialize(this.png, this.idat_offs+8+2+i*(5+0xffff), bits, byte2lsb(size), byte2lsb(~size));
+ }
+
+ // initialize palette hash
+ this.palette = new Object();
+ this.pindex = 0;
+}
+
+// version string/number
+Pnglet.version = "19990427.0";
+
+// test if coordinates are within bounds
+Pnglet.prototype.inBounds = function(x,y) { return x >= 0 && x < this.width && y >= 0 && y < this.height; }
+
+// clip an x value to the window width
+Pnglet.prototype.clipX = function(x) { return (x < 0) ? 0 : (x >= this.width) ? this.width-1 : x ; }
+
+// clip a y value to the window height
+Pnglet.prototype.clipY = function(y) { return (y < 0) ? 0 : (y >= this.height) ? this.height-1 : y ; }
+
+// compute the index into a png for a given pixel
+Pnglet.prototype.index = function(x,y) {
+ var i = y*(this.width+1)+x+1;
+ var j = this.idat_offs+8+2+Math.floor((i/0xffff)+1)*5+i;
+ return j;
+}
+
+// make a color in a Pnglet
+Pnglet.prototype.color = function(red, green, blue, alpha) {
+ alpha = alpha >= 0 ? alpha : 255;
+ var rgba = (((((alpha<<8)+red)<<8)+green)<<8)+blue;
+ if ( typeof this.palette[rgba] == "undefined") {
+ if (this.pindex == this.depth) return String.fromCharCode(0);
+ this.palette[rgba] = String.fromCharCode(this.pindex);
+ this.png[this.plte_offs+8+this.pindex*3+0] = String.fromCharCode(red);
+ this.png[this.plte_offs+8+this.pindex*3+1] = String.fromCharCode(green);
+ this.png[this.plte_offs+8+this.pindex*3+2] = String.fromCharCode(blue);
+ this.png[this.trns_offs+8+this.pindex] = String.fromCharCode(alpha);
+ this.pindex += 1;
+ }
+ return this.palette[rgba];
+}
+
+// return true if this is a color
+Pnglet.prototype.isColor = function(color) {
+ return typeof(color) == 'string' &&
+ color.length == 1 &&
+ color.charCodeAt(0) >= 0 &&
+ color.charCodeAt(0) < this.depth;
+}
+
+// find the red, green, blue, or alpha value of a Pnglet color
+Pnglet.prototype.red = function(color) { return this.png[this.plte_offs+8+color.charCodeAt(0)*3+0].charCodeAt(0); }
+Pnglet.prototype.green = function(color) { return this.png[this.plte_offs+8+color.charCodeAt(0)*3+1].charCodeAt(0); }
+Pnglet.prototype.blue = function(color) { return this.png[this.plte_offs+8+color.charCodeAt(0)*3+2].charCodeAt(0); }
+Pnglet.prototype.alpha = function(color) { return this.png[this.trns_offs+8+color.charCodeAt(0)].charCodeAt(0); }
+
+// draw a point or points
+Pnglet.prototype.point = function(pointColor, x0, y0) {
+ var a = arguments;
+ this.pointNXY(pointColor, (a.length-1)/2, function(i) { return a[2*i+1]; }, function(i) { return a[2*i+2]; });
+}
+
+Pnglet.prototype.pointNXY = function(pointColor, n, x, y) {
+ if ( ! this.isColor(pointColor))
+ return;
+ for (var i = 0; i < n; i += 1) {
+ var x1 = x(i), y1 = y(i);
+ if (this.inBounds(x1,y1))
+ this.png[this.index(x1,y1)] = pointColor;
+ }
+}
+
+// read a pixel
+Pnglet.prototype.getPoint = function(x,y) { return this.inBounds(x,y) ? this.png[this.index(x,y)] : String.fromCharCode(0); }
+
+// draw a horizontal line
+Pnglet.prototype.horizontalLine = function(lineColor, x1, x2, y) {
+ if ( ! this.isColor(lineColor))
+ return;
+ x1 = this.clipX(x1);
+ x2 = this.clipX(x2);
+ var x;
+ if (x1 < x2)
+ for (x = x1; x <= x2; x += 1)
+ this.png[this.index(x,y)] = lineColor;
+ else
+ for (x = x2; x <= x1; x += 1)
+ this.png[this.index(x,y)] = lineColor;
+}
+
+// draw a vertical line
+Pnglet.prototype.verticalLine = function(lineColor, x, y1, y2) {
+ if ( ! this.isColor(lineColor))
+ return;
+ y1 = this.clipY(y1);
+ y2 = this.clipY(y2);
+ var y;
+ if (y1 < y2)
+ for (y = y1; y <= y2; y += 1)
+ this.png[this.index(x,y)] = lineColor;
+ else
+ for (y = y2; y <= y1; y += 1)
+ this.png[this.index(x,y)] = lineColor;
+}
+
+// draw a general line
+Pnglet.prototype.generalLine = function(lineColor, x1, y1, x2, y2) {
+ if ( ! this.isColor(lineColor))
+ return;
+ var dx = Math.abs(x2-x1), dy = Math.abs(y2-y1);
+ var incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag, xinc, yinc;
+ if (dy <= dx) {
+ d = 2*dy - dx;
+ incr1 = 2*dy;
+ incr2 = 2 * (dy - dx);
+ if (x1 > x2) {
+ x = x2;
+ y = y2;
+ ydirflag = -1;
+ xend = x1;
+ } else {
+ x = x1;
+ y = y1;
+ ydirflag = 1;
+ xend = x2;
+ }
+ yinc = (((y2 - y1) * ydirflag) > 0) ? 1 : -1;
+ this.point(lineColor, x, y);
+ while (x++ < xend) {
+ if (d < 0) {
+ d += incr1;
+ } else {
+ y += yinc;
+ d += incr2;
+ }
+ this.point(lineColor, x, y);
+ }
+ } else { /* dy > dx */
+ d = 2*dx - dy;
+ incr1 = 2*dx;
+ incr2 = 2 * (dx - dy);
+ if (y1 > y2) {
+ y = y2;
+ x = x2;
+ yend = y1;
+ xdirflag = -1;
+ } else {
+ y = y1;
+ x = x1;
+ yend = y2;
+ xdirflag = 1;
+ }
+ xinc = (((x2 - x1) * xdirflag) > 0) ? 1 : -1;
+ this.point(lineColor, x, y);
+ while (y++ < yend) {
+ if (d < 0) {
+ d += incr1;
+ } else {
+ x += xinc;
+ d += incr2;
+ }
+ this.point(lineColor, x, y);
+ }
+ }
+}
+
+// draw a line
+Pnglet.prototype.line = function(lineColor, x0, y0) {
+ var a = arguments;
+ this.lineNXY(lineColor, (a.length-1)/2, function(i) { return a[2*i+1]; }, function(i) { return a[2*i+2]; });
+}
+
+Pnglet.prototype.lineNXY = function(lineColor, n, x, y) {
+ if ( ! this.isColor(lineColor))
+ return;
+ var x1 = x(0), y1 = y(0);
+ for (var i = 1; i < n; i += 1) {
+ var x2 = x(i), y2 = y(i);
+ if (x1 == x2)
+ this.verticalLine(lineColor, x1, y1, y2);
+ else if (y1 == y2)
+ this.horizontalLine(lineColor, x1, x2, y1);
+ else
+ this.generalLine(lineColor, x1, y1, x2, y2);
+ x1 = x2;
+ y1 = y2;
+ }
+}
+
+// draw a polygon
+Pnglet.prototype.polygon = function(outlineColor, fillColor, x1, y1) {
+ var a = arguments;
+ this.polygonNXY(outlineColor, fillColor, (a.length-2)/2, function(i) {return a[2*i+2];}, function(i) {return a[2*i+3];});
+}
+
+Pnglet.prototype.polygonNXY = function(outlineColor, fillColor, n, x, y) {
+ if (n <= 0)
+ return;
+ if (this.isColor(fillColor))
+ this.concaveNXY(fillColor, n, x, y);
+ if (this.isColor(outlineColor))
+ this.lineNXY(outlineColor, n+1, function(i) { return x(i%n); }, function(i) { return y(i%n); });
+}
+
+/*
+ * Concave Polygon Scan Conversion
+ * by Paul Heckbert
+ * from "Graphics Gems", Academic Press, 1990
+ */
+Pnglet.prototype.concaveNXY = function(fillColor, n, ptx, pty) {
+ function Edge(ex, edx, ei) { /* a polygon edge */
+ this.x = ex; /* x coordinate of edge's intersection with current scanline */
+ this.dx = edx; /* change in x with respect to y */
+ this.i = ei; /* edge number: edge i goes from pt[i] to pt[i+1] */
+ };
+ function cdelete(di) { /* remove edge i from active list */
+ for (var j = 0; j < active.length; j += 1)
+ if (active[j].i == di)
+ active.splice(j, 1);
+ };
+ function cinsert(ii, iy) { /* append edge i to end of active list */
+ var ij = ii<n-1 ? ii+1 : 0;
+ var px, py, qx, qy;
+ if (pty(ii) < pty(ij)) {
+ px = ptx(ii); py = pty(ii);
+ qx = ptx(ij); qy = pty(ij);
+ } else {
+ px = ptx(ij); py = pty(ij);
+ qx = ptx(ii); qy = pty(ii);
+ }
+ /* initialize x position at intersection of edge with scanline y */
+ var dx = (qx-px)/(qy-py);
+ active.push(new Edge(dx*(iy+.5-py)+px, dx, ii));
+ };
+
+ var ind = new Array(n); /* list of vertex indices, sorted by pt[ind[j]].y */
+ var active = new Array(0); /* start with an empty active list */
+
+ /* create y-sorted array of indices ind[k] into vertex list */
+ for (var k = 0; k < n; k += 1) ind[k] = k;
+ ind.sort(function(i1, i2) { return pty(i1) <= pty(i2) ? -1 : 1; });
+ k = 0; /* ind[k] is next vertex to process */
+ var y0 = Math.max(0, Math.ceil(pty(ind[0])+.5)); /* ymin of polygon */
+ var y1 = Math.min(this.height, Math.floor(pty(ind[n-1])-.5)); /* ymax of polygon */
+
+ for (var y = y0; y <= y1; y += 1) { /* step through scanlines */
+ /* scanline y is at y+.5 in continuous coordinates */
+
+ /* check vertices between previous scanline and current one, if any */
+ for (; k<n && pty(ind[k]) <= y+.5; k += 1) {
+ /* to simplify, if pt.y=y+.5, pretend it's above */
+ /* invariant: y-.5 < pt[i].y <= y+.5 */
+ var i = ind[k];
+
+ /*
+ * insert or delete edges before and after vertex i (i-1 to i,
+ * and i to i+1) from active list if they cross scanline y
+ */
+ var j = (i-1+n)%n; /* vertex previous to i */
+ if (pty(j) <= y-.5) { /* old edge, remove from active list */
+ cdelete(j);
+ } else if (pty(j) > y+.5) { /* new edge, add to active list */
+ cinsert(j, y);
+ }
+ if (i != ind[k]) {
+ alert("Your browser's implementation of JavaScript is seriously broken,\n"+
+ "as in variables are changing value of their own volition.\n"+
+ "You should upgrade to a newer version browser.");
+ return;
+ }
+ j = (i+1)%n; /* vertex next after i */
+ if (pty(j) <= y-.5) { /* old edge, remove from active list */
+ cdelete(i);
+ } else if (pty(j) > y+.5) { /* new edge, add to active list */
+ cinsert(i, y);
+ }
+ }
+
+ /* sort active edge list by active[j].x */
+ active.sort(function(u,v) { return u.x <= v.x ? -1 : 1; });
+
+ /* draw horizontal segments for scanline y */
+ for (j = 0; j < active.length; j += 2) { /* draw horizontal segments */
+ /* span 'tween j & j+1 is inside, span tween j+1 & j+2 is outside */
+ var xl = Math.ceil(active[j].x+.5); /* left end of span */
+ if (xl<0) xl = 0;
+ var xr = Math.floor(active[j+1].x-.5); /* right end of span */
+ if (xr>this.width-1) xr = this.width-1;
+ if (xl<=xr)
+ this.horizontalLine(fillColor, xl, xr, y); /* draw pixels in span */
+ active[j].x += active[j].dx; /* increment edge coords */
+ active[j+1].x += active[j+1].dx;
+ }
+ }
+}
+
+// draw a rectangle
+Pnglet.prototype.rectangle = function(outlineColor, fillColor, x0,y0,x1,y1) {
+ if (this.isColor(fillColor))
+ for (var y = y0; y < y1; y += 1)
+ this.horizontalLine(fillColor, x0+1, x1-2, y);
+ if (this.isColor(outlineColor)) {
+ this.horizontalLine(outlineColor, x0, x1-1, y0);
+ this.horizontalLine(outlineColor, x0, x1-1, y1-1);
+ this.verticalLine(outlineColor, x0, y0, y1-1);
+ this.verticalLine(outlineColor, x1-1, y0, y1-1);
+ }
+}
+
+// draw an arc
+Pnglet.prototype.arc = function(outlineColor, cx,cy,w,h, s,e) {
+ var p = this.midpointEllipse(cx,cy, w,h, s,e);
+ function x(i) { return p[i*2]; };
+ function y(i) { return p[i*2+1]; };
+ this.lineNXY(outlineColor, p.length/2, x, y);
+}
+
+// draw an oval
+Pnglet.prototype.oval = function(outlineColor, fillColor, cx,cy,w,h) {
+ var p = this.midpointEllipse(cx,cy, w,h, 0,359);
+ function x(i) { return p[i*2]; };
+ function y(i) { return p[i*2+1]; };
+ this.polygonNXY(outlineColor, fillColor, p.length/2, x, y);
+}
+
+// draw an arc with chord
+Pnglet.prototype.chord = function(outlineColor, fillColor, cx,cy,w,h, s,e) {
+ var p = this.midpointEllipse(cx,cy, w,h, s,e);
+ function x(i) { return p[i*2]; };
+ function y(i) { return p[i*2+1]; };
+ this.polygonNXY(outlineColor, fillColor, p.length/2, x, y);
+}
+
+// draw an arc with pieslice
+Pnglet.prototype.pieslice = function(outlineColor, fillColor, cx,cy,w,h, s,e) {
+ var p = this.midpointEllipse(cx,cy, w,h, s,e);
+ p[p.length] = cx;
+ p[p.length] = cy;
+ function x(i) { return p[i*2]; };
+ function y(i) { return p[i*2+1]; };
+ this.polygonNXY(outlineColor, fillColor, p.length/2, x, y);
+}
+
+// oval arcs
+// generate points of oval circumference
+// midpoint ellipse, Foley & van Dam, 2nd Edition, p. 90, 1990
+Pnglet.prototype.midpointEllipse = function(cx,cy, w,h, s,e) {
+ var a = Math.floor(w/2), b = Math.floor(h/2);
+ var a2 = a*a, b2 = b*b, x = 0, y = b;
+ var d1 = b2 - a2*b + a2/4;
+ cx = Math.floor(cx);
+ cy = Math.floor(cy);
+ var p = new Array();
+
+ // quadrant I, anticlockwise
+ p.push(x,-y);
+ while (a2*(y-1/2) > b2*(x+1)) {
+ if (d1 < 0) {
+ d1 += b2*(2*x+3);
+ } else {
+ d1 += b2*(2*x+3) + a2*(-2*y + 2);
+ y -= 1;
+ }
+ x += 1;
+ p.unshift(x,-y);
+ }
+ var d2 = b2*(x+1/2)*(x+1/2) + a2*(y-1)*(y-1) - a2*b2;
+ while (y > 0) {
+ if (d2 < 0) {
+ d2 += b2*(2*x+2) + a2*(-2*y+3);
+ x += 1;
+ } else {
+ d2 += a2*(-2*y+3);
+ }
+ y -= 1;
+ p.unshift(x,-y);
+ }
+ // quadrant II, anticlockwise
+ var n4 = p.length;
+ for (var i = n4-4; i >= 0; i -= 2)
+ p.push(-p[i], p[i+1]);
+ // quadrants III and IV, anticlockwise
+ var n2 = p.length;
+ for (i = n2-4; i > 0; i -= 2)
+ p.push(p[i], -p[i+1]);
+
+ // compute start and end indexes from start and extent
+ e %= 360;
+ if (e < 0) {
+ s += e;
+ e = -e;
+ }
+ s %= 360;
+ if (s < 0)
+ s += 360;
+ var is = Math.floor(s/359 * p.length/2);
+ var ie = Math.floor(e/359 * p.length/2)+1;
+ p = p.slice(is*2).concat(p.slice(0, is*2)).slice(0, ie*2);
+
+ // displace to center
+ for (i = 0; i < p.length; i += 2) {
+ p[i] += cx;
+ p[i+1] += cy;
+ }
+ return p;
+}
+
+// fill a region
+// from gd1.3 with modifications
+Pnglet.prototype.fill = function(outlineColor,fillColor,x,y) {
+ if (outlineColor) { // fill to outline color
+ /* Seek left */
+ var leftLimit = -1;
+ for (var i = x; i >= 0 && this.getPoint(i, y) != outlineColor; i -= 1)
+ leftLimit = i;
+
+ if (leftLimit == -1)
+ return;
+
+ /* Seek right */
+ var rightLimit = x;
+ for (i = (x+1); i < this.width && this.getPoint(i, y) != outlineColor; i += 1)
+ rightLimit = i;
+
+ /* fill extent found */
+ this.horizontalLine(fillColor, leftLimit, rightLimit, y);
+
+ /* Seek above and below */
+ for (var dy = -1; dy <= 1; dy += 2) {
+ if (this.inBounds(x,y+dy)) {
+ var lastBorder = 1;
+ for (i = leftLimit; i <= rightLimit; i++) {
+ var c = this.getPoint(i, y+dy);
+ if (lastBorder) {
+ if ((c != outlineColor) && (c != fillColor)) {
+ this.fill(outlineColor, fillColor, i, y+dy);
+ lastBorder = 0;
+ }
+ } else if ((c == outlineColor) || (c == fillColor)) {
+ lastBorder = 1;
+ }
+ }
+ }
+ }
+
+ } else { // flood fill color at x, y
+ /* Test for completion */
+ var oldColor = this.getPoint(x, y);
+ if (oldColor == fillColor)
+ return;
+
+ /* Seek left */
+ leftLimit = (-1);
+ for (i = x; i >= 0 && this.getPoint(i, y) == oldColor; i--)
+ leftLimit = i;
+
+ if (leftLimit == -1)
+ return;
+
+ /* Seek right */
+ rightLimit = x;
+ for (i = (x+1); i < this.width && this.getPoint(i, y) == oldColor; i++)
+ rightLimit = i;
+
+ /* Fill extent found */
+ this.horizontalLine(fillColor, leftLimit, rightLimit, y);
+
+ /* Seek above and below */
+ for (dy = -1; dy <= 1; dy += 2) {
+ if (this.inBounds(x,y+dy)) {
+ lastBorder = 1;
+ for (i = leftLimit; i <= rightLimit; i++) {
+ c = this.getPoint(i, y+dy);
+ if (lastBorder) {
+ if (c == oldColor) {
+ this.fill(null, fillColor, i, y+dy);
+ lastBorder = 0;
+ }
+ } else if (c != oldColor) {
+ lastBorder = 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+// smoothed points
+Pnglet.prototype.smoothPoint = function(smoothSteps, pointColor, x0, y0) {
+ var a = arguments, self = this, n = (a.length-2)/2;
+ this.smooth(smoothSteps,
+ function(n, x, y) { self.pointNXY(pointColor, n, x, y); },
+ n,
+ function(i) { return a[2*i+2]; },
+ function(i) { return a[2*i+3]; });
+}
+
+// smoothed polyline
+Pnglet.prototype.smoothLine = function(smoothSteps, lineColor, x0, y0) {
+ var a = arguments, self = this, n = (a.length-2)/2;
+ this.smooth(smoothSteps,
+ function(n, x, y) { self.lineNXY(lineColor, n, x, y); },
+ n,
+ function(i) { return a[2*i+2]; },
+ function(i) { return a[2*i+3]; });
+}
+
+// smoothed polygon
+Pnglet.prototype.smoothPolygon = function(smoothSteps, outlineColor, fillColor, x0, y0) {
+ var a = arguments, self = this, n = (a.length-3)/2 + 1;
+ this.smooth(smoothSteps,
+ function(n, x, y) { self.polygonNXY(outlineColor, fillColor, n, x, y); },
+ n,
+ function(i) { return a[2*(i%(n-1))+3]; },
+ function(i) { return a[2*(i%(n-1))+4]; });
+}
+
+// generate smoothSteps points for the line segment connecting
+// each consecutive pair of points in x(i), y(i).
+// adapted from the source for tk8.1b3, http://www.scriptics.com
+Pnglet.prototype.smooth = function(smoothSteps, fNXY, n, x, y) {
+ var control = new Array(8);
+ var outputPoints = 0;
+ var dblPoints = new Array();
+
+ // compute numSteps of smoothed points
+ // according to the basis in control[]
+ // placing points into coordPtr[coordOff]
+ function smoothPoints(control, numSteps, coordPtr, coordOff) {
+ for (var i = 1; i <= numSteps; i++, coordOff += 2) {
+ var t = i/numSteps, t2 = t*t, t3 = t2*t,
+ u = 1.0 - t, u2 = u*u, u3 = u2*u;
+ coordPtr[coordOff+0] = control[0]*u3 + 3.0 * (control[2]*t*u2 + control[4]*t2*u) + control[6]*t3;
+ coordPtr[coordOff+1] = control[1]*u3 + 3.0 * (control[3]*t*u2 + control[5]*t2*u) + control[7]*t3;
+ }
+ };
+
+ /*
+ * If the curve is a closed one then generate a special spline
+ * that spans the last points and the first ones. Otherwise
+ * just put the first point into the output.
+ */
+
+ var closed = (x(0) == x(n-1)) && (y(0) == y(n-1));
+ if (closed) {
+ control[0] = 0.5*x(n-2) + 0.5*x(0);
+ control[1] = 0.5*y(n-2) + 0.5*y(0);
+ control[2] = 0.167*x(n-2) + 0.833*x(0);
+ control[3] = 0.167*y(n-2) + 0.833*y(0);
+ control[4] = 0.833*x(0) + 0.167*x(1);
+ control[5] = 0.833*y(0) + 0.167*y(1);
+ control[6] = 0.5*x(0) + 0.5*x(1);
+ control[7] = 0.5*y(0) + 0.5*y(1);
+ dblPoints[2*outputPoints+0] = control[0];
+ dblPoints[2*outputPoints+1] = control[1];
+ outputPoints += 1;
+ smoothPoints(control, smoothSteps, dblPoints, 2*outputPoints);
+ outputPoints += smoothSteps;
+ } else {
+ dblPoints[2*outputPoints+0] = x(0);
+ dblPoints[2*outputPoints+1] = y(0);
+ outputPoints += 1;
+ }
+
+ for (var i = 2; i < n; i += 1) {
+ var j = i - 2;
+ /*
+ * Set up the first two control points. This is done
+ * differently for the first spline of an open curve
+ * than for other cases.
+ */
+ if ((i == 2) && !closed) {
+ control[0] = x(j);
+ control[1] = y(j);
+ control[2] = 0.333*x(j) + 0.667*x(j+1);
+ control[3] = 0.333*y(j) + 0.667*y(j+1);
+ } else {
+ control[0] = 0.5*x(j) + 0.5*x(j+1);
+ control[1] = 0.5*y(j) + 0.5*y(j+1);
+ control[2] = 0.167*x(j) + 0.833*x(j+1);
+ control[3] = 0.167*y(j) + 0.833*y(j+1);
+ }
+
+ /*
+ * Set up the last two control points. This is done
+ * differently for the last spline of an open curve
+ * than for other cases.
+ */
+
+ if ((i == (n-1)) && !closed) {
+ control[4] = .667*x(j+1) + .333*x(j+2);
+ control[5] = .667*y(j+1) + .333*y(j+2);
+ control[6] = x(j+2);
+ control[7] = y(j+2);
+ } else {
+ control[4] = .833*x(j+1) + .167*x(j+2);
+ control[5] = .833*y(j+1) + .167*y(j+2);
+ control[6] = 0.5*x(j+1) + 0.5*x(j+2);
+ control[7] = 0.5*y(j+1) + 0.5*y(j+2);
+ }
+
+ /*
+ * If the first two points coincide, or if the last
+ * two points coincide, then generate a single
+ * straight-line segment by outputting the last control
+ * point.
+ */
+
+ if (((x(j) == x(j+1)) && (y(j) == y(j+1)))
+ || ((x(j+1) == x(j+2)) && (y(j+1) == y(j+2)))) {
+ dblPoints[2*outputPoints+0] = control[6];
+ dblPoints[2*outputPoints+1] = control[7];
+ outputPoints += 1;
+ continue;
+ }
+
+ /*
+ * Generate a Bezier spline using the control points.
+ */
+ smoothPoints(control, smoothSteps, dblPoints, 2*outputPoints);
+ outputPoints += smoothSteps;
+ }
+
+ // draw the points
+ // anonymous functions don't work here
+ // they result in "undefined" point values
+ function myx(i) { return Math.round(dblPoints[2*i]); }
+ function myy(i) { return Math.round(dblPoints[2*i+1]); }
+ fNXY(outputPoints, myx, myy);
+}
+
+// output a PNG string
+Pnglet.prototype.output = function() {
+ // output translations
+ function initialize(png, offs, str) {
+ for (var i = 1; i < arguments.length; i += 1)
+ if (typeof arguments[i].length != "undefined")
+ for (var j = 0; j < arguments[i].length; j += 1)
+ png[offs++] = arguments[i].charAt(j);
+ }
+ function byte4(w) { return String.fromCharCode((w>>24)&255, (w>>16)&255, (w>>8)&255, w&255); }
+
+ // compute adler32 of output pixels + row filter bytes
+ var BASE = 65521; /* largest prime smaller than 65536 */
+ var NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+ var s1 = 1;
+ var s2 = 0;
+ var n = NMAX;
+ for (var y = 0; y < this.height; y += 1)
+ for (var x = -1; x < this.width; x += 1) {
+ s1 += this.png[this.index(x,y)].charCodeAt(0);
+ s2 += s1;
+ if ((n -= 1) == 0) {
+ s1 %= BASE;
+ s2 %= BASE;
+ n = NMAX;
+ }
+ }
+ s1 %= BASE;
+ s2 %= BASE;
+ initialize(this.png, this.idat_offs+this.idat_size-8, byte4((s2 << 16) | s1));
+
+ // compute crc32 of the PNG chunks
+ function crc32(png, offs, size) {
+ var crc = -1; // initialize crc
+ for (var i = 4; i < size-4; i += 1)
+ crc = Pnglet.crc32_table[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);
+ initialize(png, offs+size-4, byte4(crc ^ -1));
+ }
+
+ crc32(this.png, this.ihdr_offs, this.ihdr_size);
+ crc32(this.png, this.plte_offs, this.plte_size);
+ crc32(this.png, this.trns_offs, this.trns_size);
+ crc32(this.png, this.idat_offs, this.idat_size);
+ crc32(this.png, this.iend_offs, this.iend_size);
+
+ // convert PNG to string
+ return "\211PNG\r\n\032\n"+this.png.join('');
+}
+
+/* Table of CRCs of all 8-bit messages. */
+Pnglet.crc32_table = new Array(256);
+for (var n = 0; n < 256; n++) {
+ var c = n;
+ for (var k = 0; k < 8; k++) {
+ if (c & 1)
+ c = -306674912 ^ ((c >> 1) & 0x7fffffff);
+ else
+ c = (c >> 1) & 0x7fffffff;
+ }
+ Pnglet.crc32_table[n] = c;
+}
+</script>
+</head>
+
+<body>
+<p>Should see a light green rectangle:</p>
+<div id=result></div>
+<script>
+ pngdata = new Pnglet(1,1,1);
+ pngdata.point(pngdata.color(0,255,0,127),1,1);
+
+ png = document.createElement("img");
+ png.style.height = "100px";
+ png.style.width = "100px";
+
+ png.src = "data:image/png;base64," + btoa(pngdata.output());
+ document.getElementById('result').appendChild(png);
+</script>
+</body>
\ No newline at end of file
--- /dev/null
+/*
+ Copyright (C) 2000-2001 Dawit Alemayehu <adawit@kde.org>
+ Copyright (C) 2006 Alexey Proskuryakov <ap@webkit.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License (LGPL)
+ version 2 as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ This code is based on the java implementation in HTTPClient
+ package by Ronald Tschalär Copyright (C) 1996-1999.
+*/
+
+#include "config.h"
+#include "Base64.h"
+
+#include <wtf/platform.h>
+#include <wtf/StringExtras.h>
+
+static const char base64EncMap[64] =
+{
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
+ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
+ 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
+ 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
+ 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F
+};
+
+static const char base64DecMap[128] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
+ 0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
+ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
+ 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+void base64Encode(const Vector<char>& in, Vector<char>& out, bool insertLFs)
+{
+ // clear out the output buffer
+ out.clear();
+ if (in.isEmpty())
+ return;
+
+ unsigned sidx = 0;
+ unsigned didx = 0;
+ const char* data = in.data();
+ const unsigned len = in.size();
+
+ unsigned out_len = ((len + 2) / 3) * 4;
+
+ // Deal with the 76 characters or less per
+ // line limit specified in RFC 2045 on a
+ // pre request basis.
+ insertLFs = (insertLFs && out_len > 76);
+ if (insertLFs)
+ out_len += ((out_len - 1) / 76);
+
+ int count = 0;
+ out.resize(out_len);
+
+ // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
+ if (len > 1) {
+ while (sidx < len - 2) {
+ if (insertLFs) {
+ if (count && (count % 76) == 0)
+ out[didx++] = '\n';
+ count += 4;
+ }
+ out[didx++] = base64EncMap[(data[sidx] >> 2) & 077];
+ out[didx++] = base64EncMap[(data[sidx + 1] >> 4) & 017 | (data[sidx] << 4) & 077];
+ out[didx++] = base64EncMap[(data[sidx + 2] >> 6) & 003 | (data[sidx + 1] << 2) & 077];
+ out[didx++] = base64EncMap[data[sidx + 2] & 077];
+ sidx += 3;
+ }
+ }
+
+ if (sidx < len) {
+ if (insertLFs && (count > 0) && (count % 76) == 0)
+ out[didx++] = '\n';
+
+ out[didx++] = base64EncMap[(data[sidx] >> 2) & 077];
+ if (sidx < len - 1) {
+ out[didx++] = base64EncMap[(data[sidx + 1] >> 4) & 017 | (data[sidx] << 4) & 077];
+ out[didx++] = base64EncMap[(data[sidx + 1] << 2) & 077];
+ } else
+ out[didx++] = base64EncMap[(data[sidx] << 4) & 077];
+ }
+
+ // Add padding
+ while (didx < out.size()) {
+ out[didx] = '=';
+ didx++;
+ }
+}
+
+bool base64Decode(const Vector<char>& in, Vector<char>& out)
+{
+ out.clear();
+ if (in.isEmpty())
+ return true;
+
+ unsigned len = in.size();
+ const char* data = in.data();
+
+ while (len && data[len-1] == '=')
+ --len;
+
+ out.resize(len);
+ for (unsigned idx = 0; idx < len; idx++) {
+ unsigned char ch = data[idx];
+ if ((ch > 47 && ch < 58) || (ch > 64 && ch < 91) || (ch > 96 && ch < 123) || ch == '+' || ch == '/' || ch == '=')
+ out[idx] = base64DecMap[ch];
+ else
+ return false;
+ }
+
+ // 4-byte to 3-byte conversion
+ unsigned outLen = len - ((len + 3) / 4);
+ if (!outLen || ((outLen + 2) / 3) * 4 < len)
+ return false;
+
+ unsigned sidx = 0;
+ unsigned didx = 0;
+ if (outLen > 1) {
+ while (didx < outLen - 2) {
+ out[didx] = (((out[sidx] << 2) & 255) | ((out[sidx + 1] >> 4) & 003));
+ out[didx + 1] = (((out[sidx + 1] << 4) & 255) | ((out[sidx + 2] >> 2) & 017));
+ out[didx + 2] = (((out[sidx + 2] << 6) & 255) | (out[sidx + 3] & 077));
+ sidx += 4;
+ didx += 3;
+ }
+ }
+
+ if (didx < outLen)
+ out[didx] = (((out[sidx] << 2) & 255) | ((out[sidx + 1] >> 4) & 003));
+
+ if (++didx < outLen)
+ out[didx] = (((out[sidx + 1] << 4) & 255) | ((out[sidx + 2] >> 2) & 017));
+
+ if (outLen < out.size())
+ out.resize(outLen);
+
+ return true;
+}