image/test/unit/test_encoder_apng.js

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /*
     2  * Test for APNG encoding in libpr0n
     3  *
     4  */
     7 const Ci = Components.interfaces;
     8 const Cc = Components.classes;
    10   // dispose=[none|background|previous]
    11   // blend=[source|over]
    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,
    20 	frames  : [
    21 		{ // frame #1
    22 			width  : 3,    height : 3,
    23 			x_offset : 0,  y_offset : 0,
    24 			dispose : "none",   blend : "source", delay : 500,
    26 			format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
    27 			transparency : null,
    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 		},
    36 		{ // frame #2
    37 			width  : 3,    height : 3,
    38 			x_offset : 0,  y_offset : 0,
    39 			dispose : "none",   blend : "source", delay : 500,
    41 			format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
    42 			transparency : null,
    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 		},
    51 		{ // frame #3
    52 			width  : 3,    height : 3,
    53 			x_offset : 0,  y_offset : 0,
    54 			dispose : "none",   blend : "source", delay : 500,
    56 			format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
    57 			transparency : null,
    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 		}
    66 		],
    67 	expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABBJREFUCJlj+M/AAEEMWFgAj44I+H2CySsAAAAaZmNUTAAAAAEAAAADAAAAAwAAAAAAAAAAAfQD6AAA7mXKzAAAABNmZEFUAAAAAgiZY2D4zwBFWFgAhpcI+I731VcAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABNmZEFUAAAABAiZY2Bg+A9DmCwAfaAI+AGmQVoAAAAASUVORK5CYII="
    68 };
    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,
    78 	frames  : [
    79 		{ // frame #1
    80 			width  : 3,    height : 3,
    81 			x_offset : 0,  y_offset : 0,
    82 			dispose : "none",   blend : "source", delay : 500,
    84 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
    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 		},
    93 		{ // frame #2
    94 			width  : 3,    height : 3,
    95 			x_offset : 0,  y_offset : 0,
    96 			dispose : "none",   blend : "source", delay : 500,
    98 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   107 		{ // frame #3
   108 			width  : 3,    height : 3,
   109 			x_offset : 0,  y_offset : 0,
   110 			dispose : "none",   blend : "source", delay : 500,
   112 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		}
   121 		],
   122 	expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAO5lyswAAAASZmRBVAAAAAIImWNg+I8EcXIAVOAR77Vyl9QAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABRmZEFUAAAABAiZY2Bg+P8fgXFxAEvpEe8rClxSAAAAAElFTkSuQmCC"
   123 };
   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,
   134 	frames  : [
   135 		{ // frame #1
   136 			width  : 3,    height : 3,
   137 			x_offset : 0,  y_offset : 0,
   138 			dispose : "none",   blend : "source", delay : 500,
   140 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   149 		{ // frame #2
   150 			width  : 3,    height : 3,
   151 			x_offset : 0,  y_offset : 0,
   152 			dispose : "none",   blend : "source", delay : 500,
   154 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   163 		{ // frame #3
   164 			width  : 3,    height : 3,
   165 			x_offset : 0,  y_offset : 0,
   166 			dispose : "none",   blend : "source", delay : 500,
   168 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		}
   177 		],
   178 	expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAACAAAAAPONk3AAAAARSURBVAiZY/jPwPAfhhlwcgBd1xHvwqIidQAAABpmY1RMAAAAAAAAAAMAAAADAAAAAAAAAAAB9APoAAB1FiAYAAAAEmZkQVQAAAABCJljYPiPBHFyAFTgEe+kD/2tAAAAGmZjVEwAAAACAAAAAwAAAAMAAAAAAAAAAAH0A+gAAJiA8/EAAAAUZmRBVAAAAAMImWNgYPj/H4FxcQBL6RHvC5ggGQAAAABJRU5ErkJggg=="
   179 };
   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,
   192 	frames  : [
   193 		{ // frame #1
   194 			width  : 3,    height : 3,
   195 			x_offset : 0,  y_offset : 0,
   196 			dispose : "none",   blend : "source", delay : 500,
   198 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   207 		{ // frame #2
   208 			width  : 3,    height : 3,
   209 			x_offset : 0,  y_offset : 0,
   210 			dispose : "none",   blend : "over", delay : 500,
   212 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   221 		{ // frame #3
   222 			width  : 3,    height : 3,
   223 			x_offset : 0,  y_offset : 0,
   224 			dispose : "none",   blend : "over", delay : 500,
   226 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		}
   235 		],
   236 	expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAZli+loAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gAAXT0KbMAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC"
   237 };
   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,
   251 	frames  : [
   252 		{ // frame #1
   253 			width  : 3,    height : 3,
   254 			x_offset : 0,  y_offset : 0,
   255 			dispose : "background",   blend : "source", delay : 500,
   257 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   266 		{ // frame #2
   267 			width  : 3,    height : 3,
   268 			x_offset : 0,  y_offset : 0,
   269 			dispose : "background",   blend : "over", delay : 500,
   271 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		},
   280 		{ // frame #3
   281 			width  : 3,    height : 3,
   282 			x_offset : 0,  y_offset : 0,
   283 			dispose : "background",   blend : "over", delay : 500,
   285 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   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 		}
   294 		],
   295 	expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gBAYB5yxsAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gBAW3vGPIAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC"
   296 };
   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,
   306 	frames  : [
   307 		{ // frame #1
   308 			width  : 3,    height : 3,
   309 			x_offset : 0,  y_offset : 0,
   310 			dispose : "none",   blend : "source", delay : 500,
   312 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   314 			pixels : [
   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 		},
   322 		{ // frame #2
   323 			width  : 1,    height : 1,
   324 			x_offset : 0,  y_offset : 0,
   325 			dispose : "none",   blend : "source", delay : 500,
   327 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   329 			pixels : [
   330 				0,0,0,255
   331 				]
   332 		},
   334 		{ // frame #3
   335 			width  : 1,    height : 1,
   336 			x_offset : 1,  y_offset : 1,
   337 			dispose : "none",   blend : "source", delay : 500,
   339 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   341 			pixels : [
   342 				0,0,0,255
   343 				]
   344 		},
   346 		{ // frame #4
   347 			width  : 1,    height : 1,
   348 			x_offset : 2,  y_offset : 2,
   349 			dispose : "none",   blend : "source", delay : 500,
   351 			format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
   353 			pixels : [
   354 				0,0,0,255
   355 				]
   356 		}
   357 		],
   359 	expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAAA5JREFUCJlj+I8EGHByALuHI91kueRqAAAAGmZjVEwAAAABAAAAAQAAAAEAAAAAAAAAAAH0A+gAADJXfawAAAARZmRBVAAAAAIImWNgYGD4DwABBAEAbr5mpgAAABpmY1RMAAAAAwAAAAEAAAABAAAAAQAAAAEB9APoAAC4OHoxAAAAEWZkQVQAAAAECJljYGBg+A8AAQQBAJZ8LRAAAAAaZmNUTAAAAAUAAAABAAAAAQAAAAIAAAACAfQD6AAA/fh01wAAABFmZEFUAAAABgiZY2BgYPgPAAEEAQB3Eum9AAAAAElFTkSuQmCC"
   360 };
   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);
   371 	dump("Checking apng2A...\n");
   372 	run_test_for(apng2A);
   373 	dump("Checking apng2B...\n");
   374 	run_test_for(apng2B);
   376 	dump("Checking apng3...\n");
   377 	run_test_for(apng3);
   378 }; 
   381 function run_test_for(input) {
   382 	var encoder, dataURL;
   384 	encoder = encodeImage(input);
   385 	dataURL = makeDataURL(encoder, "image/png");
   386 	do_check_eq(dataURL, input.expected);
   387 };
   390 function encodeImage(input) {
   391 	var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance();
   392 	encoder.QueryInterface(Ci.imgIEncoder);
   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);
   401 	for (var i = 0; i < input.frames.length; i++) {
   402 		var frame = input.frames[i];
   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; }
   412 		encoder.addImageFrame(frame.pixels, frame.pixels.length,
   413 			frame.width, frame.height, frame.stride, frame.format, options);
   414 	}
   416 	encoder.endImageEncode();
   418 	return encoder;
   419 }
   422 function makeDataURL(encoder, mimetype) {
   423 	var rawStream = encoder.QueryInterface(Ci.nsIInputStream);
   425 	var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance();
   426 	stream.QueryInterface(Ci.nsIBinaryInputStream);
   428 	stream.setInputStream(rawStream);
   430 	var bytes = stream.readByteArray(stream.available()); // returns int[]
   432 	var base64String = toBase64(bytes);
   434 	return "data:" + mimetype + ";base64," + base64String;
   435 }
   437 /* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */
   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     }
   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     }
   469     return result;
   470 }

mercurial