blob: a3c3c321032f3a7c88661ab0f0c90d4035ad56bd [file] [log] [blame]
<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>