| <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> |