From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- image/test/unit/async_load_tests.js | 214 ++++++ image/test/unit/bug413512.ico | Bin 0 -> 17759 bytes image/test/unit/bug815359.ico | Bin 0 -> 4286 bytes image/test/unit/image1.png | Bin 0 -> 8415 bytes image/test/unit/image1png16x16.jpg | Bin 0 -> 1051 bytes image/test/unit/image1png64x64.jpg | Bin 0 -> 4503 bytes image/test/unit/image2.jpg | Bin 0 -> 3494 bytes image/test/unit/image2jpg16x16-win.png | Bin 0 -> 948 bytes image/test/unit/image2jpg16x16.png | Bin 0 -> 950 bytes image/test/unit/image2jpg16x16cropped.jpg | Bin 0 -> 879 bytes image/test/unit/image2jpg16x16cropped2.jpg | Bin 0 -> 878 bytes image/test/unit/image2jpg16x32cropped3.jpg | Bin 0 -> 1127 bytes image/test/unit/image2jpg16x32scaled.jpg | Bin 0 -> 1219 bytes image/test/unit/image2jpg32x16cropped4.jpg | Bin 0 -> 1135 bytes image/test/unit/image2jpg32x16scaled.jpg | Bin 0 -> 1227 bytes image/test/unit/image2jpg32x32-win.png | Bin 0 -> 3104 bytes image/test/unit/image2jpg32x32.jpg | Bin 0 -> 1634 bytes image/test/unit/image2jpg32x32.png | Bin 0 -> 3105 bytes image/test/unit/image3.ico | Bin 0 -> 1406 bytes image/test/unit/image3ico16x16.png | Bin 0 -> 330 bytes image/test/unit/image3ico32x32.png | Bin 0 -> 2285 bytes image/test/unit/image4.gif | Bin 0 -> 1809 bytes image/test/unit/image4gif16x16bmp24bpp.ico | Bin 0 -> 894 bytes image/test/unit/image4gif16x16bmp32bpp.ico | Bin 0 -> 1150 bytes image/test/unit/image4gif32x32bmp24bpp.ico | Bin 0 -> 3262 bytes image/test/unit/image4gif32x32bmp32bpp.ico | Bin 0 -> 4286 bytes image/test/unit/image_load_helpers.js | 122 ++++ image/test/unit/test_async_notification.js | 10 + image/test/unit/test_async_notification_404.js | 16 + .../test/unit/test_async_notification_animated.js | 14 + image/test/unit/test_encoder_apng.js | 470 +++++++++++++ image/test/unit/test_encoder_png.js | 256 +++++++ image/test/unit/test_imgtools.js | 737 +++++++++++++++++++++ image/test/unit/test_moz_icon_uri.js | 157 +++++ image/test/unit/test_private_channel.js | 143 ++++ image/test/unit/xpcshell.ini | 45 ++ 36 files changed, 2184 insertions(+) create mode 100644 image/test/unit/async_load_tests.js create mode 100644 image/test/unit/bug413512.ico create mode 100644 image/test/unit/bug815359.ico create mode 100644 image/test/unit/image1.png create mode 100644 image/test/unit/image1png16x16.jpg create mode 100644 image/test/unit/image1png64x64.jpg create mode 100644 image/test/unit/image2.jpg create mode 100644 image/test/unit/image2jpg16x16-win.png create mode 100644 image/test/unit/image2jpg16x16.png create mode 100644 image/test/unit/image2jpg16x16cropped.jpg create mode 100644 image/test/unit/image2jpg16x16cropped2.jpg create mode 100644 image/test/unit/image2jpg16x32cropped3.jpg create mode 100644 image/test/unit/image2jpg16x32scaled.jpg create mode 100644 image/test/unit/image2jpg32x16cropped4.jpg create mode 100644 image/test/unit/image2jpg32x16scaled.jpg create mode 100644 image/test/unit/image2jpg32x32-win.png create mode 100644 image/test/unit/image2jpg32x32.jpg create mode 100644 image/test/unit/image2jpg32x32.png create mode 100644 image/test/unit/image3.ico create mode 100644 image/test/unit/image3ico16x16.png create mode 100644 image/test/unit/image3ico32x32.png create mode 100644 image/test/unit/image4.gif create mode 100644 image/test/unit/image4gif16x16bmp24bpp.ico create mode 100644 image/test/unit/image4gif16x16bmp32bpp.ico create mode 100644 image/test/unit/image4gif32x32bmp24bpp.ico create mode 100644 image/test/unit/image4gif32x32bmp32bpp.ico create mode 100644 image/test/unit/image_load_helpers.js create mode 100644 image/test/unit/test_async_notification.js create mode 100644 image/test/unit/test_async_notification_404.js create mode 100644 image/test/unit/test_async_notification_animated.js create mode 100644 image/test/unit/test_encoder_apng.js create mode 100644 image/test/unit/test_encoder_png.js create mode 100644 image/test/unit/test_imgtools.js create mode 100644 image/test/unit/test_moz_icon_uri.js create mode 100644 image/test/unit/test_private_channel.js create mode 100644 image/test/unit/xpcshell.ini (limited to 'image/test/unit') diff --git a/image/test/unit/async_load_tests.js b/image/test/unit/async_load_tests.js new file mode 100644 index 000000000..fafb26f75 --- /dev/null +++ b/image/test/unit/async_load_tests.js @@ -0,0 +1,214 @@ +/* + * Test to ensure that image loading/decoding notifications are always + * delivered async, and in the order we expect. + * + * Must be included from a file that has a uri of the image to test defined in + * var uri. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +var server = new HttpServer(); +server.registerDirectory("/", do_get_file('')); +server.registerContentType("sjs", "sjs"); +server.start(-1); + + +load('image_load_helpers.js'); + +var requests = []; + +// Return a closure that holds on to the listener from the original +// imgIRequest, and compares its results to the cloned one. +function getCloneStopCallback(original_listener) +{ + return function cloneStop(listener) { + do_check_eq(original_listener.state, listener.state); + + // Sanity check to make sure we didn't accidentally use the same listener + // twice. + do_check_neq(original_listener, listener); + do_test_finished(); + } +} + +// Make sure that cloned requests get all the same callbacks as the original, +// but they aren't synchronous right now. +function checkClone(other_listener, aRequest) +{ + do_test_pending(); + + // For as long as clone notification is synchronous, we can't test the clone state reliably. + var listener = new ImageListener(null, function(foo, bar) { do_test_finished(); } /*getCloneStopCallback(other_listener)*/); + listener.synchronous = false; + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var clone = aRequest.clone(outer); + requests.push(clone); +} + +// Ensure that all the callbacks were called on aRequest. +function checkSizeAndLoad(listener, aRequest) +{ + do_check_neq(listener.state & SIZE_AVAILABLE, 0); + do_check_neq(listener.state & LOAD_COMPLETE, 0); + + do_test_finished(); +} + +function secondLoadDone(oldlistener, aRequest) +{ + do_test_pending(); + + try { + var staticrequest = aRequest.getStaticRequest(); + + // For as long as clone notification is synchronous, we can't test the + // clone state reliably. + var listener = new ImageListener(null, checkSizeAndLoad); + listener.synchronous = false; + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var staticrequestclone = staticrequest.clone(outer); + requests.push(staticrequestclone); + } catch(e) { + // We can't create a static request. Most likely the request we started + // with didn't load successfully. + do_test_finished(); + } + + run_loadImageWithChannel_tests(); + + do_test_finished(); +} + +// Load the request a second time. This should come from the image cache, and +// therefore would be at most risk of being served synchronously. +function checkSecondLoad() +{ + do_test_pending(); + + var listener = new ImageListener(checkClone, secondLoadDone); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null)); + listener.synchronous = false; +} + +function firstLoadDone(oldlistener, aRequest) +{ + checkSecondLoad(uri); + + do_test_finished(); +} + +// Return a closure that allows us to check the stream listener's status when the +// image finishes loading. +function getChannelLoadImageStopCallback(streamlistener, next) +{ + return function channelLoadStop(imglistener, aRequest) { + + next(); + + do_test_finished(); + } +} + +// Load the request a second time. This should come from the image cache, and +// therefore would be at most risk of being served synchronously. +function checkSecondChannelLoad() +{ + do_test_pending(); + var channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true}); + var channellistener = new ChannelListener(); + channel.asyncOpen2(channellistener); + + var listener = new ImageListener(null, + getChannelLoadImageStopCallback(channellistener, + all_done_callback)); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var outlistener = {}; + requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener)); + channellistener.outputListener = outlistener.value; + + listener.synchronous = false; +} + +function run_loadImageWithChannel_tests() +{ + // To ensure we're testing what we expect to, create a new loader and cache. + gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader); + + do_test_pending(); + var channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true}); + var channellistener = new ChannelListener(); + channel.asyncOpen2(channellistener); + + var listener = new ImageListener(null, + getChannelLoadImageStopCallback(channellistener, + checkSecondChannelLoad)); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var outlistener = {}; + requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener)); + channellistener.outputListener = outlistener.value; + + listener.synchronous = false; +} + +function all_done_callback() +{ + server.stop(function() { do_test_finished(); }); +} + +function startImageCallback(otherCb) +{ + return function(listener, request) + { + // Make sure we can load the same image immediately out of the cache. + do_test_pending(); + var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); }); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener2); + requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null)); + listener2.synchronous = false; + + // Now that we've started another load, chain to the callback. + otherCb(listener, request); + } +} + +var gCurrentLoader; + +function cleanup() +{ + for (var i = 0; i < requests.length; ++i) { + requests[i].cancelAndForgetObserver(0); + } +} + +function run_test() +{ + do_register_cleanup(cleanup); + + gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader); + + do_test_pending(); + var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var req = gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null); + requests.push(req); + + // Ensure that we don't cause any mayhem when we lock an image. + req.lockImage(); + + listener.synchronous = false; +} diff --git a/image/test/unit/bug413512.ico b/image/test/unit/bug413512.ico new file mode 100644 index 000000000..b2db0429f Binary files /dev/null and b/image/test/unit/bug413512.ico differ diff --git a/image/test/unit/bug815359.ico b/image/test/unit/bug815359.ico new file mode 100644 index 000000000..a24b8fb6b Binary files /dev/null and b/image/test/unit/bug815359.ico differ diff --git a/image/test/unit/image1.png b/image/test/unit/image1.png new file mode 100644 index 000000000..2fb37aeec Binary files /dev/null and b/image/test/unit/image1.png differ diff --git a/image/test/unit/image1png16x16.jpg b/image/test/unit/image1png16x16.jpg new file mode 100644 index 000000000..ea14dbede Binary files /dev/null and b/image/test/unit/image1png16x16.jpg differ diff --git a/image/test/unit/image1png64x64.jpg b/image/test/unit/image1png64x64.jpg new file mode 100644 index 000000000..11c34f6c6 Binary files /dev/null and b/image/test/unit/image1png64x64.jpg differ diff --git a/image/test/unit/image2.jpg b/image/test/unit/image2.jpg new file mode 100644 index 000000000..b2131bf0c Binary files /dev/null and b/image/test/unit/image2.jpg differ diff --git a/image/test/unit/image2jpg16x16-win.png b/image/test/unit/image2jpg16x16-win.png new file mode 100644 index 000000000..a821626c0 Binary files /dev/null and b/image/test/unit/image2jpg16x16-win.png differ diff --git a/image/test/unit/image2jpg16x16.png b/image/test/unit/image2jpg16x16.png new file mode 100644 index 000000000..5722223c2 Binary files /dev/null and b/image/test/unit/image2jpg16x16.png differ diff --git a/image/test/unit/image2jpg16x16cropped.jpg b/image/test/unit/image2jpg16x16cropped.jpg new file mode 100644 index 000000000..fca22cb30 Binary files /dev/null and b/image/test/unit/image2jpg16x16cropped.jpg differ diff --git a/image/test/unit/image2jpg16x16cropped2.jpg b/image/test/unit/image2jpg16x16cropped2.jpg new file mode 100644 index 000000000..e51d3530d Binary files /dev/null and b/image/test/unit/image2jpg16x16cropped2.jpg differ diff --git a/image/test/unit/image2jpg16x32cropped3.jpg b/image/test/unit/image2jpg16x32cropped3.jpg new file mode 100644 index 000000000..13a3d26e5 Binary files /dev/null and b/image/test/unit/image2jpg16x32cropped3.jpg differ diff --git a/image/test/unit/image2jpg16x32scaled.jpg b/image/test/unit/image2jpg16x32scaled.jpg new file mode 100644 index 000000000..6abef0f99 Binary files /dev/null and b/image/test/unit/image2jpg16x32scaled.jpg differ diff --git a/image/test/unit/image2jpg32x16cropped4.jpg b/image/test/unit/image2jpg32x16cropped4.jpg new file mode 100644 index 000000000..46f34918c Binary files /dev/null and b/image/test/unit/image2jpg32x16cropped4.jpg differ diff --git a/image/test/unit/image2jpg32x16scaled.jpg b/image/test/unit/image2jpg32x16scaled.jpg new file mode 100644 index 000000000..e302fbafd Binary files /dev/null and b/image/test/unit/image2jpg32x16scaled.jpg differ diff --git a/image/test/unit/image2jpg32x32-win.png b/image/test/unit/image2jpg32x32-win.png new file mode 100644 index 000000000..4d84df26a Binary files /dev/null and b/image/test/unit/image2jpg32x32-win.png differ diff --git a/image/test/unit/image2jpg32x32.jpg b/image/test/unit/image2jpg32x32.jpg new file mode 100644 index 000000000..cf9a10a37 Binary files /dev/null and b/image/test/unit/image2jpg32x32.jpg differ diff --git a/image/test/unit/image2jpg32x32.png b/image/test/unit/image2jpg32x32.png new file mode 100644 index 000000000..723008771 Binary files /dev/null and b/image/test/unit/image2jpg32x32.png differ diff --git a/image/test/unit/image3.ico b/image/test/unit/image3.ico new file mode 100644 index 000000000..d44438903 Binary files /dev/null and b/image/test/unit/image3.ico differ diff --git a/image/test/unit/image3ico16x16.png b/image/test/unit/image3ico16x16.png new file mode 100644 index 000000000..e9e520cb6 Binary files /dev/null and b/image/test/unit/image3ico16x16.png differ diff --git a/image/test/unit/image3ico32x32.png b/image/test/unit/image3ico32x32.png new file mode 100644 index 000000000..58497e3fa Binary files /dev/null and b/image/test/unit/image3ico32x32.png differ diff --git a/image/test/unit/image4.gif b/image/test/unit/image4.gif new file mode 100644 index 000000000..b1530bc81 Binary files /dev/null and b/image/test/unit/image4.gif differ diff --git a/image/test/unit/image4gif16x16bmp24bpp.ico b/image/test/unit/image4gif16x16bmp24bpp.ico new file mode 100644 index 000000000..890c81c27 Binary files /dev/null and b/image/test/unit/image4gif16x16bmp24bpp.ico differ diff --git a/image/test/unit/image4gif16x16bmp32bpp.ico b/image/test/unit/image4gif16x16bmp32bpp.ico new file mode 100644 index 000000000..f8a9eb8ad Binary files /dev/null and b/image/test/unit/image4gif16x16bmp32bpp.ico differ diff --git a/image/test/unit/image4gif32x32bmp24bpp.ico b/image/test/unit/image4gif32x32bmp24bpp.ico new file mode 100644 index 000000000..28092818d Binary files /dev/null and b/image/test/unit/image4gif32x32bmp24bpp.ico differ diff --git a/image/test/unit/image4gif32x32bmp32bpp.ico b/image/test/unit/image4gif32x32bmp32bpp.ico new file mode 100644 index 000000000..0e2d28c82 Binary files /dev/null and b/image/test/unit/image4gif32x32bmp32bpp.ico differ diff --git a/image/test/unit/image_load_helpers.js b/image/test/unit/image_load_helpers.js new file mode 100644 index 000000000..e8d9a29f8 --- /dev/null +++ b/image/test/unit/image_load_helpers.js @@ -0,0 +1,122 @@ +/* + * Helper structures to track callbacks from image and channel loads. + */ + +// START_REQUEST and STOP_REQUEST are used by ChannelListener, and +// stored in ChannelListener.requestStatus. +const START_REQUEST = 0x01; +const STOP_REQUEST = 0x02; +const DATA_AVAILABLE = 0x04; + +// One bit per callback that imageListener below implements. Stored in +// ImageListener.state. +const SIZE_AVAILABLE = 0x01; +const FRAME_UPDATE = 0x02; +const FRAME_COMPLETE = 0x04; +const LOAD_COMPLETE = 0x08; +const DECODE_COMPLETE = 0x10; + +// Safebrowsing requires that the profile dir is set. +do_get_profile(); + +// An implementation of imgIScriptedNotificationObserver with the ability to +// call specified functions on onStartRequest and onStopRequest. +function ImageListener(start_callback, stop_callback) +{ + this.sizeAvailable = function onSizeAvailable(aRequest) + { + do_check_false(this.synchronous); + + this.state |= SIZE_AVAILABLE; + + if (this.start_callback) + this.start_callback(this, aRequest); + } + this.frameComplete = function onFrameComplete(aRequest) + { + do_check_false(this.synchronous); + + this.state |= FRAME_COMPLETE; + } + this.decodeComplete = function onDecodeComplete(aRequest) + { + do_check_false(this.synchronous); + + this.state |= DECODE_COMPLETE; + } + this.loadComplete = function onLoadcomplete(aRequest) + { + do_check_false(this.synchronous); + + this.state |= LOAD_COMPLETE; + + if (this.stop_callback) + this.stop_callback(this, aRequest); + } + this.frameUpdate = function onFrameUpdate(aRequest) + { + } + this.isAnimated = function onIsAnimated() + { + } + + // Initialize the synchronous flag to true to start. This must be set to + // false before exiting to the event loop! + this.synchronous = true; + + // A function to call when onStartRequest is called. + this.start_callback = start_callback; + + // A function to call when onStopRequest is called. + this.stop_callback = stop_callback; + + // The image load/decode state. + // A bitfield that tracks which callbacks have been called. Takes the bits + // defined above. + this.state = 0; +} + +function NS_FAILED(val) +{ + return !!(val & 0x80000000); +} + +function ChannelListener() +{ + this.onStartRequest = function onStartRequest(aRequest, aContext) + { + if (this.outputListener) + this.outputListener.onStartRequest(aRequest, aContext); + + this.requestStatus |= START_REQUEST; + } + + this.onDataAvailable = function onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) + { + if (this.outputListener) + this.outputListener.onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount); + + this.requestStatus |= DATA_AVAILABLE; + } + + this.onStopRequest = function onStopRequest(aRequest, aContext, aStatusCode) + { + if (this.outputListener) + this.outputListener.onStopRequest(aRequest, aContext, aStatusCode); + + // If we failed (or were canceled - failure is implied if canceled), + // there's no use tracking our state, since it's meaningless. + if (NS_FAILED(aStatusCode)) + this.requestStatus = 0; + else + this.requestStatus |= STOP_REQUEST; + } + + // A listener to pass the notifications we get to. + this.outputListener = null; + + // The request's status. A bitfield that holds one or both of START_REQUEST + // and STOP_REQUEST, according to which callbacks have been called on the + // associated request. + this.requestStatus = 0; +} diff --git a/image/test/unit/test_async_notification.js b/image/test/unit/test_async_notification.js new file mode 100644 index 000000000..63d04b99b --- /dev/null +++ b/image/test/unit/test_async_notification.js @@ -0,0 +1,10 @@ +/* + * Test for asynchronous image load/decode notifications in the case that the image load works. + */ + +// A simple 3x3 png; rows go red, green, blue. Stolen from the PNG encoder test. +var pngspec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="; +var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); +var uri = ioService.newURI(pngspec, null, null); + +load('async_load_tests.js'); diff --git a/image/test/unit/test_async_notification_404.js b/image/test/unit/test_async_notification_404.js new file mode 100644 index 000000000..4949d282d --- /dev/null +++ b/image/test/unit/test_async_notification_404.js @@ -0,0 +1,16 @@ +/* + * Test to ensure that load/decode notifications are delivered completely and + * asynchronously when dealing with a file that's a 404. + */ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var ioService = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + +XPCOMUtils.defineLazyGetter(this, "uri", function() { + return ioService.newURI("http://localhost:" + + server.identity.primaryPort + + "/async-notification-never-here.jpg", null, null); +}); + +load('async_load_tests.js'); diff --git a/image/test/unit/test_async_notification_animated.js b/image/test/unit/test_async_notification_animated.js new file mode 100644 index 000000000..bfe6e2d0d --- /dev/null +++ b/image/test/unit/test_async_notification_animated.js @@ -0,0 +1,14 @@ +/* + * Test for asynchronous image load/decode notifications in the case that the + * image load works, but for an animated image. + * + * If this fails because a request wasn't cancelled, it's possible that + * imgContainer::ExtractFrame didn't set the new image's status correctly. + */ + +// transparent-animation.gif from the gif reftests. +var spec = "data:image/gif;base64,R0lGODlhZABkAIABAP8AAP///yH5BAkBAAEALAAAAABLAGQAAAK8jI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpchgPMJjUqnVOipis1ir9qul+sNV8HistVkTj/JajG7/UXDy+95tm4fy/NdPF/q93dWIqgVWAhwWKgoyPjnyAeZJ2lHOWcJh9mmqcaZ5mkGSreHOCXqRloadRrGGkeoapoa6+TaN0tra4gbq3vHq+q7BVwqrMeEnKy8zNzs/AwdLT1NXW19jZ1tUgAAIfkECQEAAQAsAAAAADQAZAAAArCMj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/7vAAiHxKLxiCRCkswmc+mMSqHSapJqzSof2u4Q67WCw1MuOTs+N9Pqq7kdZcON8vk2aF+/88g6358HaCc4Rwhn2IaopnjGSOYYBukl2UWpZYm2x0enuXnX4NnXGQqAKTYaalqlWoZH+snwWsQah+pJ64Sr5ypbCvQLHCw8TFxsfIycrLzM3PxQAAAh+QQJAQABACwAAAAAGwBkAAACUIyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gTE8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvdrfYnH5LL5jE6r16sCADs="; +var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); +var uri = ioService.newURI(spec, null, null); + +load('async_load_tests.js'); diff --git a/image/test/unit/test_encoder_apng.js b/image/test/unit/test_encoder_apng.js new file mode 100644 index 000000000..e7b90c28b --- /dev/null +++ b/image/test/unit/test_encoder_apng.js @@ -0,0 +1,470 @@ +/* + * Test for APNG encoding in ImageLib + * + */ + + +var Ci = Components.interfaces; +var Cc = Components.classes; + + // 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,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABBJREFUCJlj+M/AAEEMWFgAj44I+H2CySsAAAAaZmNUTAAAAAEAAAADAAAAAwAAAAAAAAAAAfQD6AAA7mXKzAAAABNmZEFUAAAAAgiZY2D4zwBFWFgAhpcI+I731VcAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABNmZEFUAAAABAiZY2Bg+A9DmCwAfaAI+AGmQVoAAAAASUVORK5CYII=" +}; + + +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/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAO5lyswAAAASZmRBVAAAAAIImWNg+I8EcXIAVOAR77Vyl9QAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABRmZEFUAAAABAiZY2Bg+P8fgXFxAEvpEe8rClxSAAAAAElFTkSuQmCC" +}; + + +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/AAAACGFjVEwAAAACAAAAAPONk3AAAAARSURBVAiZY/jPwPAfhhlwcgBd1xHvwqIidQAAABpmY1RMAAAAAAAAAAMAAAADAAAAAAAAAAAB9APoAAB1FiAYAAAAEmZkQVQAAAABCJljYPiPBHFyAFTgEe+kD/2tAAAAGmZjVEwAAAACAAAAAwAAAAMAAAAAAAAAAAH0A+gAAJiA8/EAAAAUZmRBVAAAAAMImWNgYPj/H4FxcQBL6RHvC5ggGQAAAABJRU5ErkJggg==" +}; + + +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/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAZli+loAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gAAXT0KbMAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC" +}; + + +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/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gBAYB5yxsAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gBAW3vGPIAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC" +}; + + +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/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAAA5JREFUCJlj+I8EGHByALuHI91kueRqAAAAGmZjVEwAAAABAAAAAQAAAAEAAAAAAAAAAAH0A+gAADJXfawAAAARZmRBVAAAAAIImWNgYGD4DwABBAEAbr5mpgAAABpmY1RMAAAAAwAAAAEAAAABAAAAAQAAAAEB9APoAAC4OHoxAAAAEWZkQVQAAAAECJljYGBg+A8AAQQBAJZ8LRAAAAAaZmNUTAAAAAUAAAABAAAAAQAAAAIAAAACAfQD6AAA/fh01wAAABFmZEFUAAAABgiZY2BgYPgPAAEEAQB3Eum9AAAAAElFTkSuQmCC" +}; + +// 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"); + do_check_eq(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 = '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] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; + result += toBase64Table[data[i+2] & 0x3f]; + } + + // 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] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[(data[i+1] & 0x0f) << 2]; + result += base64Pad; + } else { + result += toBase64Table[(data[i] & 0x03) << 4]; + result += base64Pad + base64Pad; + } + } + + return result; +} diff --git a/image/test/unit/test_encoder_png.js b/image/test/unit/test_encoder_png.js new file mode 100644 index 000000000..67beb840f --- /dev/null +++ b/image/test/unit/test_encoder_png.js @@ -0,0 +1,256 @@ +/* + * Test for PNG encoding in ImageLib + * + */ + +var Ci = Components.interfaces; +var Cc = Components.classes; + +var png1A = { + // A 3x3 image, rows are red, green, blue. + // RGB format, transparency defaults. + + transparency : null, + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + + pixels : [ + 255,0,0, 255,0,0, 255,0,0, + 0,255,0, 0,255,0, 0,255,0, + 0,0,255, 0,0,255, 0,0,255, + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" +}; + +var png1B = { + // A 3x3 image, rows are red, green, blue. + // RGB format, transparency=none. + + transparency : "none", + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + + pixels : [ + 255,0,0, 255,0,0, 255,0,0, + 0,255,0, 0,255,0, 0,255,0, + 0,0,255, 0,0,255, 0,0,255, + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" +}; + +var png2A = { + // A 3x3 image, rows are: red, green, blue. Columns are: 0%, 33%, 66% transparent. + + transparency : null, + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,170, 255,0,0,85, + 0,255,0,255, 0,255,0,170, 0,255,0,85, + 0,0,255,255, 0,0,255,170, 0,0,255,85 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAAIElEQVQImSXJMQEAMAwEIUy+yZi8DmVFFBcjycn86GgPJw4O8v9DkkEAAAAASUVORK5CYII=" +}; + +var png2B = { + // A 3x3 image, rows are: red, green, blue. Columns are: 0%, 33%, 66% transparent, + // but transparency will be ignored. + + transparency : "none", + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,170, 255,0,0,85, + 0,255,0,255, 0,255,0,170, 0,255,0,85, + 0,0,255,255, 0,0,255,170, 0,0,255,85 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" +}; + +// Main test entry point. +function run_test() { + dump("Checking png1A...\n") + run_test_for(png1A); + dump("Checking png1B...\n") + run_test_for(png1B); + dump("Checking png2A...\n") + run_test_for(png2A); + dump("Checking png2B...\n") + run_test_for(png2B); +}; + + +function run_test_for(input) { + var encoder, dataURL; + + encoder = encodeImage(input); + dataURL = makeDataURL(encoder, "image/png"); + do_check_eq(dataURL, input.expected); + + encoder = encodeImageAsync(input); + dataURL = makeDataURLFromAsync(encoder, "image/png", 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; + } + + var frame = input.frames[0]; + encoder.initFromData(frame.pixels, frame.pixels.length, + frame.width, frame.height, frame.stride, + frame.format, options); + return encoder; +} + +function _encodeImageAsyncFactory(frame, options, encoder) +{ + function finishEncode() { + encoder.addImageFrame(frame.pixels, frame.pixels.length, + frame.width, frame.height, frame.stride, + frame.format, options); + encoder.endImageEncode(); + } + return finishEncode; +} + +function encodeImageAsync(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; + } + + var frame = input.frames[0]; + encoder.startImageEncode(frame.width, frame.height, + frame.format, options); + + do_timeout(50, _encodeImageAsyncFactory(frame, options, encoder)); + 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; +} + +function makeDataURLFromAsync(encoder, mimetype, expected) { + do_test_pending(); + var rawStream = encoder.QueryInterface(Ci.nsIAsyncInputStream); + + var currentThread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread; + + var bytes = []; + + var binarystream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(); + binarystream.QueryInterface(Ci.nsIBinaryInputStream); + + var asyncReader = + { + onInputStreamReady: function(stream) + { + binarystream.setInputStream(stream); + var available = 0; + try { + available = stream.available(); + } catch(e) { } + + if (available > 0) + { + bytes = bytes.concat(binarystream.readByteArray(available)); + stream.asyncWait(this, 0, 0, currentThread); + } else { + var base64String = toBase64(bytes); + var dataURL = "data:" + mimetype + ";base64," + base64String; + do_check_eq(dataURL, expected); + do_test_finished(); + } + + } + }; + rawStream.asyncWait(asyncReader, 0, 0, currentThread); +} + +/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */ + +/* Convert data (an array of integers) to a Base64 string. */ +const toBase64Table = '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] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; + result += toBase64Table[data[i+2] & 0x3f]; + } + + // 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] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[(data[i+1] & 0x0f) << 2]; + result += base64Pad; + } else { + result += toBase64Table[(data[i] & 0x03) << 4]; + result += base64Pad + base64Pad; + } + } + + return result; +} diff --git a/image/test/unit/test_imgtools.js b/image/test/unit/test_imgtools.js new file mode 100644 index 000000000..b9b8da82b --- /dev/null +++ b/image/test/unit/test_imgtools.js @@ -0,0 +1,737 @@ +/* + * Tests for imgITools + */ + +var Ci = Components.interfaces; +var Cc = Components.classes; + + +/* + * dumpToFile() + * + * For test development, dumps the specified array to a file. + * Call |dumpToFile(outData);| in a test to file to a file. + */ +function dumpToFile(aData) { + var outputFile = do_get_tempdir(); + outputFile.append("testdump.png"); + + var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + // WR_ONLY|CREAT|TRUNC + outputStream.init(outputFile, 0x02 | 0x08 | 0x20, 0o644, null); + + var bos = Cc["@mozilla.org/binaryoutputstream;1"]. + createInstance(Ci.nsIBinaryOutputStream); + bos.setOutputStream(outputStream); + + bos.writeByteArray(aData, aData.length); + + outputStream.close(); +} + + +/* + * getFileInputStream() + * + * Returns an input stream for the specified file. + */ +function getFileInputStream(aFile) { + var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + // init the stream as RD_ONLY, -1 == default permissions. + inputStream.init(aFile, 0x01, -1, null); + + // Blah. The image decoders use ReadSegments, which isn't implemented on + // file input streams. Use a buffered stream to make it work. + var bis = Cc["@mozilla.org/network/buffered-input-stream;1"]. + createInstance(Ci.nsIBufferedInputStream); + bis.init(inputStream, 1024); + + return bis; +} + + +/* + * streamToArray() + * + * Consumes an input stream, and returns its bytes as an array. + */ +function streamToArray(aStream) { + var size = aStream.available(); + + // use a binary input stream to grab the bytes. + var bis = Cc["@mozilla.org/binaryinputstream;1"]. + createInstance(Ci.nsIBinaryInputStream); + bis.setInputStream(aStream); + + var bytes = bis.readByteArray(size); + if (size != bytes.length) + throw "Didn't read expected number of bytes"; + + return bytes; +} + + +/* + * compareArrays + * + * Compares two arrays, and throws if there's a difference. + */ +function compareArrays(aArray1, aArray2) { + do_check_eq(aArray1.length, aArray2.length); + + for (var i = 0; i < aArray1.length; i++) + if (aArray1[i] != aArray2[i]) + throw "arrays differ at index " + i; +} + + +/* + * checkExpectedError + * + * Checks to see if a thrown error was expected or not, and if it + * matches the expected value. + */ +function checkExpectedError (aExpectedError, aActualError) { + if (aExpectedError) { + if (!aActualError) + throw "Didn't throw as expected (" + aExpectedError + ")"; + + if (!aExpectedError.test(aActualError)) + throw "Threw (" + aActualError + "), not (" + aExpectedError; + + // We got the expected error, so make a note in the test log. + dump("...that error was expected.\n\n"); + } else if (aActualError) { + throw "Threw unexpected error: " + aActualError; + } +} + + +function run_test() { + +try { + + +/* ========== 0 ========== */ +var testnum = 0; +var testdesc = "imgITools setup"; +var err = null; + +var imgTools = Cc["@mozilla.org/image/tools;1"]. + getService(Ci.imgITools); + +if (!imgTools) + throw "Couldn't get imgITools service" + +// Ugh, this is an ugly hack. The pixel values we get in Windows are sometimes +// +/- 1 value compared to other platforms, so we need to compare against a +// different set of reference images. nsIXULRuntime.OS doesn't seem to be +// available in xpcshell, so we'll use this as a kludgy way to figure out if +// we're running on Windows. +var isWindows = mozinfo.os == "win"; + + +/* ========== 1 ========== */ +testnum++; +testdesc = "test decoding a PNG"; + +// 64x64 png, 8415 bytes. +var imgName = "image1.png"; +var inMimeType = "image/png"; +var imgFile = do_get_file(imgName); + +var istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 8415); + +// Use decodeImageData for this test even though it's deprecated to ensure that +// it correctly forwards to decodeImage and continues to work. +var outParam = { value: null }; +imgTools.decodeImageData(istream, inMimeType, outParam); +var container = outParam.value; + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 64); +do_check_eq(container.height, 64); + + +/* ========== 2 ========== */ +testnum++; +testdesc = "test encoding a scaled JPEG"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 16); + +var encodedBytes = streamToArray(istream); +// Get bytes for exected result +var refName = "image1png16x16.jpg"; +var refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1051); +var referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 3 ========== */ +testnum++; +testdesc = "test encoding an unscaled JPEG"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, "image/jpeg"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image1png64x64.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 4503); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 4 ========== */ +testnum++; +testdesc = "test decoding a JPEG"; + +// 32x32 jpeg, 3494 bytes. +imgName = "image2.jpg"; +inMimeType = "image/jpeg"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 3494); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 32); +do_check_eq(container.height, 32); + + +/* ========== 5 ========== */ +testnum++; +testdesc = "test encoding a scaled PNG"; + +if (!isWindows) { +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/png", 16, 16); + +encodedBytes = streamToArray(istream); +// Get bytes for exected result +refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 950); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); +} + + +/* ========== 6 ========== */ +testnum++; +testdesc = "test encoding an unscaled PNG"; + +if (!isWindows) { +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, "image/png"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = isWindows ? "image2jpg32x32-win.png" : "image2jpg32x32.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 3105); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); +} + + +/* ========== 7 ========== */ +testnum++; +testdesc = "test decoding a ICO"; + +// 16x16 ico, 1406 bytes. +imgName = "image3.ico"; +inMimeType = "image/x-icon"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 1406); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 16); +do_check_eq(container.height, 16); + + +/* ========== 8 ========== */ +testnum++; +testdesc = "test encoding a scaled PNG"; // note that we're scaling UP + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/png", 32, 32); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image3ico32x32.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 2285); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 9 ========== */ +testnum++; +testdesc = "test encoding an unscaled PNG"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, "image/png"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image3ico16x16.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 330); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 10 ========== */ +testnum++; +testdesc = "test decoding a GIF"; + +// 32x32 gif, 1809 bytes. +imgName = "image4.gif"; +inMimeType = "image/gif"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 1809); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 32); +do_check_eq(container.height, 32); + +/* ========== 11 ========== */ +testnum++; +testdesc = "test encoding an unscaled ICO with format options " + + "(format=bmp;bpp=32)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, + "image/vnd.microsoft.icon", + "format=bmp;bpp=32"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif32x32bmp32bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 4286); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + +/* ========== 12 ========== */ +testnum++; +testdesc = "test encoding a scaled ICO with format options " + + "(format=bmp;bpp=32)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, + "image/vnd.microsoft.icon", + 16, + 16, + "format=bmp;bpp=32"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif16x16bmp32bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1150); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + +/* ========== 13 ========== */ +testnum++; +testdesc = "test encoding an unscaled ICO with format options " + + "(format=bmp;bpp=24)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, + "image/vnd.microsoft.icon", + "format=bmp;bpp=24"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif32x32bmp24bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 3262); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + +/* ========== 14 ========== */ +testnum++; +testdesc = "test encoding a scaled ICO with format options " + + "(format=bmp;bpp=24)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, + "image/vnd.microsoft.icon", + 16, + 16, + "format=bmp;bpp=24"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif16x16bmp24bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 894); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 15 ========== */ +testnum++; +testdesc = "test cropping a JPG"; + +// 32x32 jpeg, 3494 bytes. +imgName = "image2.jpg"; +inMimeType = "image/jpeg"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 3494); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 32); +do_check_eq(container.height, 32); + +// encode a cropped image +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x16cropped.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 879); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 16 ========== */ +testnum++; +testdesc = "test cropping a JPG with an offset"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 16, 16, 16, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x16cropped2.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 878); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 17 ========== */ +testnum++; +testdesc = "test cropping a JPG without a given height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x32cropped3.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1127); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 18 ========== */ +testnum++; +testdesc = "test cropping a JPG without a given width"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x16cropped4.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1135); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 19 ========== */ +testnum++; +testdesc = "test cropping a JPG without a given width and height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x32.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1634); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 20 ========== */ +testnum++; +testdesc = "test scaling a JPG without a given width"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x16scaled.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1227); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 21 ========== */ +testnum++; +testdesc = "test scaling a JPG without a given height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x32scaled.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1219); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 22 ========== */ +testnum++; +testdesc = "test scaling a JPG without a given width and height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x32.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1634); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 22 ========== */ +testnum++; +testdesc = "test invalid arguments for cropping"; + +var numErrors = 0; + +try { + // width/height can't be negative + imgTools.encodeScaledImage(container, "image/jpeg", -1, -1); +} catch (e) { numErrors++; } + +try { + // offsets can't be negative + imgTools.encodeCroppedImage(container, "image/jpeg", -1, -1, 16, 16); +} catch (e) { numErrors++; } + +try { + // width/height can't be negative + imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, -1, -1); +} catch (e) { numErrors++; } + +try { + // out of bounds + imgTools.encodeCroppedImage(container, "image/jpeg", 17, 17, 16, 16); +} catch (e) { numErrors++; } + +try { + // out of bounds + imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 33, 33); +} catch (e) { numErrors++; } + +try { + // out of bounds + imgTools.encodeCroppedImage(container, "image/jpeg", 1, 1, 0, 0); +} catch (e) { numErrors++; } + +do_check_eq(numErrors, 6); + + +/* ========== bug 363986 ========== */ +testnum = 363986; +testdesc = "test PNG and JPEG encoders' Read/ReadSegments methods"; + +var testData = + [{preImage: "image3.ico", + preImageMimeType: "image/x-icon", + refImage: "image3ico16x16.png", + refImageMimeType: "image/png"}, + {preImage: "image1.png", + preImageMimeType: "image/png", + refImage: "image1png64x64.jpg", + refImageMimeType: "image/jpeg"}]; + +for(var i=0; i