2 * A handy class to calculate color values.
5 * @author Robert Eisele <robert@xarg.org>
6 * @copyright Copyright (c) 2010, Robert Eisele
7 * @link http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/
8 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
14 // helper functions for that ctx
15 function write(buffer
, offs
) {
16 for (var i
= 2; i
< arguments
.length
; i
++) {
17 for (var j
= 0; j
< arguments
[i
].length
; j
++) {
18 buffer
[offs
++] = arguments
[i
].charAt(j
);
24 return String
.fromCharCode((w
>> 8) & 255, w
& 255);
28 return String
.fromCharCode((w
>> 24) & 255, (w
>> 16) & 255, (w
>> 8) & 255, w
& 255);
31 function byte2lsb(w
) {
32 return String
.fromCharCode(w
& 255, (w
>> 8) & 255);
35 window
.PNGlib = function(width
,height
,depth
) {
41 // pixel data and row filter identifier size
42 this.pix_size
= height
* (width
+ 1);
44 // deflate header, pix_size, block headers, adler32 checksum
45 this.data_size
= 2 + this.pix_size
+ 5 * Math
.floor((0xfffe + this.pix_size
) / 0xffff) + 4;
47 // offsets and sizes of Png chunks
48 this.ihdr_offs
= 0; // IHDR offset and size
49 this.ihdr_size
= 4 + 4 + 13 + 4;
50 this.plte_offs
= this.ihdr_offs
+ this.ihdr_size
; // PLTE offset and size
51 this.plte_size
= 4 + 4 + 3 * depth
+ 4;
52 this.trns_offs
= this.plte_offs
+ this.plte_size
; // tRNS offset and size
53 this.trns_size
= 4 + 4 + depth
+ 4;
54 this.idat_offs
= this.trns_offs
+ this.trns_size
; // IDAT offset and size
55 this.idat_size
= 4 + 4 + this.data_size
+ 4;
56 this.iend_offs
= this.idat_offs
+ this.idat_size
; // IEND offset and size
57 this.iend_size
= 4 + 4 + 4;
58 this.buffer_size
= this.iend_offs
+ this.iend_size
; // total PNG size
60 this.buffer
= new Array();
61 this.palette
= new Object();
64 var _crc32
= new Array();
66 // initialize buffer with zero bytes
67 for (var i
= 0; i
< this.buffer_size
; i
++) {
68 this.buffer
[i
] = "\x00";
71 // initialize non-zero elements
72 write(this.buffer
, this.ihdr_offs
, byte4(this.ihdr_size
- 12), 'IHDR', byte4(width
), byte4(height
), "\x08\x03");
73 write(this.buffer
, this.plte_offs
, byte4(this.plte_size
- 12), 'PLTE');
74 write(this.buffer
, this.trns_offs
, byte4(this.trns_size
- 12), 'tRNS');
75 write(this.buffer
, this.idat_offs
, byte4(this.idat_size
- 12), 'IDAT');
76 write(this.buffer
, this.iend_offs
, byte4(this.iend_size
- 12), 'IEND');
78 // initialize deflate header
79 var header
= ((8 + (7 << 4)) << 8) | (3 << 6);
80 header
+= 31 - (header
% 31);
82 write(this.buffer
, this.idat_offs
+ 8, byte2(header
));
84 // initialize deflate block headers
85 for (var i
= 0; (i
<< 16) - 1 < this.pix_size
; i
++) {
87 if (i
+ 0xffff < this.pix_size
) {
91 size
= this.pix_size
- (i
<< 16) - i
;
94 write(this.buffer
, this.idat_offs
+ 8 + 2 + (i
<< 16) + (i
<< 2), bits
, byte2lsb(size
), byte2lsb(~size
));
97 /* Create crc32 lookup table */
98 for (var i
= 0; i
< 256; i
++) {
100 for (var j
= 0; j
< 8; j
++) {
102 c
= -306674912 ^ ((c
>> 1) & 0x7fffffff);
104 c
= (c
>> 1) & 0x7fffffff;
110 // compute the index into a png for a given pixel
111 this.index = function(x
,y
) {
112 var i
= y
* (this.width
+ 1) + x
+ 1;
113 var j
= this.idat_offs
+ 8 + 2 + 5 * Math
.floor((i
/ 0xffff) + 1) + i
;
117 // convert a color and build up the palette
118 this.color = function(red
, green
, blue
, alpha
) {
120 alpha
= alpha
>= 0 ? alpha
: 255;
121 var color
= (((((alpha
<< 8) | red
) << 8) | green
) << 8) | blue
;
123 if (typeof this.palette
[color
] == "undefined") {
124 if (this.pindex
== this.depth
) return "\x00";
126 var ndx
= this.plte_offs
+ 8 + 3 * this.pindex
;
128 this.buffer
[ndx
+ 0] = String
.fromCharCode(red
);
129 this.buffer
[ndx
+ 1] = String
.fromCharCode(green
);
130 this.buffer
[ndx
+ 2] = String
.fromCharCode(blue
);
131 this.buffer
[this.trns_offs
+8+this.pindex
] = String
.fromCharCode(alpha
);
133 this.palette
[color
] = String
.fromCharCode(this.pindex
++);
135 return this.palette
[color
];
138 // output a PNG string, Base64 encoded
139 this.getBase64 = function() {
141 var s
= this.getDump();
143 var ch
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
144 var c1
, c2
, c3
, e1
, e2
, e3
, e4
;
150 c1
= s
.charCodeAt(i
);
152 c2
= s
.charCodeAt(i
+1);
153 e2
= ((c1
& 3) << 4) | (c2
>> 4);
154 c3
= s
.charCodeAt(i
+2);
155 if (l
< i
+2) { e3
= 64; } else { e3
= ((c2
& 0xf) << 2) | (c3
>> 6); }
156 if (l
< i
+3) { e4
= 64; } else { e4
= c3
& 0x3f; }
157 r
+= ch
.charAt(e1
) + ch
.charAt(e2
) + ch
.charAt(e3
) + ch
.charAt(e4
);
158 } while ((i
+= 3) < l
);
162 // output a PNG string
163 this.getDump = function() {
165 // compute adler32 of output pixels + row filter bytes
166 var BASE
= 65521; /* largest prime smaller than 65536 */
167 var NMAX
= 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
172 for (var y = 0; y < this.height; y++) {
173 for (var x = -1; x < this.width; x++) {
174 s1+= this.buffer[this.index(x, y)].charCodeAt(0);
185 write(this.buffer, this.idat_offs + this.idat_size - 8, byte4((s2 << 16) | s1));
187 // compute crc32 of the PNG chunks
188 function crc32(png, offs, size) {
190 for (var i = 4; i < size-4; i += 1) {
191 crc = _crc32[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);
193 write(png, offs+size-4, byte4(crc ^ -1));
196 crc32(this.buffer, this.ihdr_offs, this.ihdr_size);
197 crc32(this.buffer, this.plte_offs, this.plte_size);
198 crc32(this.buffer, this.trns_offs, this.trns_size);
199 crc32(this.buffer, this.idat_offs, this.idat_size);
200 crc32(this.buffer, this.iend_offs, this.iend_size);
202 // convert PNG to string
203 return "\211PNG\r\n\032\n"+this.buffer.join('');