|
1 /* |
|
2 * Test for APNG encoding in libpr0n |
|
3 * |
|
4 */ |
|
5 |
|
6 |
|
7 const Ci = Components.interfaces; |
|
8 const Cc = Components.classes; |
|
9 |
|
10 // dispose=[none|background|previous] |
|
11 // blend=[source|over] |
|
12 |
|
13 var apng1A = { |
|
14 // A 3x3 image with 3 frames, alternating red, green, blue. RGB format. |
|
15 width : 3, height : 3, skipFirstFrame : false, |
|
16 format : Ci.imgIEncoder.INPUT_FORMAT_RGB, |
|
17 transparency : null, |
|
18 plays : 0, |
|
19 |
|
20 frames : [ |
|
21 { // frame #1 |
|
22 width : 3, height : 3, |
|
23 x_offset : 0, y_offset : 0, |
|
24 dispose : "none", blend : "source", delay : 500, |
|
25 |
|
26 format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, |
|
27 transparency : null, |
|
28 |
|
29 pixels : [ |
|
30 255,0,0, 255,0,0, 255,0,0, |
|
31 255,0,0, 255,0,0, 255,0,0, |
|
32 255,0,0, 255,0,0, 255,0,0 |
|
33 ] |
|
34 }, |
|
35 |
|
36 { // frame #2 |
|
37 width : 3, height : 3, |
|
38 x_offset : 0, y_offset : 0, |
|
39 dispose : "none", blend : "source", delay : 500, |
|
40 |
|
41 format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, |
|
42 transparency : null, |
|
43 |
|
44 pixels : [ |
|
45 0,255,0, 0,255,0, 0,255,0, |
|
46 0,255,0, 0,255,0, 0,255,0, |
|
47 0,255,0, 0,255,0, 0,255,0 |
|
48 ] |
|
49 }, |
|
50 |
|
51 { // frame #3 |
|
52 width : 3, height : 3, |
|
53 x_offset : 0, y_offset : 0, |
|
54 dispose : "none", blend : "source", delay : 500, |
|
55 |
|
56 format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, |
|
57 transparency : null, |
|
58 |
|
59 pixels : [ |
|
60 0,0,255, 0,0,255, 0,0,255, |
|
61 0,0,255, 0,0,255, 0,0,255, |
|
62 0,0,255, 0,0,255, 0,0,255 |
|
63 ] |
|
64 } |
|
65 |
|
66 ], |
|
67 expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABBJREFUCJlj+M/AAEEMWFgAj44I+H2CySsAAAAaZmNUTAAAAAEAAAADAAAAAwAAAAAAAAAAAfQD6AAA7mXKzAAAABNmZEFUAAAAAgiZY2D4zwBFWFgAhpcI+I731VcAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABNmZEFUAAAABAiZY2Bg+A9DmCwAfaAI+AGmQVoAAAAASUVORK5CYII=" |
|
68 }; |
|
69 |
|
70 |
|
71 var apng1B = { |
|
72 // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. |
|
73 width : 3, height : 3, skipFirstFrame : false, |
|
74 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, |
|
75 transparency : null, |
|
76 plays : 0, |
|
77 |
|
78 frames : [ |
|
79 { // frame #1 |
|
80 width : 3, height : 3, |
|
81 x_offset : 0, y_offset : 0, |
|
82 dispose : "none", blend : "source", delay : 500, |
|
83 |
|
84 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
85 |
|
86 pixels : [ |
|
87 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
88 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
89 255,0,0,255, 255,0,0,255, 255,0,0,255 |
|
90 ] |
|
91 }, |
|
92 |
|
93 { // frame #2 |
|
94 width : 3, height : 3, |
|
95 x_offset : 0, y_offset : 0, |
|
96 dispose : "none", blend : "source", delay : 500, |
|
97 |
|
98 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
99 |
|
100 pixels : [ |
|
101 0,255,0,255, 0,255,0,255, 0,255,0,255, |
|
102 0,255,0,255, 0,255,0,255, 0,255,0,255, |
|
103 0,255,0,255, 0,255,0,255, 0,255,0,255 |
|
104 ] |
|
105 }, |
|
106 |
|
107 { // frame #3 |
|
108 width : 3, height : 3, |
|
109 x_offset : 0, y_offset : 0, |
|
110 dispose : "none", blend : "source", delay : 500, |
|
111 |
|
112 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
113 |
|
114 pixels : [ |
|
115 0,0,255,255, 0,0,255,255, 0,0,255,255, |
|
116 0,0,255,255, 0,0,255,255, 0,0,255,255, |
|
117 0,0,255,255, 0,0,255,255, 0,0,255,255 |
|
118 ] |
|
119 } |
|
120 |
|
121 ], |
|
122 expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAO5lyswAAAASZmRBVAAAAAIImWNg+I8EcXIAVOAR77Vyl9QAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABRmZEFUAAAABAiZY2Bg+P8fgXFxAEvpEe8rClxSAAAAAElFTkSuQmCC" |
|
123 }; |
|
124 |
|
125 |
|
126 var apng1C = { |
|
127 // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. |
|
128 // The first frame is skipped, so it will only flash green/blue (or static red in an APNG-unaware viewer) |
|
129 width : 3, height : 3, skipFirstFrame : true, |
|
130 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, |
|
131 transparency : null, |
|
132 plays : 0, |
|
133 |
|
134 frames : [ |
|
135 { // frame #1 |
|
136 width : 3, height : 3, |
|
137 x_offset : 0, y_offset : 0, |
|
138 dispose : "none", blend : "source", delay : 500, |
|
139 |
|
140 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
141 |
|
142 pixels : [ |
|
143 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
144 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
145 255,0,0,255, 255,0,0,255, 255,0,0,255 |
|
146 ] |
|
147 }, |
|
148 |
|
149 { // frame #2 |
|
150 width : 3, height : 3, |
|
151 x_offset : 0, y_offset : 0, |
|
152 dispose : "none", blend : "source", delay : 500, |
|
153 |
|
154 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
155 |
|
156 pixels : [ |
|
157 0,255,0,255, 0,255,0,255, 0,255,0,255, |
|
158 0,255,0,255, 0,255,0,255, 0,255,0,255, |
|
159 0,255,0,255, 0,255,0,255, 0,255,0,255 |
|
160 ] |
|
161 }, |
|
162 |
|
163 { // frame #3 |
|
164 width : 3, height : 3, |
|
165 x_offset : 0, y_offset : 0, |
|
166 dispose : "none", blend : "source", delay : 500, |
|
167 |
|
168 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
169 |
|
170 pixels : [ |
|
171 0,0,255,255, 0,0,255,255, 0,0,255,255, |
|
172 0,0,255,255, 0,0,255,255, 0,0,255,255, |
|
173 0,0,255,255, 0,0,255,255, 0,0,255,255 |
|
174 ] |
|
175 } |
|
176 |
|
177 ], |
|
178 expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAACAAAAAPONk3AAAAARSURBVAiZY/jPwPAfhhlwcgBd1xHvwqIidQAAABpmY1RMAAAAAAAAAAMAAAADAAAAAAAAAAAB9APoAAB1FiAYAAAAEmZkQVQAAAABCJljYPiPBHFyAFTgEe+kD/2tAAAAGmZjVEwAAAACAAAAAwAAAAMAAAAAAAAAAAH0A+gAAJiA8/EAAAAUZmRBVAAAAAMImWNgYPj/H4FxcQBL6RHvC5ggGQAAAABJRU5ErkJggg==" |
|
179 }; |
|
180 |
|
181 |
|
182 var apng2A = { |
|
183 // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. |
|
184 // blend = over mode |
|
185 // (The green frame is a horizontal gradient, and the blue frame is a |
|
186 // vertical gradient. They stack as they animate.) |
|
187 width : 3, height : 3, skipFirstFrame : false, |
|
188 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, |
|
189 transparency : null, |
|
190 plays : 0, |
|
191 |
|
192 frames : [ |
|
193 { // frame #1 |
|
194 width : 3, height : 3, |
|
195 x_offset : 0, y_offset : 0, |
|
196 dispose : "none", blend : "source", delay : 500, |
|
197 |
|
198 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
199 |
|
200 pixels : [ |
|
201 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
202 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
203 255,0,0,255, 255,0,0,255, 255,0,0,255 |
|
204 ] |
|
205 }, |
|
206 |
|
207 { // frame #2 |
|
208 width : 3, height : 3, |
|
209 x_offset : 0, y_offset : 0, |
|
210 dispose : "none", blend : "over", delay : 500, |
|
211 |
|
212 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
213 |
|
214 pixels : [ |
|
215 0,255,0,255, 0,255,0,180, 0,255,0,75, |
|
216 0,255,0,255, 0,255,0,180, 0,255,0,75, |
|
217 0,255,0,255, 0,255,0,180, 0,255,0,75 |
|
218 ] |
|
219 }, |
|
220 |
|
221 { // frame #3 |
|
222 width : 3, height : 3, |
|
223 x_offset : 0, y_offset : 0, |
|
224 dispose : "none", blend : "over", delay : 500, |
|
225 |
|
226 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
227 |
|
228 pixels : [ |
|
229 0,0,255,75, 0,0,255,75, 0,0,255,75, |
|
230 0,0,255,180, 0,0,255,180, 0,0,255,180, |
|
231 0,0,255,255, 0,0,255,255, 0,0,255,255 |
|
232 ] |
|
233 } |
|
234 |
|
235 ], |
|
236 expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAZli+loAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gAAXT0KbMAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC" |
|
237 }; |
|
238 |
|
239 |
|
240 var apng2B = { |
|
241 // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. |
|
242 // blend = over, dispose = background |
|
243 // (The green frame is a horizontal gradient, and the blue frame is a |
|
244 // vertical gradient. Each frame is displayed individually, blended to |
|
245 // whatever the background is.) |
|
246 width : 3, height : 3, skipFirstFrame : false, |
|
247 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, |
|
248 transparency : null, |
|
249 plays : 0, |
|
250 |
|
251 frames : [ |
|
252 { // frame #1 |
|
253 width : 3, height : 3, |
|
254 x_offset : 0, y_offset : 0, |
|
255 dispose : "background", blend : "source", delay : 500, |
|
256 |
|
257 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
258 |
|
259 pixels : [ |
|
260 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
261 255,0,0,255, 255,0,0,255, 255,0,0,255, |
|
262 255,0,0,255, 255,0,0,255, 255,0,0,255 |
|
263 ] |
|
264 }, |
|
265 |
|
266 { // frame #2 |
|
267 width : 3, height : 3, |
|
268 x_offset : 0, y_offset : 0, |
|
269 dispose : "background", blend : "over", delay : 500, |
|
270 |
|
271 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
272 |
|
273 pixels : [ |
|
274 0,255,0,255, 0,255,0,180, 0,255,0,75, |
|
275 0,255,0,255, 0,255,0,180, 0,255,0,75, |
|
276 0,255,0,255, 0,255,0,180, 0,255,0,75 |
|
277 ] |
|
278 }, |
|
279 |
|
280 { // frame #3 |
|
281 width : 3, height : 3, |
|
282 x_offset : 0, y_offset : 0, |
|
283 dispose : "background", blend : "over", delay : 500, |
|
284 |
|
285 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
286 |
|
287 pixels : [ |
|
288 0,0,255,75, 0,0,255,75, 0,0,255,75, |
|
289 0,0,255,180, 0,0,255,180, 0,0,255,180, |
|
290 0,0,255,255, 0,0,255,255, 0,0,255,255 |
|
291 ] |
|
292 } |
|
293 |
|
294 ], |
|
295 expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gBAYB5yxsAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gBAW3vGPIAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC" |
|
296 }; |
|
297 |
|
298 |
|
299 var apng3 = { |
|
300 // A 3x3 image with 4 frames. First frame is white, then 1x1 frames draw a diagonal line |
|
301 width : 3, height : 3, skipFirstFrame : false, |
|
302 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, |
|
303 transparency : null, |
|
304 plays : 0, |
|
305 |
|
306 frames : [ |
|
307 { // frame #1 |
|
308 width : 3, height : 3, |
|
309 x_offset : 0, y_offset : 0, |
|
310 dispose : "none", blend : "source", delay : 500, |
|
311 |
|
312 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
313 |
|
314 pixels : [ |
|
315 |
|
316 255,255,255,255, 255,255,255,255, 255,255,255,255, |
|
317 255,255,255,255, 255,255,255,255, 255,255,255,255, |
|
318 255,255,255,255, 255,255,255,255, 255,255,255,255 |
|
319 ] |
|
320 }, |
|
321 |
|
322 { // frame #2 |
|
323 width : 1, height : 1, |
|
324 x_offset : 0, y_offset : 0, |
|
325 dispose : "none", blend : "source", delay : 500, |
|
326 |
|
327 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
328 |
|
329 pixels : [ |
|
330 0,0,0,255 |
|
331 ] |
|
332 }, |
|
333 |
|
334 { // frame #3 |
|
335 width : 1, height : 1, |
|
336 x_offset : 1, y_offset : 1, |
|
337 dispose : "none", blend : "source", delay : 500, |
|
338 |
|
339 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
340 |
|
341 pixels : [ |
|
342 0,0,0,255 |
|
343 ] |
|
344 }, |
|
345 |
|
346 { // frame #4 |
|
347 width : 1, height : 1, |
|
348 x_offset : 2, y_offset : 2, |
|
349 dispose : "none", blend : "source", delay : 500, |
|
350 |
|
351 format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, |
|
352 |
|
353 pixels : [ |
|
354 0,0,0,255 |
|
355 ] |
|
356 } |
|
357 ], |
|
358 |
|
359 expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAAA5JREFUCJlj+I8EGHByALuHI91kueRqAAAAGmZjVEwAAAABAAAAAQAAAAEAAAAAAAAAAAH0A+gAADJXfawAAAARZmRBVAAAAAIImWNgYGD4DwABBAEAbr5mpgAAABpmY1RMAAAAAwAAAAEAAAABAAAAAQAAAAEB9APoAAC4OHoxAAAAEWZkQVQAAAAECJljYGBg+A8AAQQBAJZ8LRAAAAAaZmNUTAAAAAUAAAABAAAAAQAAAAIAAAACAfQD6AAA/fh01wAAABFmZEFUAAAABgiZY2BgYPgPAAEEAQB3Eum9AAAAAElFTkSuQmCC" |
|
360 }; |
|
361 |
|
362 // Main test entry point. |
|
363 function run_test() { |
|
364 dump("Checking apng1A...\n"); |
|
365 run_test_for(apng1A); |
|
366 dump("Checking apng1B...\n"); |
|
367 run_test_for(apng1B); |
|
368 dump("Checking apng1C...\n"); |
|
369 run_test_for(apng1C); |
|
370 |
|
371 dump("Checking apng2A...\n"); |
|
372 run_test_for(apng2A); |
|
373 dump("Checking apng2B...\n"); |
|
374 run_test_for(apng2B); |
|
375 |
|
376 dump("Checking apng3...\n"); |
|
377 run_test_for(apng3); |
|
378 }; |
|
379 |
|
380 |
|
381 function run_test_for(input) { |
|
382 var encoder, dataURL; |
|
383 |
|
384 encoder = encodeImage(input); |
|
385 dataURL = makeDataURL(encoder, "image/png"); |
|
386 do_check_eq(dataURL, input.expected); |
|
387 }; |
|
388 |
|
389 |
|
390 function encodeImage(input) { |
|
391 var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance(); |
|
392 encoder.QueryInterface(Ci.imgIEncoder); |
|
393 |
|
394 var options = ""; |
|
395 if (input.transparency) { options += "transparency=" + input.transparency; } |
|
396 options += ";frames=" + input.frames.length; |
|
397 options += ";skipfirstframe=" + (input.skipFirstFrame ? "yes" : "no"); |
|
398 options += ";plays=" + input.plays; |
|
399 encoder.startImageEncode(input.width, input.height, input.format, options); |
|
400 |
|
401 for (var i = 0; i < input.frames.length; i++) { |
|
402 var frame = input.frames[i]; |
|
403 |
|
404 options = ""; |
|
405 if (frame.transparency) { options += "transparency=" + input.transparency; } |
|
406 options += ";delay=" + frame.delay; |
|
407 options += ";dispose=" + frame.dispose; |
|
408 options += ";blend=" + frame.blend; |
|
409 if (frame.x_offset > 0) { options += ";xoffset=" + frame.x_offset; } |
|
410 if (frame.y_offset > 0) { options += ";yoffset=" + frame.y_offset; } |
|
411 |
|
412 encoder.addImageFrame(frame.pixels, frame.pixels.length, |
|
413 frame.width, frame.height, frame.stride, frame.format, options); |
|
414 } |
|
415 |
|
416 encoder.endImageEncode(); |
|
417 |
|
418 return encoder; |
|
419 } |
|
420 |
|
421 |
|
422 function makeDataURL(encoder, mimetype) { |
|
423 var rawStream = encoder.QueryInterface(Ci.nsIInputStream); |
|
424 |
|
425 var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(); |
|
426 stream.QueryInterface(Ci.nsIBinaryInputStream); |
|
427 |
|
428 stream.setInputStream(rawStream); |
|
429 |
|
430 var bytes = stream.readByteArray(stream.available()); // returns int[] |
|
431 |
|
432 var base64String = toBase64(bytes); |
|
433 |
|
434 return "data:" + mimetype + ";base64," + base64String; |
|
435 } |
|
436 |
|
437 /* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */ |
|
438 |
|
439 /* Convert data (an array of integers) to a Base64 string. */ |
|
440 const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + |
|
441 '0123456789+/'; |
|
442 const base64Pad = '='; |
|
443 function toBase64(data) { |
|
444 var result = ''; |
|
445 var length = data.length; |
|
446 var i; |
|
447 // Convert every three bytes to 4 ascii characters. |
|
448 for (i = 0; i < (length - 2); i += 3) { |
|
449 result += toBase64Table[data[i] >> 2]; |
|
450 result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; |
|
451 result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; |
|
452 result += toBase64Table[data[i+2] & 0x3f]; |
|
453 } |
|
454 |
|
455 // Convert the remaining 1 or 2 bytes, pad out to 4 characters. |
|
456 if (length%3) { |
|
457 i = length - (length%3); |
|
458 result += toBase64Table[data[i] >> 2]; |
|
459 if ((length%3) == 2) { |
|
460 result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; |
|
461 result += toBase64Table[(data[i+1] & 0x0f) << 2]; |
|
462 result += base64Pad; |
|
463 } else { |
|
464 result += toBase64Table[(data[i] & 0x03) << 4]; |
|
465 result += base64Pad + base64Pad; |
|
466 } |
|
467 } |
|
468 |
|
469 return result; |
|
470 } |