test_encoder_apng.js 14 KB

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