Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / fast / dom / Window / btoa-pnglet.html
bloba3c3c321032f3a7c88661ab0f0c90d4035ad56bd
1 <head>
2 <script>
3 //
4 // PNG drawing library for JavaScript.
5 // Copyright (C) 1999 by Roger E Critchlow Jr,
6 // Santa Fe, New Mexico, USA.
7 //
8 // Licensed under the Academic Free License version 2.1
9 //
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) {
80 var size, bits;
81 if (i + 0xffff < this.pix_size) {
82 size = 0xffff;
83 bits = String.fromCharCode(0);
84 } else {
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();
93 this.pindex = 0;
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;
112 return j;
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);
126 this.pindex += 1;
128 return this.palette[rgba];
131 // return true if this is a color
132 Pnglet.prototype.isColor = function(color) {
133 return typeof(color) == 'string' &&
134 color.length == 1 &&
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) {
147 var a = arguments;
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))
153 return;
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;
161 // read a pixel
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))
167 return;
168 x1 = this.clipX(x1);
169 x2 = this.clipX(x2);
170 var x;
171 if (x1 < x2)
172 for (x = x1; x <= x2; x += 1)
173 this.png[this.index(x,y)] = lineColor;
174 else
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))
182 return;
183 y1 = this.clipY(y1);
184 y2 = this.clipY(y2);
185 var y;
186 if (y1 < y2)
187 for (y = y1; y <= y2; y += 1)
188 this.png[this.index(x,y)] = lineColor;
189 else
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))
197 return;
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;
200 if (dy <= dx) {
201 d = 2*dy - dx;
202 incr1 = 2*dy;
203 incr2 = 2 * (dy - dx);
204 if (x1 > x2) {
205 x = x2;
206 y = y2;
207 ydirflag = -1;
208 xend = x1;
209 } else {
210 x = x1;
211 y = y1;
212 ydirflag = 1;
213 xend = x2;
215 yinc = (((y2 - y1) * ydirflag) > 0) ? 1 : -1;
216 this.point(lineColor, x, y);
217 while (x++ < xend) {
218 if (d < 0) {
219 d += incr1;
220 } else {
221 y += yinc;
222 d += incr2;
224 this.point(lineColor, x, y);
226 } else { /* dy > dx */
227 d = 2*dx - dy;
228 incr1 = 2*dx;
229 incr2 = 2 * (dx - dy);
230 if (y1 > y2) {
231 y = y2;
232 x = x2;
233 yend = y1;
234 xdirflag = -1;
235 } else {
236 y = y1;
237 x = x1;
238 yend = y2;
239 xdirflag = 1;
241 xinc = (((x2 - x1) * xdirflag) > 0) ? 1 : -1;
242 this.point(lineColor, x, y);
243 while (y++ < yend) {
244 if (d < 0) {
245 d += incr1;
246 } else {
247 x += xinc;
248 d += incr2;
250 this.point(lineColor, x, y);
255 // draw a line
256 Pnglet.prototype.line = function(lineColor, x0, y0) {
257 var a = arguments;
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))
263 return;
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);
267 if (x1 == x2)
268 this.verticalLine(lineColor, x1, y1, y2);
269 else if (y1 == y2)
270 this.horizontalLine(lineColor, x1, x2, y1);
271 else
272 this.generalLine(lineColor, x1, y1, x2, y2);
273 x1 = x2;
274 y1 = y2;
278 // draw a polygon
279 Pnglet.prototype.polygon = function(outlineColor, fillColor, x1, y1) {
280 var a = arguments;
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) {
285 if (n <= 0)
286 return;
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
295 * by Paul Heckbert
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)
307 active.splice(j, 1);
309 function cinsert(ii, iy) { /* append edge i to end of active list */
310 var ij = ii<n-1 ? ii+1 : 0;
311 var px, py, qx, qy;
312 if (pty(ii) < pty(ij)) {
313 px = ptx(ii); py = pty(ii);
314 qx = ptx(ij); qy = pty(ij);
315 } else {
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 */
341 var i = ind[k];
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 */
349 cdelete(j);
350 } else if (pty(j) > y+.5) { /* new edge, add to active list */
351 cinsert(j, y);
353 if (i != ind[k]) {
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.");
357 return;
359 j = (i+1)%n; /* vertex next after i */
360 if (pty(j) <= y-.5) { /* old edge, remove from active list */
361 cdelete(i);
362 } else if (pty(j) > y+.5) { /* new edge, add to active list */
363 cinsert(i, y);
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 */
374 if (xl<0) xl = 0;
375 var xr = Math.floor(active[j+1].x-.5); /* right end of span */
376 if (xr>this.width-1) xr = this.width-1;
377 if (xl<=xr)
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;
385 // draw a rectangle
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);
398 // draw an arc
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);
406 // draw an oval
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);
425 p[p.length] = cx;
426 p[p.length] = cy;
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);
432 // oval arcs
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;
439 cx = Math.floor(cx);
440 cy = Math.floor(cy);
441 var p = new Array();
443 // quadrant I, anticlockwise
444 p.push(x,-y);
445 while (a2*(y-1/2) > b2*(x+1)) {
446 if (d1 < 0) {
447 d1 += b2*(2*x+3);
448 } else {
449 d1 += b2*(2*x+3) + a2*(-2*y + 2);
450 y -= 1;
452 x += 1;
453 p.unshift(x,-y);
455 var d2 = b2*(x+1/2)*(x+1/2) + a2*(y-1)*(y-1) - a2*b2;
456 while (y > 0) {
457 if (d2 < 0) {
458 d2 += b2*(2*x+2) + a2*(-2*y+3);
459 x += 1;
460 } else {
461 d2 += a2*(-2*y+3);
463 y -= 1;
464 p.unshift(x,-y);
466 // quadrant II, anticlockwise
467 var n4 = p.length;
468 for (var i = n4-4; i >= 0; i -= 2)
469 p.push(-p[i], p[i+1]);
470 // quadrants III and IV, anticlockwise
471 var n2 = p.length;
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
476 e %= 360;
477 if (e < 0) {
478 s += e;
479 e = -e;
481 s %= 360;
482 if (s < 0)
483 s += 360;
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) {
490 p[i] += cx;
491 p[i+1] += cy;
493 return p;
496 // fill a region
497 // from gd1.3 with modifications
498 Pnglet.prototype.fill = function(outlineColor,fillColor,x,y) {
499 if (outlineColor) { // fill to outline color
500 /* Seek left */
501 var leftLimit = -1;
502 for (var i = x; i >= 0 && this.getPoint(i, y) != outlineColor; i -= 1)
503 leftLimit = i;
505 if (leftLimit == -1)
506 return;
508 /* Seek right */
509 var rightLimit = x;
510 for (i = (x+1); i < this.width && this.getPoint(i, y) != outlineColor; i += 1)
511 rightLimit = i;
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)) {
519 var lastBorder = 1;
520 for (i = leftLimit; i <= rightLimit; i++) {
521 var c = this.getPoint(i, y+dy);
522 if (lastBorder) {
523 if ((c != outlineColor) && (c != fillColor)) {
524 this.fill(outlineColor, fillColor, i, y+dy);
525 lastBorder = 0;
527 } else if ((c == outlineColor) || (c == fillColor)) {
528 lastBorder = 1;
534 } else { // flood fill color at x, y
535 /* Test for completion */
536 var oldColor = this.getPoint(x, y);
537 if (oldColor == fillColor)
538 return;
540 /* Seek left */
541 leftLimit = (-1);
542 for (i = x; i >= 0 && this.getPoint(i, y) == oldColor; i--)
543 leftLimit = i;
545 if (leftLimit == -1)
546 return;
548 /* Seek right */
549 rightLimit = x;
550 for (i = (x+1); i < this.width && this.getPoint(i, y) == oldColor; i++)
551 rightLimit = 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)) {
559 lastBorder = 1;
560 for (i = leftLimit; i <= rightLimit; i++) {
561 c = this.getPoint(i, y+dy);
562 if (lastBorder) {
563 if (c == oldColor) {
564 this.fill(null, fillColor, i, y+dy);
565 lastBorder = 0;
567 } else if (c != oldColor) {
568 lastBorder = 1;
576 // smoothed points
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]; });
586 // smoothed polyline
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]; });
596 // smoothed polygon
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));
633 if (closed) {
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];
644 outputPoints += 1;
645 smoothPoints(control, smoothSteps, dblPoints, 2*outputPoints);
646 outputPoints += smoothSteps;
647 } else {
648 dblPoints[2*outputPoints+0] = x(0);
649 dblPoints[2*outputPoints+1] = y(0);
650 outputPoints += 1;
653 for (var i = 2; i < n; i += 1) {
654 var j = i - 2;
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) {
661 control[0] = x(j);
662 control[1] = y(j);
663 control[2] = 0.333*x(j) + 0.667*x(j+1);
664 control[3] = 0.333*y(j) + 0.667*y(j+1);
665 } else {
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);
681 control[6] = x(j+2);
682 control[7] = y(j+2);
683 } else {
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
694 * point.
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];
701 outputPoints += 1;
702 continue;
706 * Generate a Bezier spline using the control points.
708 smoothPoints(control, smoothSteps, dblPoints, 2*outputPoints);
709 outputPoints += smoothSteps;
712 // draw the points
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 */
734 var s1 = 1;
735 var s2 = 0;
736 var n = NMAX;
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);
740 s2 += s1;
741 if ((n -= 1) == 0) {
742 s1 %= BASE;
743 s2 %= BASE;
744 n = NMAX;
747 s1 %= BASE;
748 s2 %= BASE;
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++) {
772 var c = n;
773 for (var k = 0; k < 8; k++) {
774 if (c & 1)
775 c = -306674912 ^ ((c >> 1) & 0x7fffffff);
776 else
777 c = (c >> 1) & 0x7fffffff;
779 Pnglet.crc32_table[n] = c;
781 </script>
782 </head>
784 <body>
785 <p>Should see a light green rectangle:</p>
786 <div id=result></div>
787 <script>
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);
797 </script>
798 </body>