4 // PNG drawing library for JavaScript.
5 // Copyright (C) 1999 by Roger E Critchlow Jr,
6 // Santa Fe, New Mexico, USA.
8 // Licensed under the Academic Free License version 2.1
10 // The home page for Pnglets is http://www.elf.org/pnglets,
11 // a copy of the AFL may be found at http://www.opensource.org/licenses/afl-2.1.php,
12 // Pnglets were inspired by and copied from gd1.3, http://www.boutell.com/gd,
13 // other parts were inspired by or copied from Tcl/Tk, http://www.scriptics.com,
14 // and some algorithms were taken from Foley & van Dam 2nd Edition.
16 // Thanks to Alex Vincent for pointing out the advantages of eliminating strict
17 // javascript warnings.
20 // create a new Pnglet of specified width, height, and depth
21 // width and height are specified in pixels
22 // depth is really the number of palette entries
23 function Pnglet(width
,height
,depth
) {
24 this.width
= width
|| 16;
25 this.height
= height
|| 16;
26 this.depth
= Math
.min(256, depth
|| 16);
28 // pixel data and row filter identifier size
29 this.pix_size
= height
*(width
+1);
31 // deflate header, pix_size, block headers, adler32 checksum
32 this.data_size
= 2 + this.pix_size
+ 5*Math
.floor((this.pix_size
+0xffff-1)/0xffff) + 4;
34 // offsets and sizes of Png chunks
35 this.ihdr_offs
= 0; // IHDR offset and size
36 this.ihdr_size
= 4+4+13+4;
37 this.plte_offs
= this.ihdr_offs
+this.ihdr_size
; // PLTE offset and size
38 this.plte_size
= 4+4+3*depth
+4;
39 this.trns_offs
= this.plte_offs
+this.plte_size
; // tRNS offset and size
40 this.trns_size
= 4+4+depth
+4;
41 this.idat_offs
= this.trns_offs
+this.trns_size
; // IDAT offset and size
42 this.idat_size
= 4+4+this.data_size
+4;
43 this.iend_offs
= this.idat_offs
+this.idat_size
; // IEND offset and size
44 this.iend_size
= 4+4+4;
45 this.png_size
= this.iend_offs
+this.iend_size
; // total PNG size
47 // array of one byte strings
48 this.png
= new Array(this.png_size
);
50 // functions for initializing data
51 function initialize(png
, offs
, str
) {
52 for (var i
= 1; i
< arguments
.length
; i
+= 1)
53 if (typeof arguments
[i
].length
!= "undefined")
54 for (var j
= 0; j
< arguments
[i
].length
; j
+= 1)
55 png
[offs
++] = arguments
[i
].charAt(j
);
57 function byte2(w
) { return String
.fromCharCode((w
>>8)&255, w
&255); };
58 function byte4(w
) { return String
.fromCharCode((w
>>24)&255, (w
>>16)&255, (w
>>8)&255, w
&255); };
59 function byte2lsb(w
) { return String
.fromCharCode(w
&255, (w
>>8)&255); };
61 // initialize everything to zero byte
62 for (var i
= 0; i
< this.png_size
; i
+= 1)
63 this.png
[i
] = String
.fromCharCode(0);
65 // initialize non-zero elements
66 initialize(this.png
, this.ihdr_offs
, byte4(this.ihdr_size
-12), 'IHDR',
67 byte4(width
), byte4(height
), String
.fromCharCode(8, 3));
68 initialize(this.png
, this.plte_offs
, byte4(this.plte_size
-12), 'PLTE');
69 initialize(this.png
, this.trns_offs
, byte4(this.trns_size
-12), 'tRNS');
70 initialize(this.png
, this.idat_offs
, byte4(this.idat_size
-12), 'IDAT');
71 initialize(this.png
, this.iend_offs
, byte4(this.iend_size
-12), 'IEND');
73 // initialize deflate header
74 var header
= ((8 + (7<<4)) << 8) | (3 << 6);
75 header
+= 31 - (header
% 31);
76 initialize(this.png
, this.idat_offs
+8, byte2(header
));
78 // initialize deflate block headers
79 for (i
= 0; i
*0xffff < this.pix_size
; i
+= 1) {
81 if (i
+ 0xffff < this.pix_size
) {
83 bits
= String
.fromCharCode(0);
85 size
= this.pix_size
- i
*0xffff;
86 bits
= String
.fromCharCode(1);
88 initialize(this.png
, this.idat_offs
+8+2+i
*(5+0xffff), bits
, byte2lsb(size
), byte2lsb(~size
));
91 // initialize palette hash
92 this.palette
= new Object();
96 // version string/number
97 Pnglet
.version
= "19990427.0";
99 // test if coordinates are within bounds
100 Pnglet
.prototype.inBounds = function(x
,y
) { return x
>= 0 && x
< this.width
&& y
>= 0 && y
< this.height
; }
102 // clip an x value to the window width
103 Pnglet
.prototype.clipX = function(x
) { return (x
< 0) ? 0 : (x
>= this.width
) ? this.width
-1 : x
; }
105 // clip a y value to the window height
106 Pnglet
.prototype.clipY = function(y
) { return (y
< 0) ? 0 : (y
>= this.height
) ? this.height
-1 : y
; }
108 // compute the index into a png for a given pixel
109 Pnglet
.prototype.index = function(x
,y
) {
110 var i
= y
*(this.width
+1)+x
+1;
111 var j
= this.idat_offs
+8+2+Math
.floor((i
/0xffff)+1)*5+i
;
115 // make a color in a Pnglet
116 Pnglet
.prototype.color = function(red
, green
, blue
, alpha
) {
117 alpha
= alpha
>= 0 ? alpha
: 255;
118 var rgba
= (((((alpha
<<8)+red
)<<8)+green
)<<8)+blue
;
119 if ( typeof this.palette
[rgba
] == "undefined") {
120 if (this.pindex
== this.depth
) return String
.fromCharCode(0);
121 this.palette
[rgba
] = String
.fromCharCode(this.pindex
);
122 this.png
[this.plte_offs
+8+this.pindex
*3+0] = String
.fromCharCode(red
);
123 this.png
[this.plte_offs
+8+this.pindex
*3+1] = String
.fromCharCode(green
);
124 this.png
[this.plte_offs
+8+this.pindex
*3+2] = String
.fromCharCode(blue
);
125 this.png
[this.trns_offs
+8+this.pindex
] = String
.fromCharCode(alpha
);
128 return this.palette
[rgba
];
131 // return true if this is a color
132 Pnglet
.prototype.isColor = function(color
) {
133 return typeof(color
) == 'string' &&
135 color
.charCodeAt(0) >= 0 &&
136 color
.charCodeAt(0) < this.depth
;
139 // find the red, green, blue, or alpha value of a Pnglet color
140 Pnglet
.prototype.red = function(color
) { return this.png
[this.plte_offs
+8+color
.charCodeAt(0)*3+0].charCodeAt(0); }
141 Pnglet
.prototype.green = function(color
) { return this.png
[this.plte_offs
+8+color
.charCodeAt(0)*3+1].charCodeAt(0); }
142 Pnglet
.prototype.blue = function(color
) { return this.png
[this.plte_offs
+8+color
.charCodeAt(0)*3+2].charCodeAt(0); }
143 Pnglet
.prototype.alpha = function(color
) { return this.png
[this.trns_offs
+8+color
.charCodeAt(0)].charCodeAt(0); }
145 // draw a point or points
146 Pnglet
.prototype.point = function(pointColor
, x0
, y0
) {
148 this.pointNXY(pointColor
, (a
.length
-1)/2, function(i
) { return a
[2*i
+1]; }, function(i
) { return a
[2*i
+2]; });
151 Pnglet
.prototype.pointNXY = function(pointColor
, n
, x
, y
) {
152 if ( ! this.isColor(pointColor
))
154 for (var i
= 0; i
< n
; i
+= 1) {
155 var x1
= x(i
), y1
= y(i
);
156 if (this.inBounds(x1
,y1
))
157 this.png
[this.index(x1
,y1
)] = pointColor
;
162 Pnglet
.prototype.getPoint = function(x
,y
) { return this.inBounds(x
,y
) ? this.png
[this.index(x
,y
)] : String
.fromCharCode(0); }
164 // draw a horizontal line
165 Pnglet
.prototype.horizontalLine = function(lineColor
, x1
, x2
, y
) {
166 if ( ! this.isColor(lineColor
))
172 for (x
= x1
; x
<= x2
; x
+= 1)
173 this.png
[this.index(x
,y
)] = lineColor
;
175 for (x
= x2
; x
<= x1
; x
+= 1)
176 this.png
[this.index(x
,y
)] = lineColor
;
179 // draw a vertical line
180 Pnglet
.prototype.verticalLine = function(lineColor
, x
, y1
, y2
) {
181 if ( ! this.isColor(lineColor
))
187 for (y
= y1
; y
<= y2
; y
+= 1)
188 this.png
[this.index(x
,y
)] = lineColor
;
190 for (y
= y2
; y
<= y1
; y
+= 1)
191 this.png
[this.index(x
,y
)] = lineColor
;
194 // draw a general line
195 Pnglet
.prototype.generalLine = function(lineColor
, x1
, y1
, x2
, y2
) {
196 if ( ! this.isColor(lineColor
))
198 var dx
= Math
.abs(x2
-x1
), dy
= Math
.abs(y2
-y1
);
199 var incr1
, incr2
, d
, x
, y
, xend
, yend
, xdirflag
, ydirflag
, xinc
, yinc
;
203 incr2
= 2 * (dy
- dx
);
215 yinc
= (((y2
- y1
) * ydirflag
) > 0) ? 1 : -1;
216 this.point(lineColor
, x
, y
);
224 this.point(lineColor
, x
, y
);
226 } else { /* dy > dx */
229 incr2
= 2 * (dx
- dy
);
241 xinc
= (((x2
- x1
) * xdirflag
) > 0) ? 1 : -1;
242 this.point(lineColor
, x
, y
);
250 this.point(lineColor
, x
, y
);
256 Pnglet
.prototype.line = function(lineColor
, x0
, y0
) {
258 this.lineNXY(lineColor
, (a
.length
-1)/2, function(i
) { return a
[2*i
+1]; }, function(i
) { return a
[2*i
+2]; });
261 Pnglet
.prototype.lineNXY = function(lineColor
, n
, x
, y
) {
262 if ( ! this.isColor(lineColor
))
264 var x1
= x(0), y1
= y(0);
265 for (var i
= 1; i
< n
; i
+= 1) {
266 var x2
= x(i
), y2
= y(i
);
268 this.verticalLine(lineColor
, x1
, y1
, y2
);
270 this.horizontalLine(lineColor
, x1
, x2
, y1
);
272 this.generalLine(lineColor
, x1
, y1
, x2
, y2
);
279 Pnglet
.prototype.polygon = function(outlineColor
, fillColor
, x1
, y1
) {
281 this.polygonNXY(outlineColor
, fillColor
, (a
.length
-2)/2, function(i
) {return a
[2*i
+2];}, function(i
) {return a
[2*i
+3];});
284 Pnglet
.prototype.polygonNXY = function(outlineColor
, fillColor
, n
, x
, y
) {
287 if (this.isColor(fillColor
))
288 this.concaveNXY(fillColor
, n
, x
, y
);
289 if (this.isColor(outlineColor
))
290 this.lineNXY(outlineColor
, n
+1, function(i
) { return x(i
%n
); }, function(i
) { return y(i
%n
); });
294 * Concave Polygon Scan Conversion
296 * from "Graphics Gems", Academic Press, 1990
298 Pnglet
.prototype.concaveNXY = function(fillColor
, n
, ptx
, pty
) {
299 function Edge(ex
, edx
, ei
) { /* a polygon edge */
300 this.x
= ex
; /* x coordinate of edge's intersection with current scanline */
301 this.dx
= edx
; /* change in x with respect to y */
302 this.i
= ei
; /* edge number: edge i goes from pt[i] to pt[i+1] */
304 function cdelete(di
) { /* remove edge i from active list */
305 for (var j
= 0; j
< active
.length
; j
+= 1)
306 if (active
[j
].i
== di
)
309 function cinsert(ii
, iy
) { /* append edge i to end of active list */
310 var ij
= ii
<n
-1 ? ii
+1 : 0;
312 if (pty(ii
) < pty(ij
)) {
313 px
= ptx(ii
); py
= pty(ii
);
314 qx
= ptx(ij
); qy
= pty(ij
);
316 px
= ptx(ij
); py
= pty(ij
);
317 qx
= ptx(ii
); qy
= pty(ii
);
319 /* initialize x position at intersection of edge with scanline y */
320 var dx
= (qx
-px
)/(qy
-py
);
321 active
.push(new Edge(dx
*(iy
+.5-py
)+px
, dx
, ii
));
324 var ind
= new Array(n
); /* list of vertex indices, sorted by pt[ind[j]].y */
325 var active
= new Array(0); /* start with an empty active list */
327 /* create y-sorted array of indices ind[k] into vertex list */
328 for (var k
= 0; k
< n
; k
+= 1) ind
[k
] = k
;
329 ind
.sort(function(i1
, i2
) { return pty(i1
) <= pty(i2
) ? -1 : 1; });
330 k
= 0; /* ind[k] is next vertex to process */
331 var y0
= Math
.max(0, Math
.ceil(pty(ind
[0])+.5)); /* ymin of polygon */
332 var y1
= Math
.min(this.height
, Math
.floor(pty(ind
[n
-1])-.5)); /* ymax of polygon */
334 for (var y
= y0
; y
<= y1
; y
+= 1) { /* step through scanlines */
335 /* scanline y is at y+.5 in continuous coordinates */
337 /* check vertices between previous scanline and current one, if any */
338 for (; k
<n
&& pty(ind
[k
]) <= y
+.5; k
+= 1) {
339 /* to simplify, if pt.y=y+.5, pretend it's above */
340 /* invariant: y-.5 < pt[i].y <= y+.5 */
344 * insert or delete edges before and after vertex i (i-1 to i,
345 * and i to i+1) from active list if they cross scanline y
347 var j
= (i
-1+n
)%n
; /* vertex previous to i */
348 if (pty(j
) <= y
-.5) { /* old edge, remove from active list */
350 } else if (pty(j
) > y
+.5) { /* new edge, add to active list */
354 alert("Your browser's implementation of JavaScript is seriously broken,\n"+
355 "as in variables are changing value of their own volition.\n"+
356 "You should upgrade to a newer version browser.");
359 j
= (i
+1)%n
; /* vertex next after i */
360 if (pty(j
) <= y
-.5) { /* old edge, remove from active list */
362 } else if (pty(j
) > y
+.5) { /* new edge, add to active list */
367 /* sort active edge list by active[j].x */
368 active
.sort(function(u
,v
) { return u
.x
<= v
.x
? -1 : 1; });
370 /* draw horizontal segments for scanline y */
371 for (j
= 0; j
< active
.length
; j
+= 2) { /* draw horizontal segments */
372 /* span 'tween j & j+1 is inside, span tween j+1 & j+2 is outside */
373 var xl
= Math
.ceil(active
[j
].x
+.5); /* left end of span */
375 var xr
= Math
.floor(active
[j
+1].x
-.5); /* right end of span */
376 if (xr
>this.width
-1) xr
= this.width
-1;
378 this.horizontalLine(fillColor
, xl
, xr
, y
); /* draw pixels in span */
379 active
[j
].x
+= active
[j
].dx
; /* increment edge coords */
380 active
[j
+1].x
+= active
[j
+1].dx
;
386 Pnglet
.prototype.rectangle = function(outlineColor
, fillColor
, x0
,y0
,x1
,y1
) {
387 if (this.isColor(fillColor
))
388 for (var y
= y0
; y
< y1
; y
+= 1)
389 this.horizontalLine(fillColor
, x0
+1, x1
-2, y
);
390 if (this.isColor(outlineColor
)) {
391 this.horizontalLine(outlineColor
, x0
, x1
-1, y0
);
392 this.horizontalLine(outlineColor
, x0
, x1
-1, y1
-1);
393 this.verticalLine(outlineColor
, x0
, y0
, y1
-1);
394 this.verticalLine(outlineColor
, x1
-1, y0
, y1
-1);
399 Pnglet
.prototype.arc = function(outlineColor
, cx
,cy
,w
,h
, s
,e
) {
400 var p
= this.midpointEllipse(cx
,cy
, w
,h
, s
,e
);
401 function x(i
) { return p
[i
*2]; };
402 function y(i
) { return p
[i
*2+1]; };
403 this.lineNXY(outlineColor
, p
.length
/2, x
, y
);
407 Pnglet
.prototype.oval = function(outlineColor
, fillColor
, cx
,cy
,w
,h
) {
408 var p
= this.midpointEllipse(cx
,cy
, w
,h
, 0,359);
409 function x(i
) { return p
[i
*2]; };
410 function y(i
) { return p
[i
*2+1]; };
411 this.polygonNXY(outlineColor
, fillColor
, p
.length
/2, x
, y
);
414 // draw an arc with chord
415 Pnglet
.prototype.chord = function(outlineColor
, fillColor
, cx
,cy
,w
,h
, s
,e
) {
416 var p
= this.midpointEllipse(cx
,cy
, w
,h
, s
,e
);
417 function x(i
) { return p
[i
*2]; };
418 function y(i
) { return p
[i
*2+1]; };
419 this.polygonNXY(outlineColor
, fillColor
, p
.length
/2, x
, y
);
422 // draw an arc with pieslice
423 Pnglet
.prototype.pieslice = function(outlineColor
, fillColor
, cx
,cy
,w
,h
, s
,e
) {
424 var p
= this.midpointEllipse(cx
,cy
, w
,h
, s
,e
);
427 function x(i
) { return p
[i
*2]; };
428 function y(i
) { return p
[i
*2+1]; };
429 this.polygonNXY(outlineColor
, fillColor
, p
.length
/2, x
, y
);
433 // generate points of oval circumference
434 // midpoint ellipse, Foley & van Dam, 2nd Edition, p. 90, 1990
435 Pnglet
.prototype.midpointEllipse = function(cx
,cy
, w
,h
, s
,e
) {
436 var a
= Math
.floor(w
/2), b = Math.floor(h/2);
437 var a2
= a
*a
, b2
= b
*b
, x
= 0, y
= b
;
438 var d1
= b2
- a2
*b
+ a2
/4;
443 // quadrant I, anticlockwise
445 while (a2
*(y
-1/2) > b2
*(x
+1)) {
449 d1
+= b2
*(2*x
+3) + a2
*(-2*y
+ 2);
455 var d2
= b2
*(x
+1/2)*(x+1/2) + a2
*(y
-1)*(y
-1) - a2
*b2
;
458 d2
+= b2
*(2*x
+2) + a2
*(-2*y
+3);
466 // quadrant II, anticlockwise
468 for (var i
= n4
-4; i
>= 0; i
-= 2)
469 p
.push(-p
[i
], p
[i
+1]);
470 // quadrants III and IV, anticlockwise
472 for (i
= n2
-4; i
> 0; i
-= 2)
473 p
.push(p
[i
], -p
[i
+1]);
475 // compute start and end indexes from start and extent
484 var is
= Math
.floor(s
/359 * p.length/2);
485 var ie
= Math
.floor(e
/359 * p.length/2)+1;
486 p
= p
.slice(is
*2).concat(p
.slice(0, is
*2)).slice(0, ie
*2);
488 // displace to center
489 for (i
= 0; i
< p
.length
; i
+= 2) {
497 // from gd1.3 with modifications
498 Pnglet
.prototype.fill = function(outlineColor
,fillColor
,x
,y
) {
499 if (outlineColor
) { // fill to outline color
502 for (var i
= x
; i
>= 0 && this.getPoint(i
, y
) != outlineColor
; i
-= 1)
510 for (i
= (x
+1); i
< this.width
&& this.getPoint(i
, y
) != outlineColor
; i
+= 1)
513 /* fill extent found */
514 this.horizontalLine(fillColor
, leftLimit
, rightLimit
, y
);
516 /* Seek above and below */
517 for (var dy
= -1; dy
<= 1; dy
+= 2) {
518 if (this.inBounds(x
,y
+dy
)) {
520 for (i
= leftLimit
; i
<= rightLimit
; i
++) {
521 var c
= this.getPoint(i
, y
+dy
);
523 if ((c
!= outlineColor
) && (c
!= fillColor
)) {
524 this.fill(outlineColor
, fillColor
, i
, y
+dy
);
527 } else if ((c
== outlineColor
) || (c
== fillColor
)) {
534 } else { // flood fill color at x, y
535 /* Test for completion */
536 var oldColor
= this.getPoint(x
, y
);
537 if (oldColor
== fillColor
)
542 for (i
= x
; i
>= 0 && this.getPoint(i
, y
) == oldColor
; i
--)
550 for (i
= (x
+1); i
< this.width
&& this.getPoint(i
, y
) == oldColor
; i
++)
553 /* Fill extent found */
554 this.horizontalLine(fillColor
, leftLimit
, rightLimit
, y
);
556 /* Seek above and below */
557 for (dy
= -1; dy
<= 1; dy
+= 2) {
558 if (this.inBounds(x
,y
+dy
)) {
560 for (i
= leftLimit
; i
<= rightLimit
; i
++) {
561 c
= this.getPoint(i
, y
+dy
);
564 this.fill(null, fillColor
, i
, y
+dy
);
567 } else if (c
!= oldColor
) {
577 Pnglet
.prototype.smoothPoint = function(smoothSteps
, pointColor
, x0
, y0
) {
578 var a
= arguments
, self
= this, n
= (a
.length
-2)/2;
579 this.smooth(smoothSteps
,
580 function(n
, x
, y
) { self
.pointNXY(pointColor
, n
, x
, y
); },
582 function(i
) { return a
[2*i
+2]; },
583 function(i
) { return a
[2*i
+3]; });
587 Pnglet
.prototype.smoothLine = function(smoothSteps
, lineColor
, x0
, y0
) {
588 var a
= arguments
, self
= this, n
= (a
.length
-2)/2;
589 this.smooth(smoothSteps
,
590 function(n
, x
, y
) { self
.lineNXY(lineColor
, n
, x
, y
); },
592 function(i
) { return a
[2*i
+2]; },
593 function(i
) { return a
[2*i
+3]; });
597 Pnglet
.prototype.smoothPolygon = function(smoothSteps
, outlineColor
, fillColor
, x0
, y0
) {
598 var a
= arguments
, self
= this, n
= (a
.length
-3)/2 + 1;
599 this.smooth(smoothSteps
,
600 function(n
, x
, y
) { self
.polygonNXY(outlineColor
, fillColor
, n
, x
, y
); },
602 function(i
) { return a
[2*(i
%(n
-1))+3]; },
603 function(i
) { return a
[2*(i
%(n
-1))+4]; });
606 // generate smoothSteps points for the line segment connecting
607 // each consecutive pair of points in x(i), y(i).
608 // adapted from the source for tk8.1b3, http://www.scriptics.com
609 Pnglet
.prototype.smooth = function(smoothSteps
, fNXY
, n
, x
, y
) {
610 var control
= new Array(8);
611 var outputPoints
= 0;
612 var dblPoints
= new Array();
614 // compute numSteps of smoothed points
615 // according to the basis in control[]
616 // placing points into coordPtr[coordOff]
617 function smoothPoints(control
, numSteps
, coordPtr
, coordOff
) {
618 for (var i
= 1; i
<= numSteps
; i
++, coordOff
+= 2) {
619 var t
= i
/numSteps
, t2
= t
*t
, t3
= t2
*t
,
620 u
= 1.0 - t
, u2
= u
*u
, u3
= u2
*u
;
621 coordPtr
[coordOff
+0] = control
[0]*u3
+ 3.0 * (control
[2]*t
*u2
+ control
[4]*t2
*u
) + control
[6]*t3
;
622 coordPtr
[coordOff
+1] = control
[1]*u3
+ 3.0 * (control
[3]*t
*u2
+ control
[5]*t2
*u
) + control
[7]*t3
;
627 * If the curve is a closed one then generate a special spline
628 * that spans the last points and the first ones. Otherwise
629 * just put the first point into the output.
632 var closed
= (x(0) == x(n
-1)) && (y(0) == y(n
-1));
634 control
[0] = 0.5*x(n
-2) + 0.5*x(0);
635 control
[1] = 0.5*y(n
-2) + 0.5*y(0);
636 control
[2] = 0.167*x(n
-2) + 0.833*x(0);
637 control
[3] = 0.167*y(n
-2) + 0.833*y(0);
638 control
[4] = 0.833*x(0) + 0.167*x(1);
639 control
[5] = 0.833*y(0) + 0.167*y(1);
640 control
[6] = 0.5*x(0) + 0.5*x(1);
641 control
[7] = 0.5*y(0) + 0.5*y(1);
642 dblPoints
[2*outputPoints
+0] = control
[0];
643 dblPoints
[2*outputPoints
+1] = control
[1];
645 smoothPoints(control
, smoothSteps
, dblPoints
, 2*outputPoints
);
646 outputPoints
+= smoothSteps
;
648 dblPoints
[2*outputPoints
+0] = x(0);
649 dblPoints
[2*outputPoints
+1] = y(0);
653 for (var i
= 2; i
< n
; i
+= 1) {
656 * Set up the first two control points. This is done
657 * differently for the first spline of an open curve
658 * than for other cases.
660 if ((i
== 2) && !closed
) {
663 control
[2] = 0.333*x(j
) + 0.667*x(j
+1);
664 control
[3] = 0.333*y(j
) + 0.667*y(j
+1);
666 control
[0] = 0.5*x(j
) + 0.5*x(j
+1);
667 control
[1] = 0.5*y(j
) + 0.5*y(j
+1);
668 control
[2] = 0.167*x(j
) + 0.833*x(j
+1);
669 control
[3] = 0.167*y(j
) + 0.833*y(j
+1);
673 * Set up the last two control points. This is done
674 * differently for the last spline of an open curve
675 * than for other cases.
678 if ((i
== (n
-1)) && !closed
) {
679 control
[4] = .667*x(j
+1) + .333*x(j
+2);
680 control
[5] = .667*y(j
+1) + .333*y(j
+2);
684 control
[4] = .833*x(j
+1) + .167*x(j
+2);
685 control
[5] = .833*y(j
+1) + .167*y(j
+2);
686 control
[6] = 0.5*x(j
+1) + 0.5*x(j
+2);
687 control
[7] = 0.5*y(j
+1) + 0.5*y(j
+2);
691 * If the first two points coincide, or if the last
692 * two points coincide, then generate a single
693 * straight-line segment by outputting the last control
697 if (((x(j
) == x(j
+1)) && (y(j
) == y(j
+1)))
698 || ((x(j
+1) == x(j
+2)) && (y(j
+1) == y(j
+2)))) {
699 dblPoints
[2*outputPoints
+0] = control
[6];
700 dblPoints
[2*outputPoints
+1] = control
[7];
706 * Generate a Bezier spline using the control points.
708 smoothPoints(control
, smoothSteps
, dblPoints
, 2*outputPoints
);
709 outputPoints
+= smoothSteps
;
713 // anonymous functions don't work here
714 // they result in "undefined" point values
715 function myx(i
) { return Math
.round(dblPoints
[2*i
]); }
716 function myy(i
) { return Math
.round(dblPoints
[2*i
+1]); }
717 fNXY(outputPoints
, myx
, myy
);
720 // output a PNG string
721 Pnglet
.prototype.output = function() {
722 // output translations
723 function initialize(png
, offs
, str
) {
724 for (var i
= 1; i
< arguments
.length
; i
+= 1)
725 if (typeof arguments
[i
].length
!= "undefined")
726 for (var j
= 0; j
< arguments
[i
].length
; j
+= 1)
727 png
[offs
++] = arguments
[i
].charAt(j
);
729 function byte4(w
) { return String
.fromCharCode((w
>>24)&255, (w
>>16)&255, (w
>>8)&255, w
&255); }
731 // compute adler32 of output pixels + row filter bytes
732 var BASE
= 65521; /* largest prime smaller than 65536 */
733 var NMAX
= 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
737 for (var y = 0; y < this.height; y += 1)
738 for (var x = -1; x < this.width; x += 1) {
739 s1 += this.png[this.index(x,y)].charCodeAt(0);
749 initialize(this.png, this.idat_offs+this.idat_size-8, byte4((s2 << 16) | s1));
751 // compute crc32 of the PNG chunks
752 function crc32(png, offs, size) {
753 var crc = -1; // initialize crc
754 for (var i = 4; i < size-4; i += 1)
755 crc = Pnglet.crc32_table[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);
756 initialize(png, offs+size-4, byte4(crc ^ -1));
759 crc32(this.png, this.ihdr_offs, this.ihdr_size);
760 crc32(this.png, this.plte_offs, this.plte_size);
761 crc32(this.png, this.trns_offs, this.trns_size);
762 crc32(this.png, this.idat_offs, this.idat_size);
763 crc32(this.png, this.iend_offs, this.iend_size);
765 // convert PNG to string
766 return "\211PNG\r\n\032\n"+this.png.join('');
769 /* Table of CRCs of all 8-bit messages. */
770 Pnglet
.crc32_table
= new Array(256);
771 for (var n
= 0; n
< 256; n
++) {
773 for (var k
= 0; k
< 8; k
++) {
775 c
= -306674912 ^ ((c
>> 1) & 0x7fffffff);
777 c
= (c
>> 1) & 0x7fffffff;
779 Pnglet
.crc32_table
[n
] = c
;
785 <p>Should see a light green rectangle:
</p>
786 <div id=result
></div>
788 pngdata
= new Pnglet(1,1,1);
789 pngdata
.point(pngdata
.color(0,255,0,127),1,1);
791 png
= document
.createElement("img");
792 png
.style
.height
= "100px";
793 png
.style
.width
= "100px";
795 png
.src
= "data:image/png;base64," + btoa(pngdata
.output());
796 document
.getElementById('result').appendChild(png
);