/*
* Test for APNG encoding in ImageLib
*
*/
// dispose=[none|background|previous]
// blend=[source|over]
var apng1A = {
// A 3x3 image with 3 frames, alternating red, green, blue. RGB format.
width: 3 ,
height: 3 ,
skipFirstFrame: false ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGB,
transparency: null ,
plays: 0 ,
frames: [
{
// frame #1
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGB,
stride: 9 ,
transparency: null ,
pixels: [
255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 ,
0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 ,
],
},
{
// frame #2
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGB,
stride: 9 ,
transparency: null ,
pixels: [
0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 ,
255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 ,
],
},
{
// frame #3
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGB,
stride: 9 ,
transparency: null ,
pixels: [
0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 , 255 , 0 , 0 ,
255 , 0 , 0 , 255 , 0 , 0 , 255 ,
],
},
],
expected:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAAA9JREFUCFtj/M8ABYxYWAA5IQMBD9nE1QAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAADuZcrMAAAAFGZkQVQAAAACCFtjZPjPAAGMWFgANiQDAVBdoI8AAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABVmZEFUAAAABAhbY2Rg+M8ABoxYWAAzJwMBWk5KPwAAAABJRU5ErkJggg==" ,
};
var apng1B = {
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
width: 3 ,
height: 3 ,
skipFirstFrame: false ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
transparency: null ,
plays: 0 ,
frames: [
{
// frame #1
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 ,
0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 ,
],
},
{
// frame #2
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 ,
0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 ,
],
},
{
// frame #3
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 ,
255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 ,
255 ,
],
},
],
expected:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABJJREFUCFtj/M/AAEQQwIiTAwCM6AX+t+X3FQAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAADuZcrMAAAAFWZkQVQAAAACCFtjZPgPhFDAiJMDAInrBf4Q0nfOAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gAAAPzGSUAAAAWZmRBVAAAAAQIW2NkYPj/nwEKGHFyAIbuBf50PCpiAAAAAElFTkSuQmCC" ,
};
var apng1C = {
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
// The first frame is skipped, so it will only flash green/blue (or static red in an APNG-unaware viewer)
width: 3 ,
height: 3 ,
skipFirstFrame: true ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
transparency: null ,
plays: 0 ,
frames: [
{
// frame #1
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 ,
0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 ,
],
},
{
// frame #2
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 ,
0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 255 ,
],
},
{
// frame #3
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 ,
255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 ,
255 ,
],
},
],
expected:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAACAAAAAPONk3AAAAASSURBVAhbY/zPwABEEMCIkwMAjOgF/rfl9xUAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABVmZEFUAAAAAQhbY2T4D4RQwIiTAwCJ6wX++lSqrAAAABpmY1RMAAAAAgAAAAMAAAADAAAAAAAAAAAB9APoAACYgPPxAAAAFmZkQVQAAAADCFtjZGD4/58BChhxcgCG7gX+PgKhKQAAAABJRU5ErkJggg==" ,
};
var apng2A = {
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
// blend = over mode
// (The green frame is a horizontal gradient, and the blue frame is a
// vertical gradient. They stack as they animate.)
width: 3 ,
height: 3 ,
skipFirstFrame: false ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
transparency: null ,
plays: 0 ,
frames: [
{
// frame #1
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 ,
0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 ,
],
},
{
// frame #2
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "over" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 255 , 0 , 255 , 0 , 255 , 0 , 180 , 0 , 255 , 0 , 75 , 0 , 255 , 0 , 255 , 0 , 255 ,
0 , 180 , 0 , 255 , 0 , 75 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 180 , 0 , 255 , 0 , 75 ,
],
},
{
// frame #3
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "over" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 0 , 255 , 75 , 0 , 0 , 255 , 75 , 0 , 0 , 255 , 75 , 0 , 0 , 255 , 180 , 0 , 0 , 255 ,
180 , 0 , 0 , 255 , 180 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 ,
],
},
],
expected:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABJJREFUCFtj/M/AAEQQwIiTAwCM6AX+t+X3FQAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAAGZYvpaAAAAGWZkQVQAAAACCFtjZPgPhAwMW4F4OiNODgDI3wnis0vjTAAAABpmY1RMAAAAAwAAAAMAAAADAAAAAAAAAAAB9APoAAF09CmzAAAAHGZkQVQAAAAECFtjZGD4780ABYxAzhZkzn8YBwBn4AT/ernr+wAAAABJRU5ErkJggg==" ,
};
var apng2B = {
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
// blend = over, dispose = background
// (The green frame is a horizontal gradient, and the blue frame is a
// vertical gradient. Each frame is displayed individually, blended to
// whatever the background is.)
width: 3 ,
height: 3 ,
skipFirstFrame: false ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
transparency: null ,
plays: 0 ,
frames: [
{
// frame #1
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "background" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 ,
0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 ,
],
},
{
// frame #2
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "background" ,
blend: "over" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 255 , 0 , 255 , 0 , 255 , 0 , 180 , 0 , 255 , 0 , 75 , 0 , 255 , 0 , 255 , 0 , 255 ,
0 , 180 , 0 , 255 , 0 , 75 , 0 , 255 , 0 , 255 , 0 , 255 , 0 , 180 , 0 , 255 , 0 , 75 ,
],
},
{
// frame #3
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "background" ,
blend: "over" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
0 , 0 , 255 , 75 , 0 , 0 , 255 , 75 , 0 , 0 , 255 , 75 , 0 , 0 , 255 , 180 , 0 , 0 , 255 ,
180 , 0 , 0 , 255 , 180 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 , 0 , 0 , 255 , 255 ,
],
},
],
expected:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABJJREFUCFtj/M/AAEQQwIiTAwCM6AX+t+X3FQAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAQGAecsbAAAAGWZkQVQAAAACCFtjZPgPhAwMW4F4OiNODgDI3wnis0vjTAAAABpmY1RMAAAAAwAAAAMAAAADAAAAAAAAAAAB9APoAQFt7xjyAAAAHGZkQVQAAAAECFtjZGD4780ABYxAzhZkzn8YBwBn4AT/ernr+wAAAABJRU5ErkJggg==" ,
};
var apng3 = {
// A 3x3 image with 4 frames. First frame is white, then 1x1 frames draw a diagonal line
width: 3 ,
height: 3 ,
skipFirstFrame: false ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
transparency: null ,
plays: 0 ,
frames: [
{
// frame #1
width: 3 ,
height: 3 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [
255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 ,
255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 ,
255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 ,
],
},
{
// frame #2
width: 1 ,
height: 1 ,
x_offset: 0 ,
y_offset: 0 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [0 , 0 , 0 , 255 ],
},
{
// frame #3
width: 1 ,
height: 1 ,
x_offset: 1 ,
y_offset: 1 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [0 , 0 , 0 , 255 ],
},
{
// frame #4
width: 1 ,
height: 1 ,
x_offset: 2 ,
y_offset: 2 ,
dispose: "none" ,
blend: "source" ,
delay: 500 ,
format: Ci.imgIEncoder.INPUT_FORMAT_RGBA,
stride: 12 ,
pixels: [0 , 0 , 0 , 255 ],
},
],
expected:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCFtj/A8EDFDAiJMDABlqC/jamhxvAAAAGmZjVEwAAAABAAAAAQAAAAEAAAAAAAAAAAH0A+gAADJXfawAAAARZmRBVAAAAAIIW2NgYGD4DwABBAEA0iEgKQAAABpmY1RMAAAAAwAAAAEAAAABAAAAAQAAAAEB9APoAAC4OHoxAAAAEWZkQVQAAAAECFtjYGBg+A8AAQQBACrja58AAAAaZmNUTAAAAAUAAAABAAAAAQAAAAIAAAACAfQD6AAA/fh01wAAABFmZEFUAAAABghbY2BgYPgPAAEEAQDLja8yAAAAAElFTkSuQmCC" ,
};
// Main test entry point.
function run_test() {
dump("Checking apng1A...\n" );
run_test_for(apng1A);
dump("Checking apng1B...\n" );
run_test_for(apng1B);
dump("Checking apng1C...\n" );
run_test_for(apng1C);
dump("Checking apng2A...\n" );
run_test_for(apng2A);
dump("Checking apng2B...\n" );
run_test_for(apng2B);
dump("Checking apng3...\n" );
run_test_for(apng3);
}
function run_test_for(input) {
var encoder, dataURL;
encoder = encodeImage(input);
dataURL = makeDataURL(encoder, "image/png" );
Assert .equal(dataURL, input.expected);
}
function encodeImage(input) {
var encoder =
Cc["@mozilla.org/image/encoder;2?type=image/png" ].createInstance();
encoder.QueryInterface(Ci.imgIEncoder);
var options = "" ;
if (input.transparency) {
options += "transparency=" + input.transparency;
}
options += ";frames=" + input.frames.length;
options += ";skipfirstframe=" + (input.skipFirstFrame ? "yes" : "no" );
options += ";plays=" + input.plays;
encoder.startImageEncode(input.width, input.height, input.format, options);
for (var i = 0 ; i < input.frames.length; i++) {
var frame = input.frames[i];
options = "" ;
if (frame.transparency) {
options += "transparency=" + input.transparency;
}
options += ";delay=" + frame.delay;
options += ";dispose=" + frame.dispose;
options += ";blend=" + frame.blend;
if (frame.x_offset > 0 ) {
options += ";xoffset=" + frame.x_offset;
}
if (frame.y_offset > 0 ) {
options += ";yoffset=" + frame.y_offset;
}
encoder.addImageFrame(
frame.pixels,
frame.pixels.length,
frame.width,
frame.height,
frame.stride,
frame.format,
options
);
}
encoder.endImageEncode();
return encoder;
}
function makeDataURL(encoder, mimetype) {
var rawStream = encoder.QueryInterface(Ci.nsIInputStream);
var stream = Cc["@mozilla.org/binaryinputstream;1" ].createInstance();
stream.QueryInterface(Ci.nsIBinaryInputStream);
stream.setInputStream(rawStream);
var bytes = stream.readByteArray(stream.available()); // returns int[]
var base64String = toBase64(bytes);
return "data:" + mimetype + ";base64," + base64String;
}
/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */
/* Convert data (an array of integers) to a Base64 string. */
const toBase64Table =
// eslint-disable-next-line no-useless-concat
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789+/" ;
const base64Pad = "=" ;
function toBase64(data) {
var result = "" ;
var length = data.length;
var i;
// Convert every three bytes to 4 ascii characters.
for (i = 0 ; i < length - 2 ; i += 3 ) {
result += toBase64Table[data[i] >> 2 ];
result += toBase64Table[((data[i] & 0 x03) << 4 ) + (data[i + 1 ] >> 4 )];
result += toBase64Table[((data[i + 1 ] & 0 x0f) << 2 ) + (data[i + 2 ] >> 6 )];
result += toBase64Table[data[i + 2 ] & 0 x3f];
}
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (length % 3 ) {
i = length - (length % 3 );
result += toBase64Table[data[i] >> 2 ];
if (length % 3 == 2 ) {
result += toBase64Table[((data[i] & 0 x03) << 4 ) + (data[i + 1 ] >> 4 )];
result += toBase64Table[(data[i + 1 ] & 0 x0f) << 2 ];
result += base64Pad;
} else {
result += toBase64Table[(data[i] & 0 x03) << 4 ];
result += base64Pad + base64Pad;
}
}
return result;
}
Messung V0.5 in Prozent C=92 H=97 G=94