import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
BIN
image/test/browser/animated.gif
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
image/test/browser/animated2.gif
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
image/test/browser/big.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
14
image/test/browser/browser.ini
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
animated.gif
|
||||
animated2.gif
|
||||
big.png
|
||||
head.js
|
||||
image.html
|
||||
imageX2.html
|
||||
|
||||
[browser_bug666317.js]
|
||||
skip-if = true || e10s # Bug 1207012 - Permaorange from an uncaught exception that isn't actually turning the suite orange until it hits beta, Bug 948194 - Decoded Images seem to not be discarded on memory-pressure notification with e10s enabled
|
||||
[browser_image.js]
|
||||
skip-if = true # Bug 987616
|
||||
[browser_docshell_type_editor.js]
|
||||
140
image/test/browser/browser_bug666317.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
waitForExplicitFinish();
|
||||
|
||||
var pageSource =
|
||||
'<html><body>' +
|
||||
'<img id="testImg" src="' + TESTROOT + 'big.png">' +
|
||||
'</body></html>';
|
||||
|
||||
var oldDiscardingPref, oldTab, newTab;
|
||||
var prefBranch = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService)
|
||||
.getBranch('image.mem.');
|
||||
|
||||
var gWaitingForDiscard = false;
|
||||
var gScriptedObserver;
|
||||
var gClonedRequest;
|
||||
|
||||
function ImageObserver(decodeCallback, discardCallback) {
|
||||
this.decodeComplete = function onDecodeComplete(aRequest) {
|
||||
decodeCallback();
|
||||
}
|
||||
|
||||
this.discard = function onDiscard(request)
|
||||
{
|
||||
if (!gWaitingForDiscard) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.synchronous = false;
|
||||
discardCallback();
|
||||
}
|
||||
|
||||
this.synchronous = true;
|
||||
}
|
||||
|
||||
function currentRequest() {
|
||||
let img = gBrowser.getBrowserForTab(newTab).contentWindow
|
||||
.document.getElementById('testImg');
|
||||
img.QueryInterface(Ci.nsIImageLoadingContent);
|
||||
return img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
|
||||
}
|
||||
|
||||
function isImgDecoded() {
|
||||
let request = currentRequest();
|
||||
return request.imageStatus & Ci.imgIRequest.STATUS_DECODE_COMPLETE ? true : false;
|
||||
}
|
||||
|
||||
// Ensure that the image is decoded by drawing it to a canvas.
|
||||
function forceDecodeImg() {
|
||||
let doc = gBrowser.getBrowserForTab(newTab).contentWindow.document;
|
||||
let img = doc.getElementById('testImg');
|
||||
let canvas = doc.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
}
|
||||
|
||||
function runAfterAsyncEvents(aCallback) {
|
||||
function handlePostMessage(aEvent) {
|
||||
if (aEvent.data == 'next') {
|
||||
window.removeEventListener('message', handlePostMessage, false);
|
||||
aCallback();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handlePostMessage, false);
|
||||
|
||||
// We'll receive the 'message' event after everything else that's currently in
|
||||
// the event queue (which is a stronger guarantee than setTimeout, because
|
||||
// setTimeout events may be coalesced). This lets us ensure that we run
|
||||
// aCallback *after* any asynchronous events are delivered.
|
||||
window.postMessage('next', '*');
|
||||
}
|
||||
|
||||
function test() {
|
||||
// Enable the discarding pref.
|
||||
oldDiscardingPref = prefBranch.getBoolPref('discardable');
|
||||
prefBranch.setBoolPref('discardable', true);
|
||||
|
||||
// Create and focus a new tab.
|
||||
oldTab = gBrowser.selectedTab;
|
||||
newTab = gBrowser.addTab('data:text/html,' + pageSource);
|
||||
gBrowser.selectedTab = newTab;
|
||||
|
||||
// Run step2 after the tab loads.
|
||||
gBrowser.getBrowserForTab(newTab)
|
||||
.addEventListener("pageshow", step2);
|
||||
}
|
||||
|
||||
function step2() {
|
||||
// Create the image observer.
|
||||
var observer =
|
||||
new ImageObserver(() => runAfterAsyncEvents(step3), // DECODE_COMPLETE
|
||||
() => runAfterAsyncEvents(step5)); // DISCARD
|
||||
gScriptedObserver = Cc["@mozilla.org/image/tools;1"]
|
||||
.getService(Ci.imgITools)
|
||||
.createScriptedObserver(observer);
|
||||
|
||||
// Clone the current imgIRequest with our new observer.
|
||||
var request = currentRequest();
|
||||
gClonedRequest = request.clone(gScriptedObserver);
|
||||
|
||||
// Check that the image is decoded.
|
||||
forceDecodeImg();
|
||||
|
||||
// The DECODE_COMPLETE notification is delivered asynchronously. ImageObserver will
|
||||
// eventually call step3.
|
||||
}
|
||||
|
||||
function step3() {
|
||||
ok(isImgDecoded(), 'Image should initially be decoded.');
|
||||
|
||||
// Focus the old tab, then fire a memory-pressure notification. This should
|
||||
// cause the decoded image in the new tab to be discarded.
|
||||
gBrowser.selectedTab = oldTab;
|
||||
|
||||
// Allow time to process the tab change.
|
||||
runAfterAsyncEvents(step4);
|
||||
}
|
||||
|
||||
function step4() {
|
||||
gWaitingForDiscard = true;
|
||||
|
||||
var os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
os.notifyObservers(null, 'memory-pressure', 'heap-minimize');
|
||||
|
||||
// The DISCARD notification is delivered asynchronously. ImageObserver will
|
||||
// eventually call step5. (Or else, sadly, the test will time out.)
|
||||
}
|
||||
|
||||
function step5() {
|
||||
ok(true, 'Image should be discarded.');
|
||||
|
||||
// And we're done.
|
||||
gBrowser.removeTab(newTab);
|
||||
prefBranch.setBoolPref('discardable', oldDiscardingPref);
|
||||
|
||||
gClonedRequest.cancelAndForgetObserver(0);
|
||||
|
||||
finish();
|
||||
}
|
||||
92
image/test/browser/browser_docshell_type_editor.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const SIMPLE_HTML = "data:text/html,<html><head></head><body></body></html>";
|
||||
|
||||
// The following URI is *not* accessible to content, hence loading that URI
|
||||
// from an unprivileged site should be blocked. If docshell is of appType
|
||||
// APP_TYPE_EDITOR however the load should be allowed.
|
||||
// >> chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png
|
||||
|
||||
add_task(function* () {
|
||||
info("docshell of appType APP_TYPE_EDITOR can access privileged images.");
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: SIMPLE_HTML
|
||||
}, function* (browser) {
|
||||
yield ContentTask.spawn(browser, null, function* () {
|
||||
let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let defaultAppType = rootDocShell.appType;
|
||||
|
||||
rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_EDITOR;
|
||||
|
||||
is(rootDocShell.appType, Ci.nsIDocShell.APP_TYPE_EDITOR,
|
||||
"sanity check: appType after update should be type editor");
|
||||
|
||||
return new Promise(resolve => {
|
||||
let doc = content.document;
|
||||
let image = doc.createElement("img");
|
||||
image.onload = function() {
|
||||
ok(true, "APP_TYPE_EDITOR is allowed to load privileged image");
|
||||
// restore appType of rootDocShell before moving on to the next test
|
||||
rootDocShell.appType = defaultAppType;
|
||||
resolve();
|
||||
}
|
||||
image.onerror = function() {
|
||||
ok(false, "APP_TYPE_EDITOR is allowed to load privileged image");
|
||||
// restore appType of rootDocShell before moving on to the next test
|
||||
rootDocShell.appType = defaultAppType;
|
||||
resolve();
|
||||
}
|
||||
doc.body.appendChild(image);
|
||||
image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png";
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
info("docshell of appType APP_TYPE_UNKNOWN can *not* access privileged images.");
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: SIMPLE_HTML
|
||||
}, function* (browser) {
|
||||
yield ContentTask.spawn(browser, null, function* () {
|
||||
let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let defaultAppType = rootDocShell.appType;
|
||||
|
||||
rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_UNKNOWN;
|
||||
|
||||
is(rootDocShell.appType, Ci.nsIDocShell.APP_TYPE_UNKNOWN,
|
||||
"sanity check: appType of docshell should be unknown");
|
||||
|
||||
return new Promise(resolve => {
|
||||
let doc = content.document;
|
||||
let image = doc.createElement("img");
|
||||
image.onload = function() {
|
||||
ok(false, "APP_TYPE_UNKNOWN is *not* allowed to acces privileged image");
|
||||
// restore appType of rootDocShell before moving on to the next test
|
||||
rootDocShell.appType = defaultAppType;
|
||||
resolve();
|
||||
}
|
||||
image.onerror = function() {
|
||||
ok(true, "APP_TYPE_UNKNOWN is *not* allowed to acces privileged image");
|
||||
// restore appType of rootDocShell before moving on to the next test
|
||||
rootDocShell.appType = defaultAppType;
|
||||
resolve();
|
||||
}
|
||||
doc.body.appendChild(image);
|
||||
image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png";
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
195
image/test/browser/browser_image.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
waitForExplicitFinish();
|
||||
requestLongerTimeout(2); // see bug 660123 -- this test is slow on Mac.
|
||||
|
||||
// A hold on the current timer, so it doens't get GCed out from
|
||||
// under us
|
||||
var gTimer;
|
||||
|
||||
// Browsing to a new URL - pushing us into the bfcache - should cause
|
||||
// animations to stop, and resume when we return
|
||||
function testBFCache() {
|
||||
function theTest() {
|
||||
var abort = false;
|
||||
var chances, gImage, gFrames;
|
||||
gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "image.html");
|
||||
gBrowser.selectedBrowser.addEventListener("pageshow", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
|
||||
var window = gBrowser.contentWindow;
|
||||
// If false, we are in an optimized build, and we abort this and
|
||||
// all further tests
|
||||
if (!actOnMozImage(window.document, "img1", function(image) {
|
||||
gImage = image;
|
||||
gFrames = gImage.framesNotified;
|
||||
})) {
|
||||
gBrowser.removeCurrentTab();
|
||||
abort = true;
|
||||
}
|
||||
goer.next();
|
||||
}, true);
|
||||
yield;
|
||||
if (abort) {
|
||||
finish();
|
||||
yield; // optimized build
|
||||
}
|
||||
|
||||
// Let animation run for a bit
|
||||
chances = 120;
|
||||
do {
|
||||
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gTimer.initWithCallback(function() {
|
||||
if (gImage.framesNotified >= 20) {
|
||||
goer.send(true);
|
||||
} else {
|
||||
chances--;
|
||||
goer.send(chances == 0); // maybe if we wait a bit, it will happen
|
||||
}
|
||||
}, 500, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
} while (!(yield));
|
||||
is(chances > 0, true, "Must have animated a few frames so far");
|
||||
|
||||
// Browse elsewhere; push our animating page into the bfcache
|
||||
gBrowser.loadURI("about:blank");
|
||||
|
||||
// Wait a bit for page to fully load, then wait a while and
|
||||
// see that no animation occurs.
|
||||
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gTimer.initWithCallback(function() {
|
||||
gFrames = gImage.framesNotified;
|
||||
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gTimer.initWithCallback(function() {
|
||||
// Might have a few stray frames, until other page totally loads
|
||||
var additionalFrames = gImage.framesNotified - gFrames;
|
||||
is(additionalFrames == 0, true, "Must have not animated in bfcache! Got " + additionalFrames + " additional frames");
|
||||
goer.next();
|
||||
}, 4000, Ci.nsITimer.TYPE_ONE_SHOT); // 4 seconds - expect 40 frames
|
||||
}, 0, Ci.nsITimer.TYPE_ONE_SHOT); // delay of 0 - wait for next event loop
|
||||
yield;
|
||||
|
||||
// Go back
|
||||
gBrowser.goBack();
|
||||
|
||||
chances = 120;
|
||||
do {
|
||||
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gTimer.initWithCallback(function() {
|
||||
if (gImage.framesNotified - gFrames >= 20) {
|
||||
goer.send(true);
|
||||
} else {
|
||||
chances--;
|
||||
goer.send(chances == 0); // maybe if we wait a bit, it will happen
|
||||
}
|
||||
}, 500, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
} while (!(yield));
|
||||
is(chances > 0, true, "Must have animated once out of bfcache!");
|
||||
|
||||
// Finally, check that the css background image has essentially the same
|
||||
// # of frames, implying that it animated at the same times as the regular
|
||||
// image. We can easily retrieve regular images through their HTML image
|
||||
// elements, which is what we did before. For the background image, we
|
||||
// create a regular image now, and read the current frame count.
|
||||
var doc = gBrowser.selectedBrowser.contentWindow.document;
|
||||
var div = doc.getElementById("background_div");
|
||||
div.innerHTML += '<img src="animated2.gif" id="img3">';
|
||||
actOnMozImage(doc, "img3", function(image) {
|
||||
is(Math.abs(image.framesNotified - gImage.framesNotified)/gImage.framesNotified < 0.5, true,
|
||||
"Must have also animated the background image, and essentially the same # of frames. " +
|
||||
"Regular image got " + gImage.framesNotified + " frames but background image got " + image.framesNotified);
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
nextTest();
|
||||
}
|
||||
|
||||
var goer = theTest();
|
||||
goer.next();
|
||||
}
|
||||
|
||||
// Check that imgContainers are shared on the same page and
|
||||
// between tabs
|
||||
function testSharedContainers() {
|
||||
function theTest() {
|
||||
var gImages = [];
|
||||
var gFrames;
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "image.html");
|
||||
gBrowser.selectedBrowser.addEventListener("pageshow", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
|
||||
actOnMozImage(gBrowser.contentDocument, "img1", function(image) {
|
||||
gImages[0] = image;
|
||||
gFrames = image.framesNotified; // May in theory have frames from last test
|
||||
// in this counter - so subtract them out
|
||||
});
|
||||
goer.next();
|
||||
}, true);
|
||||
yield;
|
||||
|
||||
// Load next tab somewhat later
|
||||
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gTimer.initWithCallback(function() {
|
||||
goer.next();
|
||||
}, 1500, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
yield;
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "imageX2.html");
|
||||
gBrowser.selectedBrowser.addEventListener("pageshow", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
|
||||
[1,2].forEach(function(i) {
|
||||
actOnMozImage(gBrowser.contentDocument, "img"+i, function(image) {
|
||||
gImages[i] = image;
|
||||
});
|
||||
});
|
||||
goer.next();
|
||||
}, true);
|
||||
yield;
|
||||
|
||||
var chances = 120;
|
||||
do {
|
||||
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gTimer.initWithCallback(function() {
|
||||
if (gImages[0].framesNotified - gFrames >= 10) {
|
||||
goer.send(true);
|
||||
} else {
|
||||
chances--;
|
||||
goer.send(chances == 0); // maybe if we wait a bit, it will happen
|
||||
}
|
||||
}, 500, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
} while (!(yield));
|
||||
is(chances > 0, true, "Must have been animating while showing several images");
|
||||
|
||||
// Check they all have the same frame counts
|
||||
var theFrames = null;
|
||||
[0,1,2].forEach(function(i) {
|
||||
var frames = gImages[i].framesNotified;
|
||||
if (theFrames == null) {
|
||||
theFrames = frames;
|
||||
} else {
|
||||
is(theFrames, frames, "Sharing the same imgContainer means *exactly* the same frame counts!");
|
||||
}
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
nextTest();
|
||||
}
|
||||
|
||||
var goer = theTest();
|
||||
goer.next();
|
||||
}
|
||||
|
||||
var tests = [testBFCache, testSharedContainers];
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
tests.shift()();
|
||||
}
|
||||
|
||||
function test() {
|
||||
ignoreAllUncaughtExceptions();
|
||||
nextTest();
|
||||
}
|
||||
|
||||
26
image/test/browser/head.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const RELATIVE_DIR = "image/test/browser/";
|
||||
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
|
||||
const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR;
|
||||
|
||||
var chrome_root = getRootDirectory(gTestPath);
|
||||
const CHROMEROOT = chrome_root;
|
||||
|
||||
function getImageLoading(doc, id) {
|
||||
var htmlImg = doc.getElementById(id);
|
||||
return htmlImg.QueryInterface(Ci.nsIImageLoadingContent);
|
||||
}
|
||||
|
||||
// Tries to get the Moz debug image, imgIContainerDebug. Only works
|
||||
// in a debug build. If we succeed, we call func().
|
||||
function actOnMozImage(doc, id, func) {
|
||||
var imgContainer = getImageLoading(doc, id).getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST).image;
|
||||
var mozImage;
|
||||
try {
|
||||
mozImage = imgContainer.QueryInterface(Ci.imgIContainerDebug);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
func(mozImage);
|
||||
return true;
|
||||
}
|
||||
24
image/test/browser/image.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Imagelib2 animation tests</title>
|
||||
<style type="text/css">
|
||||
#background_div {
|
||||
border: 1px black solid;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
margin: 10px;
|
||||
background: url(animated2.gif) center center no-repeat;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</head>
|
||||
<body>
|
||||
<p>Page with image</p>
|
||||
<img src="animated.gif" id="img1">
|
||||
<div id="background_div"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
15
image/test/browser/imageX2.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Imagelib2 animation tests</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Page with images</p>
|
||||
<img src="animated.gif" id="img1">
|
||||
<br>
|
||||
<img src="animated.gif" id="img2">
|
||||
</body>
|
||||
</html>
|
||||
|
||||
36
image/test/crashtests/1205923-1.html
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<body>
|
||||
</body>
|
||||
<script>
|
||||
function createImage(loadHandler) {
|
||||
var newImage = new Image;
|
||||
newImage.id = "thepreviewimage";
|
||||
newImage.setAttribute("src", "unsized-svg.svg");
|
||||
if (loadHandler) {
|
||||
newImage.onload = loadHandler;
|
||||
}
|
||||
|
||||
// Query width & height, and display them in document:
|
||||
physWidth = newImage.width;
|
||||
physHeight = newImage.height;
|
||||
document.documentElement.innerHTML +=
|
||||
physWidth + " x " + physHeight + "<br>";
|
||||
}
|
||||
|
||||
function part2() {
|
||||
// Load image again:
|
||||
createImage();
|
||||
|
||||
// End the crashtest.
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
function startTest() {
|
||||
// Trigger image load, and call part2() when it's loaded:
|
||||
createImage(part2);
|
||||
}
|
||||
|
||||
startTest();
|
||||
</script>
|
||||
</html>
|
||||
BIN
image/test/crashtests/1210745-1.gif
Normal file
|
After Width: | Height: | Size: 23 B |
16
image/test/crashtests/1212954-1.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<defs>
|
||||
<filter id="f">
|
||||
<!-- the image data is
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="-5" height="1%" />
|
||||
-->
|
||||
<feImage xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSItNSIgaGVpZ2h0
|
||||
PSIxJSIgLz4K" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width="1" height="1" filter="url(#f)" />
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 464 B |
BIN
image/test/crashtests/1235605.gif
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
17
image/test/crashtests/1241728-1.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<body>
|
||||
<img id='m1' src='data:image/bmp;base64,Qk16AAAAAAAAAJUAABMoAAAAIA4AACAAACYBAAQAAgAAAAB/AAQCAADE9Sj/AAkAAAAAAAAR4AAAAAD/AAAA//8A////AAAAAAAAAAAAAAAAADAAMzMzMzMzMDMzMwMzGAMzMzMzMzMwABEIAf8AAAAzMzMzMzMwADIzMxERERMzMxERERGAADMzMwAAAIAzMzMzMzMzPzMzMwAAADAAADNGMzMzM0JNegAAAAAAABN2AAAAKAAAACAOAAAgFgAAAQAEAAIAAAAAfxYAEgsAABILAAAAABp/VLge/AAA/+D6AAAAAP8AAP//AP///wAAAAAwAAMzMzMzIgOPMzMzOxCA/42NjY2NjY2NjY0zM0EcMzLgMzAzMzMzMzMzMzMzMzMfAAAREREz/xE2MzczMzwWMzMAAACAMzMzLzMzMz8zMzMAAAAwAAAzRkZGRkYzMzAzMwkDMzMwSjMzMxEzMzMwABERAR4EADPyMzMzMzMwFhYWLQAAAIAzERYGFhYWFhYWVhYWFhYWFhYWFhYWFhYWFhYWFhsWAxYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYzM0EcMzLgMzAzMzMWFhYWFhYW//8AAAMWFhYWFhYWFhEZEREDMzMzMzMzMzMwfzMzM1AzMAMzMzQgAAAAEQMzMzMwFwAIAIMzMzMeYRAcMzLgMzAzMzMzAzL4MzMzMzPoAzMzMzMv+i/uMzMzMzM='>
|
||||
<img id='m2' height='2' width='2'>
|
||||
<canvas id='c1'></canvas>
|
||||
<script>
|
||||
var im1=document.getElementById('m1');
|
||||
var im2=document.getElementById('m2');
|
||||
im2.src=im1.src;
|
||||
window.onload=function(){
|
||||
var ctx=document.getElementById('c1').getContext('2d');
|
||||
ctx.drawImage(im1, 0, 0);
|
||||
ctx.drawImage(im2, 0, 0);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
image/test/crashtests/1241729-1.bmp
Normal file
|
After Width: | Height: | Size: 548 B |
5
image/test/crashtests/1241729-1.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<img src="1241729-1.bmp" style="width: 10px; height: 10px;">
|
||||
</body>
|
||||
</html>
|
||||
22
image/test/crashtests/1242093-1.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta http-equiv='Cache-control' content='no-cache'>
|
||||
</head>
|
||||
<body>
|
||||
<img id='m1' src='data:image/bmp;base64,Qk1mDgAAAAAAAHYAAAAoAAAAQAEAAPAAAAABAAQAAgAAAPANAADoAwAA6AMAABAAAAAAAAAA/wD/AAAAAAAAAP8AAP8AAP8AAAD///8AzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAAACAAEAAgABAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAAAAAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRCAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBpRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAIAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICAGgiAAJqAGpEAAAAAgIAaCIAAmoAakQAAAACAgBoIgACagBqRAAAAAICADgRMCIAAmoAakQAAAACAgA4ETAiAAJqAGpEAAAAAgIABhEBVQURA1UDEQFVCREEVQIRBFUCEQFVCBEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQFVCREBVQURAVUFEQFVCBEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQFVCREBVQURAVUFEQFVCBEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQRVBhEBVQURA1UDEQNVBhEBVQYRMCIAAmoAakQAAAACAgAGEQFVBBEBVQMRAVUCEQFVAxEBVQURAVUFEQFVBREBVQgRAVUGETAiAAJqAGpEAAAAAgIABhEBVQQRAVUDEQFVAhEBVQMRAVUFEQFVBREBVQURAVUIEQFVBhEwIgACagBqRAAAAAICAAMRB1UCEQNVAxEEVQYRAVUFEQRVAhEEVQIRB1UDETAiAAJqAGpEAAAAAgIAOBEwIgACagBqRAAAAAICADgRMCIAAmoAakQAAAACAAEAAgABAAE='>
|
||||
<img id='m2' height='2' width='2'>
|
||||
<canvas id='c1'></canvas>
|
||||
<script>
|
||||
var im1=document.getElementById('m1');
|
||||
var im2=document.getElementById('m2');
|
||||
im2.src=im1.src;
|
||||
window.onload=function(){
|
||||
var ctx=document.getElementById('c1').getContext('2d');
|
||||
ctx.drawImage(im1, 0, 0); // sync docoder call
|
||||
ctx.drawImage(im2, 0, 0); // sync downscaler call
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
image/test/crashtests/1242778-1.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
image/test/crashtests/1249576-1.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
51
image/test/crashtests/1251091-1.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<!doctype html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
var gl;
|
||||
|
||||
function start() {
|
||||
var canvas = document.getElementById("glcanvas");
|
||||
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
|
||||
if (gl) {
|
||||
initTextures();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
function initTextures() {
|
||||
var cubeTexture = gl.createTexture();
|
||||
var cubeImage = document.getElementById("i");
|
||||
cubeImage.onload = function() { handleTextureLoaded(cubeImage, cubeTexture); }
|
||||
cubeImage.onerror = function() { finish(); }
|
||||
cubeImage.src = "1251091-1.png";
|
||||
}
|
||||
|
||||
function handleTextureLoaded(image, texture) {
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
setTimeout(showit,0);
|
||||
}
|
||||
|
||||
function showit() {
|
||||
document.getElementById("i").style.display = "";
|
||||
finish();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="start()">
|
||||
<canvas id="glcanvas" width="640" height="480"></canvas>
|
||||
<img id="i" style="display: none;">
|
||||
</body>
|
||||
</html>
|
||||
BIN
image/test/crashtests/1251091-1.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
11
image/test/crashtests/1253362-1.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div style="content: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22background%3A%20url%28data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%253C%252Fsvg%253E%29%22%3E%3C%2Fsvg%3E%0D%0A)"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
image/test/crashtests/256-height.ico
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
image/test/crashtests/256-width.ico
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
image/test/crashtests/463696.bmp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
image/test/crashtests/523528-1.gif
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
image/test/crashtests/523528-2.gif
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
image/test/crashtests/570451.png
Normal file
|
After Width: | Height: | Size: 114 B |
10
image/test/crashtests/681190.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="crashtest" width="50000" height="50000" />
|
||||
<script>
|
||||
document.getElementById("crashtest").mozGetAsFile("foo.png", "image/png");
|
||||
document.getElementById("crashtest").mozGetAsFile("foo.jpeg", "image/jpeg");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
510
image/test/crashtests/694165-1.xhtml
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
<!DOCTYPE html [
|
||||
<!ENTITY thousand-g-elements "
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
<g/><g/><g/><g/><g/>
|
||||
">
|
||||
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<img src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg">
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
&thousand-g-elements;
|
||||
</svg>'/>
|
||||
</html>
|
||||
2
image/test/crashtests/732319-1.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE html>
|
||||
<img crossorigin src="data:a/a,">
|
||||
BIN
image/test/crashtests/83804-1.gif
Normal file
|
After Width: | Height: | Size: 37 B |
10
image/test/crashtests/844403-1.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<color style='background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSINCiAgICAgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPg0KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJibHVlIi8%2BDQo8L3N2Zz4NCg%3D%3D");'></color>
|
||||
<iframe contenteditable=true></iframe>
|
||||
<script>
|
||||
document.body.innerHTML = '';
|
||||
function onload() {
|
||||
e = document.createElementNS("http://www.w3.org/1998/Math/MathML", "share");
|
||||
document.body.appendChild(e);
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", onload, false);
|
||||
</script>
|
||||
BIN
image/test/crashtests/856616.gif
Normal file
|
After Width: | Height: | Size: 27 B |
BIN
image/test/crashtests/89341-1.gif
Normal file
|
After Width: | Height: | Size: 769 B |
BIN
image/test/crashtests/944353.jpg
Normal file
|
After Width: | Height: | Size: 597 KiB |
BIN
image/test/crashtests/colormap-range.gif
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
51
image/test/crashtests/crashtests.list
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Bug 668068 - Maximum (256) width and height icons that we currently interpret as 0-width and 0-height.
|
||||
load 256-height.ico
|
||||
load 256-width.ico
|
||||
|
||||
load 83804-1.gif
|
||||
load 89341-1.gif
|
||||
load 463696.bmp
|
||||
load 570451.png
|
||||
skip-if(Android) load 694165-1.xhtml
|
||||
load 681190.html
|
||||
load 732319-1.html
|
||||
load 844403-1.html
|
||||
load 856616.gif
|
||||
load 944353.jpg
|
||||
load 1205923-1.html
|
||||
# Ensure we handle detecting that an image is animated, then failing to decode
|
||||
# it. (See bug 1210745.)
|
||||
load 1210745-1.gif
|
||||
load 1212954-1.svg
|
||||
load 1235605.gif
|
||||
load 1241728-1.html
|
||||
load 1241729-1.html
|
||||
load 1242093-1.html
|
||||
load 1242778-1.png
|
||||
load 1249576-1.png
|
||||
load 1253362-1.html
|
||||
load colormap-range.gif
|
||||
HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame
|
||||
|
||||
# Animated gifs with a very large canvas, but tiny actual content.
|
||||
load delaytest.html?523528-1.gif
|
||||
load delaytest.html?523528-2.gif
|
||||
|
||||
# Bug 1160801 - Ensure that we handle invalid disposal types.
|
||||
load invalid-disposal-method-1.gif
|
||||
load invalid-disposal-method-2.gif
|
||||
load invalid-disposal-method-3.gif
|
||||
|
||||
load invalid-icc-profile.jpg # This would have exposed the leak discovered in bug 642902
|
||||
|
||||
# Ensure we handle ICO directory entries which specify the wrong size for the contained resource.
|
||||
load invalid_ico_height.ico
|
||||
load invalid_ico_width.ico
|
||||
|
||||
# Bug 525326 - Test image sizes of 65535x65535 which is larger than we allow)
|
||||
load invalid-size.gif
|
||||
load invalid-size-second-frame.gif
|
||||
|
||||
load multiple-png-hassize.ico # Bug 863958 - This icon's size is such that it leads to multiple writes to the PNG decoder after we've gotten our size.
|
||||
asserts(0-2) load ownerdiscard.html # Bug 807211
|
||||
load truncated-second-frame.png # Bug 863975
|
||||
44
image/test/crashtests/delayedframe.sjs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
function getFileStream(filename)
|
||||
{
|
||||
// Get the location of this sjs file, and then use that to figure out where
|
||||
// to find where our other files are.
|
||||
var self = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
self.initWithPath(getState("__LOCATION__"));
|
||||
var file = self.parent;
|
||||
file.append(filename);
|
||||
dump(file.path + "\n");
|
||||
|
||||
var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1']
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
fileStream.init(file, 1, 0, false);
|
||||
|
||||
return fileStream;
|
||||
}
|
||||
|
||||
var gTimer;
|
||||
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
response.processAsync();
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "image/gif", false);
|
||||
|
||||
var firststream = getFileStream("threeframes-start.gif");
|
||||
response.bodyOutputStream.writeFrom(firststream, firststream.available())
|
||||
firststream.close();
|
||||
|
||||
gTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
|
||||
gTimer.initWithCallback(function()
|
||||
{
|
||||
var secondstream = getFileStream("threeframes-end.gif");
|
||||
response.bodyOutputStream.writeFrom(secondstream, secondstream.available())
|
||||
secondstream.close();
|
||||
response.finish();
|
||||
|
||||
// This time needs to be longer than the animation timer in
|
||||
// threeframes-start.gif. That's specified as 100ms; just use 5 seconds as
|
||||
// a reasonable upper bound. Since this is just a crashtest, timeouts
|
||||
// aren't a big deal.
|
||||
}, 5 * 1000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
44
image/test/crashtests/delaytest.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<title>Delayed image reftest wrapper</title>
|
||||
</head>
|
||||
<body>
|
||||
<img id="image1">
|
||||
<script>
|
||||
// This loads a externally specified image, forces a draw (in case of
|
||||
// decode-on-draw), waits 350ms, and then triggers the reftest snapshot.
|
||||
// This allows the animation on the page to complete.
|
||||
//
|
||||
// Use as "delaytest.html?animation.png"
|
||||
//
|
||||
|
||||
// Get the image URL from our URL
|
||||
var imgURL = document.location.search.substr(1);
|
||||
|
||||
// Load the image
|
||||
var img = document.images[0];
|
||||
img.src = imgURL;
|
||||
img.onload = forceDecode;
|
||||
img.onerror = forceDecode;
|
||||
|
||||
function forceDecode() {
|
||||
|
||||
try {
|
||||
// We need to force drawing of the image in an invisible context
|
||||
var canvas = document.createElement("canvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(img, 0, 0);
|
||||
} catch(e) {}
|
||||
|
||||
// We've force the decode. start the timer to trigger the reftest
|
||||
startTimer();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
const delay = 350;
|
||||
setTimeout("document.documentElement.className = '';", delay);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
image/test/crashtests/discardframe.htm
Normal file
|
|
@ -0,0 +1 @@
|
|||
<img src=ie.png>
|
||||
BIN
image/test/crashtests/ie.png
Normal file
|
After Width: | Height: | Size: 456 KiB |
BIN
image/test/crashtests/invalid-disposal-method-1.gif
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
image/test/crashtests/invalid-disposal-method-2.gif
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
image/test/crashtests/invalid-disposal-method-3.gif
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
image/test/crashtests/invalid-icc-profile.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
image/test/crashtests/invalid-size-second-frame.gif
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
image/test/crashtests/invalid-size.gif
Normal file
|
After Width: | Height: | Size: 329 B |
BIN
image/test/crashtests/invalid_ico_height.ico
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
image/test/crashtests/invalid_ico_width.ico
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
image/test/crashtests/multiple-png-hassize.ico
Normal file
|
After Width: | Height: | Size: 18 KiB |
49
image/test/crashtests/ownerdiscard.html
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<body>
|
||||
<div id=tCF0>
|
||||
<img id=victim src=ie.png>
|
||||
<iframe src=discardframe.htm></iframe>
|
||||
</div>
|
||||
<script>
|
||||
const Ci = SpecialPowers.Ci;
|
||||
const Cc = SpecialPowers.Cc;
|
||||
|
||||
function ImageDecoderObserverStub()
|
||||
{
|
||||
this.sizeAvailable = function sizeAvailable(aRequest) {}
|
||||
this.frameComplete = function frameComplete(aRequest) {}
|
||||
this.decodeComplete = function decodeComplete(aRequest) {}
|
||||
this.loadComplete = function loadComplete(aRequest) {}
|
||||
this.frameUpdate = function frameUpdate(aRequest) {}
|
||||
this.discard = function discard(aRequest) {}
|
||||
this.isAnimated = function isAnimated(aRequest) {}
|
||||
}
|
||||
var img = document.getElementById('victim');
|
||||
var observer = new ImageDecoderObserverStub();
|
||||
observer.discard = function() {
|
||||
cleanupAndFinish();
|
||||
}
|
||||
observer = SpecialPowers.wrapCallbackObject(observer);
|
||||
var gObserver = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
|
||||
.createScriptedObserver(observer);
|
||||
var imgLoadingContent =
|
||||
SpecialPowers.wrap(img).QueryInterface(Ci.nsIImageLoadingContent);
|
||||
imgLoadingContent.addObserver(gObserver);
|
||||
function initCF() {
|
||||
setTimeout(function() { document.adoptNode(tCF0); }, 0);
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", initCF, false);
|
||||
|
||||
function cleanupAndFinish() {
|
||||
imgLoadingContent.removeObserver(gObserver);
|
||||
setTimeout("document.documentElement.className = '';", 0);
|
||||
}
|
||||
function loadhandler() {
|
||||
setTimeout("cleanupAndFinish();", 30000);
|
||||
}
|
||||
window.addEventListener("load", function() {
|
||||
SpecialPowers.pushPrefEnv({"set":[["min_discard_timeout_ms", 1]]}, loadhandler)}, false);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
image/test/crashtests/threeframes-end.gif
Normal file
BIN
image/test/crashtests/threeframes-start.gif
Normal file
|
After Width: | Height: | Size: 92 B |
BIN
image/test/crashtests/truncated-second-frame.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
1
image/test/crashtests/unsized-svg.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
|
After Width: | Height: | Size: 47 B |
673
image/test/gtest/Common.cpp
Normal file
|
|
@ -0,0 +1,673 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIDirectoryService.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIProperties.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
using std::abs;
|
||||
using std::vector;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// General Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// These macros work like gtest's ASSERT_* macros, except that they can be used
|
||||
// in functions that return values.
|
||||
#define ASSERT_TRUE_OR_RETURN(e, rv) \
|
||||
EXPECT_TRUE(e); \
|
||||
if (!(e)) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ_OR_RETURN(a, b, rv) \
|
||||
EXPECT_EQ(a, b); \
|
||||
if ((a) != (b)) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_GE_OR_RETURN(a, b, rv) \
|
||||
EXPECT_GE(a, b); \
|
||||
if (!((a) >= (b))) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_LE_OR_RETURN(a, b, rv) \
|
||||
EXPECT_LE(a, b); \
|
||||
if (!((a) <= (b))) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_LT_OR_RETURN(a, b, rv) \
|
||||
EXPECT_LT(a, b); \
|
||||
if (!((a) < (b))) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
already_AddRefed<nsIInputStream>
|
||||
LoadFile(const char* aRelativePath)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIProperties> dirService =
|
||||
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
|
||||
ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr);
|
||||
|
||||
// Retrieve the current working directory.
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR,
|
||||
NS_GET_IID(nsIFile), getter_AddRefs(file));
|
||||
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
|
||||
|
||||
// Construct the final path by appending the working path to the current
|
||||
// working directory.
|
||||
file->AppendNative(nsDependentCString(aRelativePath));
|
||||
|
||||
// Construct an input stream for the requested file.
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
|
||||
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
|
||||
|
||||
// Ensure the resulting input stream is buffered.
|
||||
if (!NS_InputStreamIsBuffered(inputStream)) {
|
||||
nsCOMPtr<nsIInputStream> bufStream;
|
||||
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
|
||||
inputStream, 1024);
|
||||
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
|
||||
inputStream = bufStream;
|
||||
}
|
||||
|
||||
return inputStream.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
IsSolidColor(SourceSurface* aSurface,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
IntSize size = aSurface->GetSize();
|
||||
return RectIsSolidColor(aSurface, IntRect(0, 0, size.width, size.height),
|
||||
aColor, aFuzz);
|
||||
}
|
||||
|
||||
bool
|
||||
IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
return PalettedRectIsSolidColor(aDecoder, currentFrame->GetRect(), aColor);
|
||||
}
|
||||
|
||||
bool
|
||||
RowsAreSolidColor(SourceSurface* aSurface,
|
||||
int32_t aStartRow,
|
||||
int32_t aRowCount,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
IntSize size = aSurface->GetSize();
|
||||
return RectIsSolidColor(aSurface, IntRect(0, aStartRow, size.width, aRowCount),
|
||||
aColor, aFuzz);
|
||||
}
|
||||
|
||||
bool
|
||||
PalettedRowsAreSolidColor(Decoder* aDecoder,
|
||||
int32_t aStartRow,
|
||||
int32_t aRowCount,
|
||||
uint8_t aColor)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
IntRect frameRect = currentFrame->GetRect();
|
||||
IntRect solidColorRect(frameRect.x, aStartRow, frameRect.width, aRowCount);
|
||||
return PalettedRectIsSolidColor(aDecoder, solidColorRect, aColor);
|
||||
}
|
||||
|
||||
bool
|
||||
RectIsSolidColor(SourceSurface* aSurface,
|
||||
const IntRect& aRect,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
IntSize surfaceSize = aSurface->GetSize();
|
||||
IntRect rect =
|
||||
aRect.Intersect(IntRect(0, 0, surfaceSize.width, surfaceSize.height));
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
|
||||
ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false);
|
||||
|
||||
ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false);
|
||||
|
||||
DataSourceSurface::ScopedMap mapping(dataSurface,
|
||||
DataSourceSurface::MapType::READ);
|
||||
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
|
||||
|
||||
uint8_t* data = dataSurface->GetData();
|
||||
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
|
||||
|
||||
int32_t rowLength = dataSurface->Stride();
|
||||
for (int32_t row = rect.y; row < rect.YMost(); ++row) {
|
||||
for (int32_t col = rect.x; col < rect.XMost(); ++col) {
|
||||
int32_t i = row * rowLength + col * 4;
|
||||
if (aFuzz != 0) {
|
||||
ASSERT_LE_OR_RETURN(abs(aColor.mBlue - data[i + 0]), aFuzz, false);
|
||||
ASSERT_LE_OR_RETURN(abs(aColor.mGreen - data[i + 1]), aFuzz, false);
|
||||
ASSERT_LE_OR_RETURN(abs(aColor.mRed - data[i + 2]), aFuzz, false);
|
||||
ASSERT_LE_OR_RETURN(abs(aColor.mAlpha - data[i + 3]), aFuzz, false);
|
||||
} else {
|
||||
ASSERT_EQ_OR_RETURN(aColor.mBlue, data[i + 0], false);
|
||||
ASSERT_EQ_OR_RETURN(aColor.mGreen, data[i + 1], false);
|
||||
ASSERT_EQ_OR_RETURN(aColor.mRed, data[i + 2], false);
|
||||
ASSERT_EQ_OR_RETURN(aColor.mAlpha, data[i + 3], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PalettedRectIsSolidColor(Decoder* aDecoder, const IntRect& aRect, uint8_t aColor)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
uint8_t* imageData;
|
||||
uint32_t imageLength;
|
||||
currentFrame->GetImageData(&imageData, &imageLength);
|
||||
ASSERT_TRUE_OR_RETURN(imageData, false);
|
||||
|
||||
// Clamp to the frame rect. If any pixels outside the frame rect are included,
|
||||
// we immediately fail, because such pixels don't have any "color" in the
|
||||
// sense this function measures - they're transparent, and that doesn't
|
||||
// necessarily correspond to any color palette index at all.
|
||||
IntRect frameRect = currentFrame->GetRect();
|
||||
ASSERT_EQ_OR_RETURN(imageLength, uint32_t(frameRect.Area()), false);
|
||||
IntRect rect = aRect.Intersect(frameRect);
|
||||
ASSERT_EQ_OR_RETURN(rect.Area(), aRect.Area(), false);
|
||||
|
||||
// Translate |rect| by |frameRect.TopLeft()| to reflect the fact that the
|
||||
// frame rect's offset doesn't actually mean anything in terms of the
|
||||
// in-memory representation of the surface. The image data starts at the upper
|
||||
// left corner of the frame rect, in other words.
|
||||
rect -= frameRect.TopLeft();
|
||||
|
||||
// Walk through the image data and make sure that the entire rect has the
|
||||
// palette index |aColor|.
|
||||
int32_t rowLength = frameRect.width;
|
||||
for (int32_t row = rect.y; row < rect.YMost(); ++row) {
|
||||
for (int32_t col = rect.x; col < rect.XMost(); ++col) {
|
||||
int32_t i = row * rowLength + col;
|
||||
ASSERT_EQ_OR_RETURN(aColor, imageData[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
RowHasPixels(SourceSurface* aSurface,
|
||||
int32_t aRow,
|
||||
const vector<BGRAColor>& aPixels)
|
||||
{
|
||||
ASSERT_GE_OR_RETURN(aRow, 0, false);
|
||||
|
||||
IntSize surfaceSize = aSurface->GetSize();
|
||||
ASSERT_EQ_OR_RETURN(aPixels.size(), size_t(surfaceSize.width), false);
|
||||
ASSERT_LT_OR_RETURN(aRow, surfaceSize.height, false);
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
|
||||
ASSERT_TRUE_OR_RETURN(dataSurface, false);
|
||||
|
||||
ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false);
|
||||
|
||||
DataSourceSurface::ScopedMap mapping(dataSurface,
|
||||
DataSourceSurface::MapType::READ);
|
||||
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
|
||||
|
||||
uint8_t* data = dataSurface->GetData();
|
||||
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
|
||||
|
||||
int32_t rowLength = dataSurface->Stride();
|
||||
for (int32_t col = 0; col < surfaceSize.width; ++col) {
|
||||
int32_t i = aRow * rowLength + col * 4;
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mBlue, data[i + 0], false);
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mGreen, data[i + 1], false);
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mRed, data[i + 2], false);
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mAlpha, data[i + 3], false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SurfacePipe Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
already_AddRefed<Decoder>
|
||||
CreateTrivialDecoder()
|
||||
{
|
||||
gfxPrefs::GetSingleton();
|
||||
DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
||||
DefaultSurfaceFlags());
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
void
|
||||
AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
|
||||
const gfx::IntRect& aInputSpaceRect,
|
||||
const gfx::IntRect& aOutputSpaceRect)
|
||||
{
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(aInputSpaceRect, invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(aOutputSpaceRect, invalidRect->mOutputSpaceRect);
|
||||
}
|
||||
|
||||
void
|
||||
CheckGeneratedImage(Decoder* aDecoder,
|
||||
const IntRect& aRect,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
const IntSize surfaceSize = surface->GetSize();
|
||||
|
||||
// This diagram shows how the surface is divided into regions that the code
|
||||
// below tests for the correct content. The output rect is the bounds of the
|
||||
// region labeled 'C'.
|
||||
//
|
||||
// +---------------------------+
|
||||
// | A |
|
||||
// +---------+--------+--------+
|
||||
// | B | C | D |
|
||||
// +---------+--------+--------+
|
||||
// | E |
|
||||
// +---------------------------+
|
||||
|
||||
// Check that the output rect itself is green. (Region 'C'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface, aRect, BGRAColor::Green(), aFuzz));
|
||||
|
||||
// Check that the area above the output rect is transparent. (Region 'A'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(0, 0, surfaceSize.width, aRect.y),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
|
||||
// Check that the area to the left of the output rect is transparent. (Region 'B'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
|
||||
// Check that the area to the right of the output rect is transparent. (Region 'D'.)
|
||||
const int32_t widthOnRight = surfaceSize.width - aRect.XMost();
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
|
||||
// Check that the area below the output rect is transparent. (Region 'E'.)
|
||||
const int32_t heightBelow = surfaceSize.height - aRect.YMost();
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
}
|
||||
|
||||
void
|
||||
CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
IntSize imageSize = currentFrame->GetImageSize();
|
||||
|
||||
// This diagram shows how the surface is divided into regions that the code
|
||||
// below tests for the correct content. The output rect is the bounds of the
|
||||
// region labeled 'C'.
|
||||
//
|
||||
// +---------------------------+
|
||||
// | A |
|
||||
// +---------+--------+--------+
|
||||
// | B | C | D |
|
||||
// +---------+--------+--------+
|
||||
// | E |
|
||||
// +---------------------------+
|
||||
|
||||
// Check that the output rect itself is all 255's. (Region 'C'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, aRect, 255));
|
||||
|
||||
// Check that the area above the output rect is all 0's. (Region 'A'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, 0, imageSize.width, aRect.y),
|
||||
0));
|
||||
|
||||
// Check that the area to the left of the output rect is all 0's. (Region 'B'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
|
||||
0));
|
||||
|
||||
// Check that the area to the right of the output rect is all 0's. (Region 'D'.)
|
||||
const int32_t widthOnRight = imageSize.width - aRect.XMost();
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
|
||||
0));
|
||||
|
||||
// Check that the area below the output rect is transparent. (Region 'E'.)
|
||||
const int32_t heightBelow = imageSize.height - aRect.YMost();
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, aRect.YMost(), imageSize.width, heightBelow),
|
||||
0));
|
||||
}
|
||||
|
||||
void
|
||||
CheckWritePixels(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
Maybe<IntRect> aOutputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputWriteRect /* = Nothing() */,
|
||||
Maybe<IntRect> aOutputWriteRect /* = Nothing() */,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100));
|
||||
IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100));
|
||||
IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect);
|
||||
IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect);
|
||||
|
||||
// Fill the image.
|
||||
int32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&] {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect);
|
||||
|
||||
// Attempt to write more data and make sure nothing changes.
|
||||
const int32_t oldCount = count;
|
||||
result = aFilter->WritePixels<uint32_t>([&] {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
});
|
||||
EXPECT_EQ(oldCount, count);
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
// Attempt to advance to the next row and make sure nothing changes.
|
||||
aFilter->AdvanceRow();
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
// Check that the generated image is correct.
|
||||
CheckGeneratedImage(aDecoder, outputWriteRect, aFuzz);
|
||||
}
|
||||
|
||||
void
|
||||
CheckPalettedWritePixels(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
Maybe<IntRect> aOutputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputWriteRect /* = Nothing() */,
|
||||
Maybe<IntRect> aOutputWriteRect /* = Nothing() */,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100));
|
||||
IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100));
|
||||
IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect);
|
||||
IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect);
|
||||
|
||||
// Fill the image.
|
||||
int32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint8_t>([&] {
|
||||
++count;
|
||||
return AsVariant(uint8_t(255));
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect);
|
||||
|
||||
// Attempt to write more data and make sure nothing changes.
|
||||
const int32_t oldCount = count;
|
||||
result = aFilter->WritePixels<uint8_t>([&] {
|
||||
++count;
|
||||
return AsVariant(uint8_t(255));
|
||||
});
|
||||
EXPECT_EQ(oldCount, count);
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
// Attempt to advance to the next row and make sure nothing changes.
|
||||
aFilter->AdvanceRow();
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
// Check that the generated image is correct.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
uint8_t* imageData;
|
||||
uint32_t imageLength;
|
||||
currentFrame->GetImageData(&imageData, &imageLength);
|
||||
ASSERT_TRUE(imageData != nullptr);
|
||||
ASSERT_EQ(outputWriteRect.width * outputWriteRect.height, int32_t(imageLength));
|
||||
for (uint32_t i = 0; i < imageLength; ++i) {
|
||||
ASSERT_EQ(uint8_t(255), imageData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test Data
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImageTestCase GreenPNGTestCase()
|
||||
{
|
||||
return ImageTestCase("green.png", "image/png", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase GreenGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("green.gif", "image/gif", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase GreenJPGTestCase()
|
||||
{
|
||||
return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100),
|
||||
TEST_CASE_IS_FUZZY);
|
||||
}
|
||||
|
||||
ImageTestCase GreenBMPTestCase()
|
||||
{
|
||||
return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase GreenICOTestCase()
|
||||
{
|
||||
// This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
|
||||
// when the BMP is embedded in an ICO, so it's transparent.
|
||||
return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase GreenIconTestCase()
|
||||
{
|
||||
return ImageTestCase("green.icon", "image/icon", IntSize(100, 100),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase GreenFirstFrameAnimatedGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100),
|
||||
TEST_CASE_IS_ANIMATED);
|
||||
}
|
||||
|
||||
ImageTestCase GreenFirstFrameAnimatedPNGTestCase()
|
||||
{
|
||||
return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
|
||||
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptTestCase()
|
||||
{
|
||||
return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
|
||||
TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptBMPWithTruncatedHeader()
|
||||
{
|
||||
// This BMP has a header which is truncated right between the BIH and the
|
||||
// bitfields, which is a particularly error-prone place w.r.t. the BMP decoder
|
||||
// state machine.
|
||||
return ImageTestCase("invalid-truncated-metadata.bmp", "image/bmp",
|
||||
IntSize(100, 100), TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptICOWithBadBMPWidthTestCase()
|
||||
{
|
||||
// This ICO contains a BMP icon which has a width that doesn't match the size
|
||||
// listed in the corresponding ICO directory entry.
|
||||
return ImageTestCase("corrupt-with-bad-bmp-width.ico", "image/x-icon",
|
||||
IntSize(100, 100), TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptICOWithBadBMPHeightTestCase()
|
||||
{
|
||||
// This ICO contains a BMP icon which has a height that doesn't match the size
|
||||
// listed in the corresponding ICO directory entry.
|
||||
return ImageTestCase("corrupt-with-bad-bmp-height.ico", "image/x-icon",
|
||||
IntSize(100, 100), TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase TransparentPNGTestCase()
|
||||
{
|
||||
return ImageTestCase("transparent.png", "image/png", IntSize(32, 32),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase TransparentGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase FirstFramePaddingGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags)
|
||||
{
|
||||
// This is a BMP that is only transparent when decoded as if it is within an
|
||||
// ICO file. (Note: aFlags needs to be set to TEST_CASE_DEFAULT_FLAGS or
|
||||
// TEST_CASE_IS_TRANSPARENT accordingly.)
|
||||
return ImageTestCase("transparent-if-within-ico.bmp", "image/bmp",
|
||||
IntSize(32, 32), aFlags);
|
||||
}
|
||||
|
||||
ImageTestCase RLE4BMPTestCase()
|
||||
{
|
||||
return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase RLE8BMPTestCase()
|
||||
{
|
||||
return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase NoFrameDelayGIFTestCase()
|
||||
{
|
||||
// This is an invalid (or at least, questionably valid) GIF that's animated
|
||||
// even though it specifies a frame delay of zero. It's animated, but it's not
|
||||
// marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that
|
||||
// it's animated.
|
||||
return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase()
|
||||
{
|
||||
// This is a corrupt GIF that has extra image sub blocks between the first and
|
||||
// second frame.
|
||||
return ImageTestCase("animated-with-extra-image-sub-blocks.gif", "image/gif",
|
||||
IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledPNGTestCase()
|
||||
{
|
||||
// This testcase (and all the other "downscaled") testcases) consists of 25
|
||||
// lines of green, followed by 25 lines of red, followed by 25 lines of green,
|
||||
// followed by 25 more lines of red. It's intended that tests downscale it
|
||||
// from 100x100 to 20x20, so we specify a 20x20 output size.
|
||||
return ImageTestCase("downscaled.png", "image/png", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.gif", "image/gif", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledJPGTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.jpg", "image/jpeg", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledBMPTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.bmp", "image/bmp", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledICOTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.ico", "image/x-icon", IntSize(100, 100),
|
||||
IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledIconTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.icon", "image/icon", IntSize(100, 100),
|
||||
IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase()
|
||||
{
|
||||
// This test case is an ICO with AND mask transparency. We want to ensure that
|
||||
// we can downscale it without crashing or triggering ASAN failures, but its
|
||||
// content isn't simple to verify, so for now we don't check the output.
|
||||
return ImageTestCase("transparent-ico-with-and-mask.ico", "image/x-icon",
|
||||
IntSize(32, 32), IntSize(20, 20),
|
||||
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IGNORE_OUTPUT);
|
||||
}
|
||||
|
||||
ImageTestCase TruncatedSmallGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1));
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
419
image/test/gtest/Common.h
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_image_test_gtest_Common_h
|
||||
#define mozilla_image_test_gtest_Common_h
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Decoder.h"
|
||||
#include "gfxColor.h"
|
||||
#include "imgITools.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "SurfacePipe.h"
|
||||
#include "SurfacePipeFactory.h"
|
||||
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Types
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum TestCaseFlags
|
||||
{
|
||||
TEST_CASE_DEFAULT_FLAGS = 0,
|
||||
TEST_CASE_IS_FUZZY = 1 << 0,
|
||||
TEST_CASE_HAS_ERROR = 1 << 1,
|
||||
TEST_CASE_IS_TRANSPARENT = 1 << 2,
|
||||
TEST_CASE_IS_ANIMATED = 1 << 3,
|
||||
TEST_CASE_IGNORE_OUTPUT = 1 << 4,
|
||||
};
|
||||
|
||||
struct ImageTestCase
|
||||
{
|
||||
ImageTestCase(const char* aPath,
|
||||
const char* aMimeType,
|
||||
gfx::IntSize aSize,
|
||||
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
|
||||
: mPath(aPath)
|
||||
, mMimeType(aMimeType)
|
||||
, mSize(aSize)
|
||||
, mOutputSize(aSize)
|
||||
, mFlags(aFlags)
|
||||
{ }
|
||||
|
||||
ImageTestCase(const char* aPath,
|
||||
const char* aMimeType,
|
||||
gfx::IntSize aSize,
|
||||
gfx::IntSize aOutputSize,
|
||||
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
|
||||
: mPath(aPath)
|
||||
, mMimeType(aMimeType)
|
||||
, mSize(aSize)
|
||||
, mOutputSize(aOutputSize)
|
||||
, mFlags(aFlags)
|
||||
{ }
|
||||
|
||||
const char* mPath;
|
||||
const char* mMimeType;
|
||||
gfx::IntSize mSize;
|
||||
gfx::IntSize mOutputSize;
|
||||
uint32_t mFlags;
|
||||
};
|
||||
|
||||
struct BGRAColor
|
||||
{
|
||||
BGRAColor() : BGRAColor(0, 0, 0, 0) { }
|
||||
|
||||
BGRAColor(uint8_t aBlue, uint8_t aGreen, uint8_t aRed, uint8_t aAlpha)
|
||||
: mBlue(aBlue)
|
||||
, mGreen(aGreen)
|
||||
, mRed(aRed)
|
||||
, mAlpha(aAlpha)
|
||||
{ }
|
||||
|
||||
static BGRAColor Green() { return BGRAColor(0x00, 0xFF, 0x00, 0xFF); }
|
||||
static BGRAColor Red() { return BGRAColor(0x00, 0x00, 0xFF, 0xFF); }
|
||||
static BGRAColor Blue() { return BGRAColor(0xFF, 0x00, 0x00, 0xFF); }
|
||||
static BGRAColor Transparent() { return BGRAColor(0x00, 0x00, 0x00, 0x00); }
|
||||
|
||||
uint32_t AsPixel() const { return gfxPackedPixel(mAlpha, mRed, mGreen, mBlue); }
|
||||
|
||||
uint8_t mBlue;
|
||||
uint8_t mGreen;
|
||||
uint8_t mRed;
|
||||
uint8_t mAlpha;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// General Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* A RAII class that ensure that ImageLib services are available. Any tests that
|
||||
* require ImageLib to be initialized (for example, any test that uses the
|
||||
* SurfaceCache; see image::EnsureModuleInitialized() for the full list) can
|
||||
* use this class to ensure that ImageLib services are available. Failure to do
|
||||
* so can result in strange, non-deterministic failures.
|
||||
*/
|
||||
struct AutoInitializeImageLib
|
||||
{
|
||||
AutoInitializeImageLib()
|
||||
{
|
||||
// Ensure that ImageLib services are initialized.
|
||||
nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1");
|
||||
EXPECT_TRUE(imgTools != nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
/// Loads a file from the current directory. @return an nsIInputStream for it.
|
||||
already_AddRefed<nsIInputStream> LoadFile(const char* aRelativePath);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel of @aSurface is @aColor.
|
||||
*
|
||||
* If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color
|
||||
* component. This may be necessary for tests that involve JPEG images or
|
||||
* downscaling.
|
||||
*/
|
||||
bool IsSolidColor(gfx::SourceSurface* aSurface,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel of @aDecoder's surface has the palette index
|
||||
* specified by @aColor.
|
||||
*/
|
||||
bool IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel in the range of rows specified by @aStartRow and
|
||||
* @aRowCount of @aSurface is @aColor.
|
||||
*
|
||||
* If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color
|
||||
* component. This may be necessary for tests that involve JPEG images or
|
||||
* downscaling.
|
||||
*/
|
||||
bool RowsAreSolidColor(gfx::SourceSurface* aSurface,
|
||||
int32_t aStartRow,
|
||||
int32_t aRowCount,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel in the range of rows specified by @aStartRow and
|
||||
* @aRowCount of @aDecoder's surface has the palette index specified by @aColor.
|
||||
*/
|
||||
bool PalettedRowsAreSolidColor(Decoder* aDecoder,
|
||||
int32_t aStartRow,
|
||||
int32_t aRowCount,
|
||||
uint8_t aColor);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel in the rect specified by @aRect is @aColor.
|
||||
*
|
||||
* If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color
|
||||
* component. This may be necessary for tests that involve JPEG images or
|
||||
* downscaling.
|
||||
*/
|
||||
bool RectIsSolidColor(gfx::SourceSurface* aSurface,
|
||||
const gfx::IntRect& aRect,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel in the rect specified by @aRect has the palette
|
||||
* index specified by @aColor.
|
||||
*/
|
||||
bool PalettedRectIsSolidColor(Decoder* aDecoder,
|
||||
const gfx::IntRect& aRect,
|
||||
uint8_t aColor);
|
||||
|
||||
/**
|
||||
* @returns true if the pixels in @aRow of @aSurface match the pixels given in
|
||||
* @aPixels.
|
||||
*/
|
||||
bool RowHasPixels(gfx::SourceSurface* aSurface,
|
||||
int32_t aRow,
|
||||
const std::vector<BGRAColor>& aPixels);
|
||||
|
||||
// ExpectNoResume is an IResumable implementation for use by tests that expect
|
||||
// Resume() to never get called.
|
||||
class ExpectNoResume final : public IResumable
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExpectNoResume, override)
|
||||
|
||||
void Resume() override { FAIL() << "Resume() should not get called"; }
|
||||
|
||||
private:
|
||||
~ExpectNoResume() override { }
|
||||
};
|
||||
|
||||
// CountResumes is an IResumable implementation for use by tests that expect
|
||||
// Resume() to get called a certain number of times.
|
||||
class CountResumes : public IResumable
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CountResumes, override)
|
||||
|
||||
CountResumes() : mCount(0) { }
|
||||
|
||||
void Resume() override { mCount++; }
|
||||
uint32_t Count() const { return mCount; }
|
||||
|
||||
private:
|
||||
~CountResumes() override { }
|
||||
|
||||
uint32_t mCount;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SurfacePipe Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Creates a decoder with no data associated with, suitable for testing code
|
||||
* that requires a decoder to initialize or to allocate surfaces but doesn't
|
||||
* actually need the decoder to do any decoding.
|
||||
*
|
||||
* XXX(seth): We only need this because SurfaceSink and PalettedSurfaceSink
|
||||
* defer to the decoder for surface allocation. Once all decoders use
|
||||
* SurfacePipe we won't need to do that anymore and we can remove this function.
|
||||
*/
|
||||
already_AddRefed<Decoder> CreateTrivialDecoder();
|
||||
|
||||
/**
|
||||
* Creates a pipeline of SurfaceFilters from a list of Config structs and passes
|
||||
* it to the provided lambda @aFunc. Assertions that the pipeline is constructly
|
||||
* correctly and cleanup of any allocated surfaces is handled automatically.
|
||||
*
|
||||
* @param aDecoder The decoder to use for allocating surfaces.
|
||||
* @param aFunc The lambda function to pass the filter pipeline to.
|
||||
* @param aConfigs The configuration for the pipeline.
|
||||
*/
|
||||
template <typename Func, typename... Configs>
|
||||
void WithFilterPipeline(Decoder* aDecoder, Func aFunc, Configs... aConfigs)
|
||||
{
|
||||
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
|
||||
nsresult rv = pipe->Configure(aConfigs...);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
aFunc(aDecoder, pipe.get());
|
||||
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
if (currentFrame) {
|
||||
currentFrame->Finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pipeline of SurfaceFilters from a list of Config structs and
|
||||
* asserts that configuring it fails. Cleanup of any allocated surfaces is
|
||||
* handled automatically.
|
||||
*
|
||||
* @param aDecoder The decoder to use for allocating surfaces.
|
||||
* @param aConfigs The configuration for the pipeline.
|
||||
*/
|
||||
template <typename... Configs>
|
||||
void AssertConfiguringPipelineFails(Decoder* aDecoder, Configs... aConfigs)
|
||||
{
|
||||
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
|
||||
nsresult rv = pipe->Configure(aConfigs...);
|
||||
|
||||
// Callers expect configuring the pipeline to fail.
|
||||
ASSERT_TRUE(NS_FAILED(rv));
|
||||
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
if (currentFrame) {
|
||||
currentFrame->Finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the provided filter pipeline is in the correct final state,
|
||||
* which is to say, the entire surface has been written to (IsSurfaceFinished()
|
||||
* returns true) and the invalid rects are as expected.
|
||||
*
|
||||
* @param aFilter The filter pipeline to check.
|
||||
* @param aInputSpaceRect The expect invalid rect, in input space.
|
||||
* @param aoutputSpaceRect The expect invalid rect, in output space.
|
||||
*/
|
||||
void AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
|
||||
const gfx::IntRect& aInputSpaceRect,
|
||||
const gfx::IntRect& aOutputSpaceRect);
|
||||
|
||||
/**
|
||||
* Checks a generated image for correctness. Reports any unexpected deviation
|
||||
* from the expected image as GTest failures.
|
||||
*
|
||||
* @param aDecoder The decoder which contains the image. The decoder's current
|
||||
* frame will be checked.
|
||||
* @param aRect The region in the space of the output surface that the filter
|
||||
* pipeline will actually write to. It's expected that pixels in
|
||||
* this region are green, while pixels outside this region are
|
||||
* transparent.
|
||||
* @param aFuzz The amount of fuzz to use in pixel comparisons.
|
||||
*/
|
||||
void CheckGeneratedImage(Decoder* aDecoder,
|
||||
const gfx::IntRect& aRect,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* Checks a generated paletted image for correctness. Reports any unexpected
|
||||
* deviation from the expected image as GTest failures.
|
||||
*
|
||||
* @param aDecoder The decoder which contains the image. The decoder's current
|
||||
* frame will be checked.
|
||||
* @param aRect The region in the space of the output surface that the filter
|
||||
* pipeline will actually write to. It's expected that pixels in
|
||||
* this region have a palette index of 255, while pixels outside
|
||||
* this region have a palette index of 0.
|
||||
*/
|
||||
void CheckGeneratedPalettedImage(Decoder* aDecoder, const gfx::IntRect& aRect);
|
||||
|
||||
/**
|
||||
* Tests the result of calling WritePixels() using the provided SurfaceFilter
|
||||
* pipeline. The pipeline must be a normal (i.e., non-paletted) pipeline.
|
||||
*
|
||||
* The arguments are specified in the an order intended to minimize the number
|
||||
* of arguments that most test cases need to pass.
|
||||
*
|
||||
* @param aDecoder The decoder whose current frame will be written to.
|
||||
* @param aFilter The SurfaceFilter pipeline to use.
|
||||
* @param aOutputRect The region in the space of the output surface that will be
|
||||
* invalidated by the filter pipeline. Defaults to
|
||||
* (0, 0, 100, 100).
|
||||
* @param aInputRect The region in the space of the input image that will be
|
||||
* invalidated by the filter pipeline. Defaults to
|
||||
* (0, 0, 100, 100).
|
||||
* @param aInputWriteRect The region in the space of the input image that the
|
||||
* filter pipeline will allow writes to. Note the
|
||||
* difference from @aInputRect: @aInputRect is the actual
|
||||
* region invalidated, while @aInputWriteRect is the
|
||||
* region that is written to. These can differ in cases
|
||||
* where the input is not clipped to the size of the image.
|
||||
* Defaults to the entire input rect.
|
||||
* @param aOutputWriteRect The region in the space of the output surface that
|
||||
* the filter pipeline will actually write to. It's
|
||||
* expected that pixels in this region are green, while
|
||||
* pixels outside this region are transparent. Defaults
|
||||
* to the entire output rect.
|
||||
*/
|
||||
void CheckWritePixels(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
Maybe<gfx::IntRect> aOutputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputWriteRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aOutputWriteRect = Nothing(),
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* Tests the result of calling WritePixels() using the provided SurfaceFilter
|
||||
* pipeline. The pipeline must be a paletted pipeline.
|
||||
* @see CheckWritePixels() for documentation of the arguments.
|
||||
*/
|
||||
void CheckPalettedWritePixels(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
Maybe<gfx::IntRect> aOutputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputWriteRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aOutputWriteRect = Nothing(),
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test Data
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImageTestCase GreenPNGTestCase();
|
||||
ImageTestCase GreenGIFTestCase();
|
||||
ImageTestCase GreenJPGTestCase();
|
||||
ImageTestCase GreenBMPTestCase();
|
||||
ImageTestCase GreenICOTestCase();
|
||||
ImageTestCase GreenIconTestCase();
|
||||
|
||||
ImageTestCase GreenFirstFrameAnimatedGIFTestCase();
|
||||
ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
|
||||
|
||||
ImageTestCase CorruptTestCase();
|
||||
ImageTestCase CorruptBMPWithTruncatedHeader();
|
||||
ImageTestCase CorruptICOWithBadBMPWidthTestCase();
|
||||
ImageTestCase CorruptICOWithBadBMPHeightTestCase();
|
||||
|
||||
ImageTestCase TransparentPNGTestCase();
|
||||
ImageTestCase TransparentGIFTestCase();
|
||||
ImageTestCase FirstFramePaddingGIFTestCase();
|
||||
ImageTestCase NoFrameDelayGIFTestCase();
|
||||
ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase();
|
||||
|
||||
ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase();
|
||||
ImageTestCase RLE4BMPTestCase();
|
||||
ImageTestCase RLE8BMPTestCase();
|
||||
|
||||
ImageTestCase DownscaledPNGTestCase();
|
||||
ImageTestCase DownscaledGIFTestCase();
|
||||
ImageTestCase DownscaledJPGTestCase();
|
||||
ImageTestCase DownscaledBMPTestCase();
|
||||
ImageTestCase DownscaledICOTestCase();
|
||||
ImageTestCase DownscaledIconTestCase();
|
||||
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase();
|
||||
|
||||
ImageTestCase TruncatedSmallGIFTestCase();
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_test_gtest_Common_h
|
||||
671
image/test/gtest/TestADAM7InterpolatingFilter.cpp
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfaceFilters.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
using std::generate;
|
||||
using std::vector;
|
||||
|
||||
template <typename Func> void
|
||||
WithADAM7InterpolatingFilter(const IntSize& aSize, Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
ADAM7InterpolatingConfig { },
|
||||
SurfaceConfig { decoder, 0, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringADAM7InterpolatingFilterFails(const IntSize& aSize)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
ADAM7InterpolatingConfig { },
|
||||
SurfaceConfig { decoder, 0, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
uint8_t
|
||||
InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
|
||||
{
|
||||
return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
|
||||
}
|
||||
|
||||
BGRAColor
|
||||
InterpolateColors(BGRAColor aColor1, BGRAColor aColor2, float aWeight)
|
||||
{
|
||||
return BGRAColor(InterpolateByte(aColor1.mBlue, aColor2.mBlue, aWeight),
|
||||
InterpolateByte(aColor1.mGreen, aColor2.mGreen, aWeight),
|
||||
InterpolateByte(aColor1.mRed, aColor2.mRed, aWeight),
|
||||
InterpolateByte(aColor1.mAlpha, aColor2.mAlpha, aWeight));
|
||||
}
|
||||
|
||||
enum class ShouldInterpolate
|
||||
{
|
||||
eYes,
|
||||
eNo
|
||||
};
|
||||
|
||||
BGRAColor
|
||||
HorizontallyInterpolatedPixel(uint32_t aCol,
|
||||
uint32_t aWidth,
|
||||
const vector<float>& aWeights,
|
||||
ShouldInterpolate aShouldInterpolate,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
// We cycle through the vector of weights forever.
|
||||
float weight = aWeights[aCol % aWeights.size()];
|
||||
|
||||
// Find the columns of the two final pixels for this set of weights.
|
||||
uint32_t finalPixel1 = aCol - aCol % aWeights.size();
|
||||
uint32_t finalPixel2 = finalPixel1 + aWeights.size();
|
||||
|
||||
// If |finalPixel2| is past the end of the row, that means that there is no
|
||||
// final pixel after the pixel at |finalPixel1|. In that case, we just want to
|
||||
// duplicate |finalPixel1|'s color until the end of the row. We can do that by
|
||||
// setting |finalPixel2| equal to |finalPixel1| so that the interpolation has
|
||||
// no effect.
|
||||
if (finalPixel2 >= aWidth) {
|
||||
finalPixel2 = finalPixel1;
|
||||
}
|
||||
|
||||
// We cycle through the vector of colors forever (subject to the above
|
||||
// constraint about the end of the row).
|
||||
BGRAColor color1 = aColors[finalPixel1 % aColors.size()];
|
||||
BGRAColor color2 = aColors[finalPixel2 % aColors.size()];
|
||||
|
||||
// If we're not interpolating, we treat all pixels which aren't final as
|
||||
// transparent. Since the number of weights we have is equal to the stride
|
||||
// between final pixels, we can check if |aCol| is a final pixel by checking
|
||||
// whether |aCol| is a multiple of |aWeights.size()|.
|
||||
if (aShouldInterpolate == ShouldInterpolate::eNo) {
|
||||
return aCol % aWeights.size() == 0 ? color1
|
||||
: BGRAColor::Transparent();
|
||||
}
|
||||
|
||||
// Interpolate.
|
||||
return InterpolateColors(color1, color2, weight);
|
||||
}
|
||||
|
||||
vector<float>&
|
||||
InterpolationWeights(int32_t aStride)
|
||||
{
|
||||
// Precalculated interpolation weights. These are used to interpolate
|
||||
// between final pixels or between important rows. Although no interpolation
|
||||
// is actually applied to the previous final pixel or important row value,
|
||||
// the arrays still start with 1.0f, which is always skipped, primarily
|
||||
// because otherwise |stride1Weights| would have zero elements.
|
||||
static vector<float> stride8Weights =
|
||||
{ 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
|
||||
static vector<float> stride4Weights = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
|
||||
static vector<float> stride2Weights = { 1.0f, 1 / 2.0f };
|
||||
static vector<float> stride1Weights = { 1.0f };
|
||||
|
||||
switch (aStride) {
|
||||
case 8: return stride8Weights;
|
||||
case 4: return stride4Weights;
|
||||
case 2: return stride2Weights;
|
||||
case 1: return stride1Weights;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
int32_t
|
||||
ImportantRowStride(uint8_t aPass)
|
||||
{
|
||||
// The stride between important rows for each pass, with a dummy value for
|
||||
// the nonexistent pass 0 and for pass 8, since the tests run an extra pass to
|
||||
// make sure nothing breaks.
|
||||
static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1, 1 };
|
||||
|
||||
return strides[aPass];
|
||||
}
|
||||
|
||||
size_t
|
||||
FinalPixelStride(uint8_t aPass)
|
||||
{
|
||||
// The stride between the final pixels in important rows for each pass, with
|
||||
// a dummy value for the nonexistent pass 0 and for pass 8, since the tests
|
||||
// run an extra pass to make sure nothing breaks.
|
||||
static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1, 1 };
|
||||
|
||||
return strides[aPass];
|
||||
}
|
||||
|
||||
bool
|
||||
IsImportantRow(int32_t aRow, uint8_t aPass)
|
||||
{
|
||||
return aRow % ImportantRowStride(aPass) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new
|
||||
* set of pixels in each block receives their final values, according to the
|
||||
* following pattern:
|
||||
*
|
||||
* 1 6 4 6 2 6 4 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 5 6 5 6 5 6 5 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 3 6 4 6 3 6 4 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 5 6 5 6 5 6 5 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
*
|
||||
* This function produces a row of pixels @aWidth wide, suitable for testing
|
||||
* horizontal interpolation on pass @aPass. The pattern of pixels used is
|
||||
* determined by @aPass and @aRow, which determine which pixels are final
|
||||
* according to the table above, and @aColors, from which the pixel values
|
||||
* are selected.
|
||||
*
|
||||
* There are two different behaviors: if |eNo| is passed for
|
||||
* @aShouldInterpolate, non-final pixels are treated as transparent. If |eNo|
|
||||
* is passed, non-final pixels get interpolated in from the surrounding final
|
||||
* pixels. The intention is that |eNo| is passed to generate input which will
|
||||
* be run through ADAM7InterpolatingFilter, and |eYes| is passed to generate
|
||||
* reference data to check that the filter is performing horizontal
|
||||
* interpolation correctly.
|
||||
*
|
||||
* This function does not perform vertical interpolation. Rows which aren't on
|
||||
* the current pass are filled with transparent pixels.
|
||||
*
|
||||
* @return a vector<BGRAColor> representing a row of pixels.
|
||||
*/
|
||||
vector<BGRAColor>
|
||||
ADAM7HorizontallyInterpolatedRow(uint8_t aPass,
|
||||
uint32_t aRow,
|
||||
uint32_t aWidth,
|
||||
ShouldInterpolate aShouldInterpolate,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
EXPECT_GT(aPass, 0);
|
||||
EXPECT_LE(aPass, 8);
|
||||
EXPECT_GT(aColors.size(), 0u);
|
||||
|
||||
vector<BGRAColor> result(aWidth);
|
||||
|
||||
if (IsImportantRow(aRow, aPass)) {
|
||||
vector<float>& weights = InterpolationWeights(FinalPixelStride(aPass));
|
||||
|
||||
// Compute the horizontally interpolated row.
|
||||
uint32_t col = 0;
|
||||
generate(result.begin(), result.end(), [&]{
|
||||
return HorizontallyInterpolatedPixel(col++, aWidth, weights,
|
||||
aShouldInterpolate, aColors);
|
||||
});
|
||||
} else {
|
||||
// This is an unimportant row; just make the entire thing transparent.
|
||||
generate(result.begin(), result.end(), []{
|
||||
return BGRAColor::Transparent();
|
||||
});
|
||||
}
|
||||
|
||||
EXPECT_EQ(result.size(), size_t(aWidth));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WriteState
|
||||
WriteUninterpolatedPixels(SurfaceFilter* aFilter,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
// Compute uninterpolated pixels for this row.
|
||||
vector<BGRAColor> pixels =
|
||||
Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
|
||||
ShouldInterpolate::eNo, aColors));
|
||||
|
||||
// Write them to the surface.
|
||||
auto pixelIterator = pixels.cbegin();
|
||||
result = aFilter->WritePixelsToRow<uint32_t>([&]{
|
||||
return AsVariant((*pixelIterator++).AsPixel());
|
||||
});
|
||||
|
||||
if (result != WriteState::NEED_MORE_DATA) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
CheckHorizontallyInterpolatedImage(Decoder* aDecoder,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
if (!IsImportantRow(row, aPass)) {
|
||||
continue; // Don't check rows which aren't important on this pass.
|
||||
}
|
||||
|
||||
// Compute the expected pixels, *with* interpolation to match what the
|
||||
// filter should have done.
|
||||
vector<BGRAColor> expectedPixels =
|
||||
Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
|
||||
ShouldInterpolate::eYes, aColors));
|
||||
|
||||
if (!RowHasPixels(surface, row, expectedPixels)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
CheckHorizontalInterpolation(const IntSize& aSize,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
const IntRect surfaceRect(IntPoint(0, 0), aSize);
|
||||
|
||||
WithADAM7InterpolatingFilter(aSize,
|
||||
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// We check horizontal interpolation behavior for each pass individually. In
|
||||
// addition to the normal 7 passes that ADAM7 includes, we also check an
|
||||
// eighth pass to verify that nothing breaks if extra data is written.
|
||||
for (uint8_t pass = 1; pass <= 8; ++pass) {
|
||||
// Write our color pattern to the surface. We don't perform any
|
||||
// interpolation when writing to the filter so that we can check that the
|
||||
// filter itself *does*.
|
||||
WriteState result =
|
||||
WriteUninterpolatedPixels(aFilter, aSize, pass, aColors);
|
||||
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);
|
||||
|
||||
// Check that the generated image matches the expected pattern, with
|
||||
// interpolation applied.
|
||||
EXPECT_TRUE(CheckHorizontallyInterpolatedImage(aDecoder, aSize,
|
||||
pass, aColors));
|
||||
|
||||
// Prepare for the next pass.
|
||||
aFilter->ResetToFirstRow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BGRAColor
|
||||
ADAM7RowColor(int32_t aRow,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
EXPECT_LT(0, aPass);
|
||||
EXPECT_GE(8, aPass);
|
||||
EXPECT_LT(0u, aColors.size());
|
||||
|
||||
// If this is an important row, select the color from the provided vector of
|
||||
// colors, which we cycle through infinitely. If not, just fill the row with
|
||||
// transparent pixels.
|
||||
return IsImportantRow(aRow, aPass) ? aColors[aRow % aColors.size()]
|
||||
: BGRAColor::Transparent();
|
||||
}
|
||||
|
||||
WriteState
|
||||
WriteRowColorPixels(SurfaceFilter* aFilter,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
const uint32_t color = ADAM7RowColor(row, aPass, aColors).AsPixel();
|
||||
|
||||
// Fill the surface with |color| pixels.
|
||||
result = aFilter->WritePixelsToRow<uint32_t>([&]{ return AsVariant(color); });
|
||||
|
||||
if (result != WriteState::NEED_MORE_DATA) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
CheckVerticallyInterpolatedImage(Decoder* aDecoder,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
vector<float>& weights = InterpolationWeights(ImportantRowStride(aPass));
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
// Vertically interpolation takes place between two important rows. The
|
||||
// separation between the important rows is determined by the stride of this
|
||||
// pass. When there is no "next" important row because we'd run off the
|
||||
// bottom of the image, we use the same row for both. This matches
|
||||
// ADAM7InterpolatingFilter's behavior of duplicating the last important row
|
||||
// since there isn't another important row to vertically interpolate it
|
||||
// with.
|
||||
const int32_t stride = ImportantRowStride(aPass);
|
||||
const int32_t prevImportantRow = row - row % stride;
|
||||
const int32_t maybeNextImportantRow = prevImportantRow + stride;
|
||||
const int32_t nextImportantRow = maybeNextImportantRow < aSize.height
|
||||
? maybeNextImportantRow
|
||||
: prevImportantRow;
|
||||
|
||||
// Retrieve the colors for the important rows we're going to interpolate.
|
||||
const BGRAColor prevImportantRowColor =
|
||||
ADAM7RowColor(prevImportantRow, aPass, aColors);
|
||||
const BGRAColor nextImportantRowColor =
|
||||
ADAM7RowColor(nextImportantRow, aPass, aColors);
|
||||
|
||||
// The weight we'll use for interpolation is also determined by the stride.
|
||||
// A row halfway between two important rows should have pixels that have a
|
||||
// 50% contribution from each of the important rows, for example.
|
||||
const float weight = weights[row % stride];
|
||||
const BGRAColor interpolatedColor =
|
||||
InterpolateColors(prevImportantRowColor, nextImportantRowColor, weight);
|
||||
|
||||
// Generate a row of expected pixels. Every pixel in the row is always the
|
||||
// same color since we're only testing vertical interpolation between
|
||||
// solid-colored rows.
|
||||
vector<BGRAColor> expectedPixels(aSize.width);
|
||||
generate(expectedPixels.begin(), expectedPixels.end(), [&]{
|
||||
return interpolatedColor;
|
||||
});
|
||||
|
||||
// Check that the pixels match.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
if (!RowHasPixels(surface, row, expectedPixels)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
CheckVerticalInterpolation(const IntSize& aSize,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
const IntRect surfaceRect(IntPoint(0, 0), aSize);
|
||||
|
||||
WithADAM7InterpolatingFilter(aSize,
|
||||
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
for (uint8_t pass = 1; pass <= 8; ++pass) {
|
||||
// Write a pattern of rows to the surface. Important rows will receive a
|
||||
// color selected from |aColors|; unimportant rows will be transparent.
|
||||
WriteState result = WriteRowColorPixels(aFilter, aSize, pass, aColors);
|
||||
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);
|
||||
|
||||
// Check that the generated image matches the expected pattern, with
|
||||
// interpolation applied.
|
||||
EXPECT_TRUE(CheckVerticallyInterpolatedImage(aDecoder, aSize,
|
||||
pass, aColors));
|
||||
|
||||
// Prepare for the next pass.
|
||||
aFilter->ResetToFirstRow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
CheckInterpolation(const IntSize& aSize, const vector<BGRAColor>& aColors)
|
||||
{
|
||||
CheckHorizontalInterpolation(aSize, aColors);
|
||||
CheckVerticalInterpolation(aSize, aColors);
|
||||
}
|
||||
|
||||
void
|
||||
CheckADAM7InterpolatingWritePixels(const IntSize& aSize)
|
||||
{
|
||||
// This test writes 8 passes of green pixels (the seven ADAM7 passes, plus one
|
||||
// extra to make sure nothing goes wrong if we write too much input) and verifies
|
||||
// that the output is a solid green surface each time. Because all the pixels
|
||||
// are the same color, interpolation doesn't matter; we test the correctness
|
||||
// of the interpolation algorithm itself separately.
|
||||
WithADAM7InterpolatingFilter(aSize,
|
||||
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
IntRect rect(IntPoint(0, 0), aSize);
|
||||
|
||||
for (int32_t pass = 1; pass <= 8; ++pass) {
|
||||
// We only actually write up to the last important row for each pass,
|
||||
// because that row unambiguously determines the remaining rows.
|
||||
const int32_t lastRow = aSize.height - 1;
|
||||
const int32_t lastImportantRow =
|
||||
lastRow - (lastRow % ImportantRowStride(pass));
|
||||
const IntRect inputWriteRect(0, 0, aSize.width, lastImportantRow + 1);
|
||||
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(rect),
|
||||
/* aInputRect = */ Some(rect),
|
||||
/* aInputWriteRect = */ Some(inputWriteRect));
|
||||
|
||||
aFilter->ResetToFirstRow();
|
||||
EXPECT_FALSE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels100_100)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(100, 100));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels99_99)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(99, 99));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels66_33)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(66, 33));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels33_66)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(33, 66));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels15_15)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(15, 15));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels9_9)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(9, 9));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels8_8)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(8, 8));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels7_7)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(7, 7));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels3_3)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(3, 3));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels1_1)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(1, 1));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, TrivialInterpolation48_48)
|
||||
{
|
||||
CheckInterpolation(IntSize(48, 48), { BGRAColor::Green() });
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput33_17)
|
||||
{
|
||||
// We check interpolation using irregular patterns to make sure that the
|
||||
// interpolation will look different for different passes.
|
||||
CheckInterpolation(IntSize(33, 17), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput32_16)
|
||||
{
|
||||
CheckInterpolation(IntSize(32, 16), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput31_15)
|
||||
{
|
||||
CheckInterpolation(IntSize(31, 15), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput17_33)
|
||||
{
|
||||
CheckInterpolation(IntSize(17, 33), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput16_32)
|
||||
{
|
||||
CheckInterpolation(IntSize(16, 32), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput15_31)
|
||||
{
|
||||
CheckInterpolation(IntSize(15, 31), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput9_9)
|
||||
{
|
||||
CheckInterpolation(IntSize(9, 9), {
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput8_8)
|
||||
{
|
||||
CheckInterpolation(IntSize(8, 8), {
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput7_7)
|
||||
{
|
||||
CheckInterpolation(IntSize(7, 7), {
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput3_3)
|
||||
{
|
||||
CheckInterpolation(IntSize(3, 3), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Red()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput1_1)
|
||||
{
|
||||
CheckInterpolation(IntSize(1, 1), { BGRAColor::Blue() });
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsFor0_0)
|
||||
{
|
||||
// A 0x0 input size is invalid, so configuration should fail.
|
||||
AssertConfiguringADAM7InterpolatingFilterFails(IntSize(0, 0));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsForMinus1_Minus1)
|
||||
{
|
||||
// A negative input size is invalid, so configuration should fail.
|
||||
AssertConfiguringADAM7InterpolatingFilterFails(IntSize(-1, -1));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, ConfiguringPalettedADAM7InterpolatingFilterFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// ADAM7InterpolatingFilter does not support paletted images, so configuration
|
||||
// should fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
ADAM7InterpolatingConfig { },
|
||||
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
IntRect(0, 0, 50, 50),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
235
image/test/gtest/TestCopyOnWrite.cpp
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "CopyOnWrite.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
struct ValueStats
|
||||
{
|
||||
int32_t mCopies = 0;
|
||||
int32_t mFrees = 0;
|
||||
int32_t mCalls = 0;
|
||||
int32_t mConstCalls = 0;
|
||||
int32_t mSerial = 0;
|
||||
};
|
||||
|
||||
struct Value
|
||||
{
|
||||
NS_INLINE_DECL_REFCOUNTING(Value)
|
||||
|
||||
explicit Value(ValueStats& aStats)
|
||||
: mStats(aStats)
|
||||
, mSerial(mStats.mSerial++)
|
||||
{ }
|
||||
|
||||
Value(const Value& aOther)
|
||||
: mStats(aOther.mStats)
|
||||
, mSerial(mStats.mSerial++)
|
||||
{
|
||||
mStats.mCopies++;
|
||||
}
|
||||
|
||||
void Go() { mStats.mCalls++; }
|
||||
void Go() const { mStats.mConstCalls++; }
|
||||
|
||||
int32_t Serial() const { return mSerial; }
|
||||
|
||||
protected:
|
||||
~Value() { mStats.mFrees++; }
|
||||
|
||||
private:
|
||||
ValueStats& mStats;
|
||||
int32_t mSerial;
|
||||
};
|
||||
|
||||
TEST(ImageCopyOnWrite, Read)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
}
|
||||
|
||||
TEST(ImageCopyOnWrite, RecursiveRead)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
// Make sure that Read() inside a Read() succeeds.
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}, []() {
|
||||
// This gets called if we can't read. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
});
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
}
|
||||
|
||||
TEST(ImageCopyOnWrite, Write)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
cow.Write([&](Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(!cow.CanRead());
|
||||
EXPECT_TRUE(!cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(0, stats.mConstCalls);
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(0, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
}
|
||||
|
||||
TEST(ImageCopyOnWrite, WriteRecursive)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
// Make sure Write() inside a Read() succeeds.
|
||||
cow.Write([&](Value* aValue) {
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(1, aValue->Serial());
|
||||
EXPECT_TRUE(!cow.CanRead());
|
||||
EXPECT_TRUE(!cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(0, stats.mConstCalls);
|
||||
|
||||
// Make sure Read() inside a Write() fails.
|
||||
cow.Read([](const Value* aValue) {
|
||||
// This gets called if we can read. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
}, []() {
|
||||
// This gets called if we can't read. We *should* get here.
|
||||
EXPECT_TRUE(true);
|
||||
});
|
||||
|
||||
// Make sure Write() inside a Write() fails.
|
||||
cow.Write([](Value* aValue) {
|
||||
// This gets called if we can write. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
}, []() {
|
||||
// This gets called if we can't write. We *should* get here.
|
||||
EXPECT_TRUE(true);
|
||||
});
|
||||
}, []() {
|
||||
// This gets called if we can't write. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
});
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
});
|
||||
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(2, stats.mFrees);
|
||||
}
|
||||
123
image/test/gtest/TestDecodeToSurface.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "imgITools.h"
|
||||
#include "ImageOps.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
class DecodeToSurfaceRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
DecodeToSurfaceRunnable(RefPtr<SourceSurface>& aSurface,
|
||||
nsIInputStream* aInputStream,
|
||||
const ImageTestCase& aTestCase)
|
||||
: mSurface(aSurface)
|
||||
, mInputStream(aInputStream)
|
||||
, mTestCase(aTestCase)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
Go();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Go()
|
||||
{
|
||||
mSurface =
|
||||
ImageOps::DecodeToSurface(mInputStream,
|
||||
nsDependentCString(mTestCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
ASSERT_TRUE(mSurface != nullptr);
|
||||
|
||||
EXPECT_EQ(SurfaceType::DATA, mSurface->GetType());
|
||||
EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
|
||||
mSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
|
||||
EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
|
||||
|
||||
EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(),
|
||||
mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<SourceSurface>& mSurface;
|
||||
nsCOMPtr<nsIInputStream> mInputStream;
|
||||
ImageTestCase mTestCase;
|
||||
};
|
||||
|
||||
static void
|
||||
RunDecodeToSurface(const ImageTestCase& aTestCase)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// We run the DecodeToSurface tests off-main-thread to ensure that
|
||||
// DecodeToSurface doesn't require any main-thread-only code.
|
||||
RefPtr<SourceSurface> surface;
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
new DecodeToSurfaceRunnable(surface, inputStream, aTestCase);
|
||||
thread->Dispatch(runnable, nsIThread::DISPATCH_SYNC);
|
||||
|
||||
thread->Shutdown();
|
||||
|
||||
// Explicitly release the SourceSurface on the main thread.
|
||||
surface = nullptr;
|
||||
}
|
||||
|
||||
class ImageDecodeToSurface : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDecodeToSurface, PNG) { RunDecodeToSurface(GreenPNGTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, GIF) { RunDecodeToSurface(GreenGIFTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, JPG) { RunDecodeToSurface(GreenJPGTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, BMP) { RunDecodeToSurface(GreenBMPTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, ICO) { RunDecodeToSurface(GreenICOTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, Icon) { RunDecodeToSurface(GreenIconTestCase()); }
|
||||
|
||||
TEST_F(ImageDecodeToSurface, AnimatedGIF)
|
||||
{
|
||||
RunDecodeToSurface(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecodeToSurface, AnimatedPNG)
|
||||
{
|
||||
RunDecodeToSurface(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecodeToSurface, Corrupt)
|
||||
{
|
||||
ImageTestCase testCase = CorruptTestCase();
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
RefPtr<SourceSurface> surface =
|
||||
ImageOps::DecodeToSurface(inputStream,
|
||||
nsDependentCString(testCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
EXPECT_TRUE(surface == nullptr);
|
||||
}
|
||||
669
image/test/gtest/TestDecoders.cpp
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "decoders/nsBMPDecoder.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "imgITools.h"
|
||||
#include "ImageFactory.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ProgressTracker.h"
|
||||
#include "SourceBuffer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
static already_AddRefed<SourceSurface>
|
||||
CheckDecoderState(const ImageTestCase& aTestCase, Decoder* aDecoder)
|
||||
{
|
||||
EXPECT_TRUE(aDecoder->GetDecodeDone());
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
|
||||
aDecoder->HasError());
|
||||
|
||||
// Verify that the decoder made the expected progress.
|
||||
Progress progress = aDecoder->TakeProgress();
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
|
||||
bool(progress & FLAG_HAS_ERROR));
|
||||
|
||||
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
|
||||
return nullptr; // That's all we can check for bad images.
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE));
|
||||
EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE));
|
||||
EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE));
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT),
|
||||
bool(progress & FLAG_HAS_TRANSPARENCY));
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
|
||||
bool(progress & FLAG_IS_ANIMATED));
|
||||
|
||||
// The decoder should get the correct size.
|
||||
IntSize size = aDecoder->Size();
|
||||
EXPECT_EQ(aTestCase.mSize.width, size.width);
|
||||
EXPECT_EQ(aTestCase.mSize.height, size.height);
|
||||
|
||||
// Get the current frame, which is always the first frame of the image
|
||||
// because CreateAnonymousDecoder() forces a first-frame-only decode.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
// Verify that the resulting surfaces matches our expectations.
|
||||
EXPECT_EQ(SurfaceType::DATA, surface->GetType());
|
||||
EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
|
||||
surface->GetFormat() == SurfaceFormat::B8G8R8A8);
|
||||
EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize());
|
||||
|
||||
return surface.forget();
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder)
|
||||
{
|
||||
RefPtr<SourceSurface> surface = CheckDecoderState(aTestCase, aDecoder);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the output.
|
||||
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
|
||||
aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void WithSingleChunkDecode(const ImageTestCase& aTestCase,
|
||||
const Maybe<IntSize>& aOutputSize,
|
||||
Func aResultChecker)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into a SourceBuffer.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize,
|
||||
DefaultSurfaceFlags());
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
// Run the full decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
// Call the lambda to verify the expected results.
|
||||
aResultChecker(decoder);
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
|
||||
{
|
||||
WithSingleChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) {
|
||||
CheckDecoderResults(aTestCase, aDecoder);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Create a SourceBuffer and a decoder.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
||||
DefaultSurfaceFlags());
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
for (uint64_t read = 0; read < length ; ++read) {
|
||||
uint64_t available = 0;
|
||||
rv = inputStream->Available(&available);
|
||||
ASSERT_TRUE(available > 0);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
task->Run();
|
||||
}
|
||||
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
task->Run();
|
||||
|
||||
CheckDecoderResults(aTestCase, decoder);
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDownscaleDuringDecode(const ImageTestCase& aTestCase)
|
||||
{
|
||||
// This function expects that |aTestCase| consists of 25 lines of green,
|
||||
// followed by 25 lines of red, followed by 25 lines of green, followed by 25
|
||||
// more lines of red. We'll downscale it from 100x100 to 20x20.
|
||||
IntSize outputSize(20, 20);
|
||||
|
||||
WithSingleChunkDecode(aTestCase, Some(outputSize), [&](Decoder* aDecoder) {
|
||||
RefPtr<SourceSurface> surface = CheckDecoderState(aTestCase, aDecoder);
|
||||
|
||||
// There are no downscale-during-decode tests that have TEST_CASE_HAS_ERROR
|
||||
// set, so we expect to always get a surface here.
|
||||
EXPECT_TRUE(surface != nullptr);
|
||||
|
||||
if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the downscaled image is correct. Note that we skip rows near
|
||||
// the transitions between colors, since the downscaler does not produce a
|
||||
// sharp boundary at these points. Even some of the rows we test need a
|
||||
// small amount of fuzz; this is just the nature of Lanczos downscaling.
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 47));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 27));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 47));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 27));
|
||||
});
|
||||
}
|
||||
|
||||
class ImageDecoders : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDecoders, PNGSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, PNGMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, PNGDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, GIFSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, GIFMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, GIFDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, JPGSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenJPGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, JPGMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenJPGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, JPGDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledJPGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, BMPSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenBMPTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, BMPMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenBMPTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, BMPDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledBMPTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICOSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenICOTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICOMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenICOTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICODownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledICOTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, IconSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenIconTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, IconMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenIconTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, IconDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledIconTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedPNGSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedPNGMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptBMPWithTruncatedHeader());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptBMPWithTruncatedHeader());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptICOWithBadBMPWidthTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptICOWithBadBMPWidthTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptICOWithBadBMPHeightTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptICOWithBadBMPHeightTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_FIRST)
|
||||
{
|
||||
ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase();
|
||||
|
||||
// Verify that we can decode this test case and retrieve the first frame using
|
||||
// imgIContainer::FRAME_FIRST. This ensures that we correctly trigger a
|
||||
// single-frame decode rather than an animated decode when
|
||||
// imgIContainer::FRAME_FIRST is requested.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Lock the image so its surfaces don't disappear during the test.
|
||||
image->LockImage();
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, specifying FRAME_FIRST
|
||||
// to ensure that we don't get an animated decode.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_FIRST,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded the static version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that we didn't decode the animated version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
|
||||
}
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, this time specifying
|
||||
// FRAME_CURRENT to ensure that we get an animated decode.
|
||||
RefPtr<SourceSurface> animatedSurface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that we decoded both frames of the animated version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that the static version is still around.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_CURRENT)
|
||||
{
|
||||
ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase();
|
||||
|
||||
// Verify that we can decode this test case and retrieve the entire sequence
|
||||
// of frames using imgIContainer::FRAME_CURRENT. This ensures that we
|
||||
// correctly trigger an animated decode rather than a single-frame decode when
|
||||
// imgIContainer::FRAME_CURRENT is requested.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Lock the image so its surfaces don't disappear during the test.
|
||||
image->LockImage();
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, specifying
|
||||
// FRAME_CURRENT to ensure we get an animated decode.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded both frames of the animated version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that we didn't decode the static version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
|
||||
}
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, this time specifying
|
||||
// FRAME_FIRST to ensure that we get a single-frame decode.
|
||||
RefPtr<SourceSurface> animatedSurface =
|
||||
image->GetFrame(imgIContainer::FRAME_FIRST,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that we decoded the static version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that both frames of the animated version are still around.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks)
|
||||
{
|
||||
ImageTestCase testCase = ExtraImageSubBlocksAnimatedGIFTestCase();
|
||||
|
||||
// Verify that we can decode this test case and get two frames, even though
|
||||
// there are extra image sub blocks between the first and second frame. The
|
||||
// extra data shouldn't confuse the decoder or cause the decode to fail.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded both frames of the image.
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, TruncatedSmallGIFSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(TruncatedSmallGIFTestCase());
|
||||
}
|
||||
672
image/test/gtest/TestDeinterlacingFilter.cpp
Normal file
|
|
@ -0,0 +1,672 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfaceFilters.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
template <typename Func> void
|
||||
WithDeinterlacingFilter(const IntSize& aSize,
|
||||
bool aProgressiveDisplay,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
DeinterlacingConfig<uint32_t> { aProgressiveDisplay },
|
||||
SurfaceConfig { decoder, 0, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
template <typename Func> void
|
||||
WithPalettedDeinterlacingFilter(const IntSize& aSize,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true },
|
||||
PalettedSurfaceConfig { decoder, 0, aSize,
|
||||
IntRect(0, 0, 100, 100),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
|
||||
SurfaceConfig { decoder, 0, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
class ImageDeinterlacingFilter : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixels100_100)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(100, 100), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixels99_99)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(99, 99), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 99, 99)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 99, 99)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixels8_8)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(8, 8), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 8, 8)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 8, 8)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixels7_7)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 7, 7)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 7, 7)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixels3_3)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 3, 3)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 3, 3)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixels1_1)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 1, 1)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 1, 1)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, PalettedWritePixels)
|
||||
{
|
||||
WithPalettedDeinterlacingFilter(IntSize(100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckPalettedWritePixels(aDecoder, aFilter);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveOutput51_52)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(51, 52), /* aProgressiveDisplay = */ false,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be green for even rows and red for odd
|
||||
// rows but we need to write the rows in the order that the deinterlacer
|
||||
// expects them.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() {
|
||||
uint32_t row = count / 51; // Integer division.
|
||||
++count;
|
||||
|
||||
// Note that we use a switch statement here, even though it's quite
|
||||
// verbose, because it's useful to have the mappings between input and
|
||||
// output rows available when debugging these tests.
|
||||
|
||||
switch (row) {
|
||||
// First pass. Output rows are positioned at 8n + 0.
|
||||
case 0: // Output row 0.
|
||||
case 1: // Output row 8.
|
||||
case 2: // Output row 16.
|
||||
case 3: // Output row 24.
|
||||
case 4: // Output row 32.
|
||||
case 5: // Output row 40.
|
||||
case 6: // Output row 48.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
case 7: // Output row 4.
|
||||
case 8: // Output row 12.
|
||||
case 9: // Output row 20.
|
||||
case 10: // Output row 28.
|
||||
case 11: // Output row 36.
|
||||
case 12: // Output row 44.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
case 13: // Output row 2.
|
||||
case 14: // Output row 6.
|
||||
case 15: // Output row 10.
|
||||
case 16: // Output row 14.
|
||||
case 17: // Output row 18.
|
||||
case 18: // Output row 22.
|
||||
case 19: // Output row 26.
|
||||
case 20: // Output row 30.
|
||||
case 21: // Output row 34.
|
||||
case 22: // Output row 38.
|
||||
case 23: // Output row 42.
|
||||
case 24: // Output row 46.
|
||||
case 25: // Output row 50.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
case 26: // Output row 1.
|
||||
case 27: // Output row 3.
|
||||
case 28: // Output row 5.
|
||||
case 29: // Output row 7.
|
||||
case 30: // Output row 9.
|
||||
case 31: // Output row 11.
|
||||
case 32: // Output row 13.
|
||||
case 33: // Output row 15.
|
||||
case 34: // Output row 17.
|
||||
case 35: // Output row 19.
|
||||
case 36: // Output row 21.
|
||||
case 37: // Output row 23.
|
||||
case 38: // Output row 25.
|
||||
case 39: // Output row 27.
|
||||
case 40: // Output row 29.
|
||||
case 41: // Output row 31.
|
||||
case 42: // Output row 33.
|
||||
case 43: // Output row 35.
|
||||
case 44: // Output row 37.
|
||||
case 45: // Output row 39.
|
||||
case 46: // Output row 41.
|
||||
case 47: // Output row 43.
|
||||
case 48: // Output row 45.
|
||||
case 49: // Output row 47.
|
||||
case 50: // Output row 49.
|
||||
case 51: // Output row 51.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected row");
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(51u * 52u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 51, 52),
|
||||
IntRect(0, 0, 51, 52));
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// even rows to be green and odd rows to be red.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 52; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
|
||||
row % 2 == 0 ? BGRAColor::Green()
|
||||
: BGRAColor::Red()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput20_20)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(20, 20), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be green for even rows and red for odd
|
||||
// rows but we need to write the rows in the order that the deinterlacer
|
||||
// expects them.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() {
|
||||
uint32_t row = count / 20; // Integer division.
|
||||
++count;
|
||||
|
||||
// Note that we use a switch statement here, even though it's quite
|
||||
// verbose, because it's useful to have the mappings between input and
|
||||
// output rows available when debugging these tests.
|
||||
|
||||
switch (row) {
|
||||
// First pass. Output rows are positioned at 8n + 0.
|
||||
case 0: // Output row 0.
|
||||
case 1: // Output row 8.
|
||||
case 2: // Output row 16.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
case 3: // Output row 4.
|
||||
case 4: // Output row 12.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
case 5: // Output row 2.
|
||||
case 6: // Output row 6.
|
||||
case 7: // Output row 10.
|
||||
case 8: // Output row 14.
|
||||
case 9: // Output row 18.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
case 10: // Output row 1.
|
||||
case 11: // Output row 3.
|
||||
case 12: // Output row 5.
|
||||
case 13: // Output row 7.
|
||||
case 14: // Output row 9.
|
||||
case 15: // Output row 11.
|
||||
case 16: // Output row 13.
|
||||
case 17: // Output row 15.
|
||||
case 18: // Output row 17.
|
||||
case 19: // Output row 19.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected row");
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(20u * 20u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 20, 20),
|
||||
IntRect(0, 0, 20, 20));
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// even rows to be green and odd rows to be red.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 20; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
|
||||
row % 2 == 0 ? BGRAColor::Green()
|
||||
: BGRAColor::Red()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput7_7)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be a repeating pattern of two green
|
||||
// rows followed by two red rows but we need to write the rows in the order
|
||||
// that the deinterlacer expects them.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() {
|
||||
uint32_t row = count / 7; // Integer division.
|
||||
++count;
|
||||
|
||||
switch (row) {
|
||||
// First pass. Output rows are positioned at 8n + 0.
|
||||
case 0: // Output row 0.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
case 1: // Output row 4.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
case 2: // Output row 2.
|
||||
case 3: // Output row 6.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
case 4: // Output row 1.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
case 5: // Output row 3.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
case 6: // Output row 5.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected row");
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(7u * 7u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 7, 7),
|
||||
IntRect(0, 0, 7, 7));
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// two green rows, followed by two red rows, then two green rows, etc.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 7; ++row) {
|
||||
BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5
|
||||
? BGRAColor::Green()
|
||||
: BGRAColor::Red();
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput3_3)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be green, red, green in that order, but
|
||||
// we need to write the rows in the order that the deinterlacer expects
|
||||
// them.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() {
|
||||
uint32_t row = count / 3; // Integer division.
|
||||
++count;
|
||||
|
||||
switch (row) {
|
||||
// First pass. Output rows are positioned at 8n + 0.
|
||||
case 0: // Output row 0.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
// No rows for this pass.
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
case 1: // Output row 2.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
case 2: // Output row 1.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected row");
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(3u * 3u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 3, 3),
|
||||
IntRect(0, 0, 3, 3));
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// green, red, green in that order.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 3; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
|
||||
row == 0 || row == 2 ? BGRAColor::Green()
|
||||
: BGRAColor::Red()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput1_1)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be a single red row.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(1u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 1, 1),
|
||||
IntRect(0, 0, 1, 1));
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// a single red row.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 1, BGRAColor::Red()));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
WriteRowAndCheckInterlacerOutput(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
BGRAColor aColor,
|
||||
WriteState aNextState,
|
||||
IntRect aInvalidRect,
|
||||
uint32_t aFirstHaeberliRow,
|
||||
uint32_t aLastHaeberliRow)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
|
||||
if (count < 7) {
|
||||
++count;
|
||||
return AsVariant(aColor.AsPixel());
|
||||
}
|
||||
return AsVariant(WriteState::NEED_MORE_DATA);
|
||||
});
|
||||
|
||||
EXPECT_EQ(aNextState, result);
|
||||
EXPECT_EQ(7u, count);
|
||||
|
||||
// Assert that we got the expected invalidation region.
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(aInvalidRect, invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(aInvalidRect, invalidRect->mOutputSpaceRect);
|
||||
|
||||
// Check that the portion of the image generated so far is correct. The rows
|
||||
// from aFirstHaeberliRow to aLastHaeberliRow should be filled with aColor.
|
||||
// Note that this is not the same as the set of rows in aInvalidRect, because
|
||||
// after writing a row the deinterlacer seeks to the next row to write, which
|
||||
// may involve copying previously-written rows in the buffer to the output
|
||||
// even though they don't change in this pass.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = aFirstHaeberliRow; row <= aLastHaeberliRow; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, aColor));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsIntermediateOutput7_7)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be a repeating pattern of two green
|
||||
// rows followed by two red rows but we need to write the rows in the order
|
||||
// that the deinterlacer expects them.
|
||||
|
||||
// First pass. Output rows are positioned at 8n + 0.
|
||||
|
||||
// Output row 0. The invalid rect is the entire image because this is the
|
||||
// end of the first pass.
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 0, 7, 7), 0, 4);
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
|
||||
// Output row 4. The invalid rect is the entire image because this is the
|
||||
// end of the second pass.
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 0, 7, 7), 1, 4);
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
|
||||
// Output row 2. The invalid rect contains the Haeberli rows for this output
|
||||
// row (rows 2 and 3) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (rows 4 and 5).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 2, 7, 4), 2, 3);
|
||||
|
||||
// Output row 6. The invalid rect is the entire image because this is the
|
||||
// end of the third pass.
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 0, 7, 7), 6, 6);
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
|
||||
// Output row 1. The invalid rect contains the Haeberli rows for this output
|
||||
// row (just row 1) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (row 2).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 1, 7, 2), 1, 1);
|
||||
|
||||
// Output row 3. The invalid rect contains the Haeberli rows for this output
|
||||
// row (just row 3) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (row 4).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 3, 7, 2), 3, 3);
|
||||
|
||||
// Output row 5. The invalid rect contains the Haeberli rows for this output
|
||||
// row (just row 5) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (row 6).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::FINISHED,
|
||||
IntRect(0, 5, 7, 2), 5, 5);
|
||||
|
||||
// Assert that we're in the expected final state.
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// two green rows, followed by two red rows, then two green rows, etc.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 7; ++row) {
|
||||
BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5
|
||||
? BGRAColor::Green()
|
||||
: BGRAColor::Red();
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveIntermediateOutput7_7)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ false,
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. The output should be a repeating pattern of two green
|
||||
// rows followed by two red rows but we need to write the rows in the order
|
||||
// that the deinterlacer expects them.
|
||||
|
||||
// First pass. Output rows are positioned at 8n + 0.
|
||||
|
||||
// Output row 0. The invalid rect is the entire image because this is the
|
||||
// end of the first pass.
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 0, 7, 7), 0, 0);
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
|
||||
// Output row 4. The invalid rect is the entire image because this is the
|
||||
// end of the second pass.
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 0, 7, 7), 4, 4);
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
|
||||
// Output row 2. The invalid rect contains the Haeberli rows for this output
|
||||
// row (rows 2 and 3) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (rows 4 and 5).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 2, 7, 4), 2, 2);
|
||||
|
||||
// Output row 6. The invalid rect is the entire image because this is the
|
||||
// end of the third pass.
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 0, 7, 7), 6, 6);
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
|
||||
// Output row 1. The invalid rect contains the Haeberli rows for this output
|
||||
// row (just row 1) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (row 2).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 1, 7, 2), 1, 1);
|
||||
|
||||
// Output row 3. The invalid rect contains the Haeberli rows for this output
|
||||
// row (just row 3) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (row 4).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(),
|
||||
WriteState::NEED_MORE_DATA,
|
||||
IntRect(0, 3, 7, 2), 3, 3);
|
||||
|
||||
// Output row 5. The invalid rect contains the Haeberli rows for this output
|
||||
// row (just row 5) as well as the rows that we copy from previous passes
|
||||
// when seeking to the next output row (row 6).
|
||||
WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(),
|
||||
WriteState::FINISHED,
|
||||
IntRect(0, 5, 7, 2), 5, 5);
|
||||
|
||||
// Assert that we're in the expected final state.
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
// Check that the generated image is correct. As mentioned above, we expect
|
||||
// two green rows, followed by two red rows, then two green rows, etc.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 7; ++row) {
|
||||
BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5
|
||||
? BGRAColor::Green()
|
||||
: BGRAColor::Red();
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsFor0_0)
|
||||
{
|
||||
// A 0x0 input size is invalid, so configuration should fail.
|
||||
AssertConfiguringDeinterlacingFilterFails(IntSize(0, 0));
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsForMinus1_Minus1)
|
||||
{
|
||||
// A negative input size is invalid, so configuration should fail.
|
||||
AssertConfiguringDeinterlacingFilterFails(IntSize(-1, -1));
|
||||
}
|
||||
231
image/test/gtest/TestDownscalingFilter.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfaceFilters.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
template <typename Func> void
|
||||
WithDownscalingFilter(const IntSize& aInputSize,
|
||||
const IntSize& aOutputSize,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
DownscalingConfig { aInputSize,
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, aOutputSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringDownscalingFilterFails(const IntSize& aInputSize,
|
||||
const IntSize& aOutputSize)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DownscalingConfig { aInputSize,
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, aOutputSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to99_99)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(99, 99),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 99, 99)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to33_33)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(33, 33),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 33, 33)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to1_1)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(1, 1),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 1, 1)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to33_99)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(33, 99),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 33, 99)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to99_33)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(99, 33),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 99, 33)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to99_1)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(99, 1),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 99, 1)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixels100_100to1_99)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(1, 99),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 1, 99)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to101_101)
|
||||
{
|
||||
// Upscaling is disallowed.
|
||||
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(101, 101));
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to100_100)
|
||||
{
|
||||
// "Scaling" to the same size is disallowed.
|
||||
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(100, 100));
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, DownscalingFailsFor0_0toMinus1_Minus1)
|
||||
{
|
||||
// A 0x0 input size is disallowed.
|
||||
AssertConfiguringDownscalingFilterFails(IntSize(0, 0), IntSize(-1, -1));
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, DownscalingFailsForMinus1_Minus1toMinus2_Minus2)
|
||||
{
|
||||
// A negative input size is disallowed.
|
||||
AssertConfiguringDownscalingFilterFails(IntSize(-1, -1), IntSize(-2, -2));
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to0_0)
|
||||
{
|
||||
// A 0x0 output size is disallowed.
|
||||
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(0, 0));
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, DownscalingFailsFor100_100toMinus1_Minus1)
|
||||
{
|
||||
// A negative output size is disallowed.
|
||||
AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(-1, -1));
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixelsOutput100_100to20_20)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(20, 20),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. It consists of 25 lines of green, followed by 25 lines of
|
||||
// red, followed by 25 lines of green, followed by 25 more lines of red.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
|
||||
uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100)
|
||||
? BGRAColor::Green().AsPixel()
|
||||
: BGRAColor::Red().AsPixel();
|
||||
++count;
|
||||
return AsVariant(color);
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 100, 100),
|
||||
IntRect(0, 0, 20, 20));
|
||||
|
||||
// Check that the generated image is correct. Note that we skip rows near
|
||||
// the transitions between colors, since the downscaler does not produce a
|
||||
// sharp boundary at these points. Even some of the rows we test need a
|
||||
// small amount of fuzz; this is just the nature of Lanczos downscaling.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, WritePixelsOutput100_100to10_20)
|
||||
{
|
||||
WithDownscalingFilter(IntSize(100, 100), IntSize(10, 20),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Fill the image. It consists of 25 lines of green, followed by 25 lines of
|
||||
// red, followed by 25 lines of green, followed by 25 more lines of red.
|
||||
uint32_t count = 0;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
|
||||
uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100)
|
||||
? BGRAColor::Green().AsPixel()
|
||||
: BGRAColor::Red().AsPixel();
|
||||
++count;
|
||||
return AsVariant(color);
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 100, 100),
|
||||
IntRect(0, 0, 10, 20));
|
||||
|
||||
// Check that the generated image is correct. Note that we skip rows near
|
||||
// the transitions between colors, since the downscaler does not produce a
|
||||
// sharp boundary at these points. Even some of the rows we test need a
|
||||
// small amount of fuzz; this is just the nature of Lanczos downscaling.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageDownscalingFilter, ConfiguringPalettedDownscaleFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// DownscalingFilter does not support paletted images, so configuration should
|
||||
// fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
PalettedSurfaceConfig { decoder, 0, IntSize(20, 20),
|
||||
IntRect(0, 0, 20, 20),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
57
image/test/gtest/TestDownscalingFilterNoSkia.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
// We want to ensure that we're testing the non-Skia fallback version of
|
||||
// DownscalingFilter, but there are two issues:
|
||||
// (1) We don't know whether Skia is currently enabled.
|
||||
// (2) If we force disable it, the disabled version will get linked into the
|
||||
// binary and will cause the tests in TestDownscalingFilter to fail.
|
||||
// To avoid these problems, we ensure that MOZ_ENABLE_SKIA is defined when
|
||||
// including DownscalingFilter.h, and we use the preprocessor to redefine the
|
||||
// DownscalingFilter class to DownscalingFilterNoSkia.
|
||||
|
||||
#define DownscalingFilter DownscalingFilterNoSkia
|
||||
|
||||
#ifdef MOZ_ENABLE_SKIA
|
||||
|
||||
#undef MOZ_ENABLE_SKIA
|
||||
#include "Common.h"
|
||||
#include "DownscalingFilter.h"
|
||||
#define MOZ_ENABLE_SKIA
|
||||
|
||||
#else
|
||||
|
||||
#include "Common.h"
|
||||
#include "DownscalingFilter.h"
|
||||
|
||||
#endif
|
||||
|
||||
#undef DownscalingFilter
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
TEST(ImageDownscalingFilter, NoSkia)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
// Configuring a DownscalingFilter should fail without Skia.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, IntSize(50, 50),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
255
image/test/gtest/TestMetadata.cpp
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "decoders/nsBMPDecoder.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "imgITools.h"
|
||||
#include "ImageFactory.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ProgressTracker.h"
|
||||
#include "SourceBuffer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
enum class BMPWithinICO
|
||||
{
|
||||
NO,
|
||||
YES
|
||||
};
|
||||
|
||||
static void
|
||||
CheckMetadata(const ImageTestCase& aTestCase,
|
||||
BMPWithinICO aBMPWithinICO = BMPWithinICO::NO)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into a SourceBuffer.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a metadata decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
if (aBMPWithinICO == BMPWithinICO::YES) {
|
||||
static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
|
||||
}
|
||||
|
||||
// Run the metadata decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
// Ensure that the metadata decoder didn't make progress it shouldn't have
|
||||
// (which would indicate that it decoded past the header of the image).
|
||||
Progress metadataProgress = decoder->TakeProgress();
|
||||
EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
|
||||
FLAG_HAS_TRANSPARENCY |
|
||||
FLAG_IS_ANIMATED)));
|
||||
|
||||
// If the test case is corrupt, assert what we can and return early.
|
||||
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
|
||||
EXPECT_TRUE(decoder->GetDecodeDone());
|
||||
EXPECT_TRUE(decoder->HasError());
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
|
||||
|
||||
// Check that we got the expected metadata.
|
||||
EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE);
|
||||
|
||||
IntSize metadataSize = decoder->Size();
|
||||
EXPECT_EQ(aTestCase.mSize.width, metadataSize.width);
|
||||
EXPECT_EQ(aTestCase.mSize.height, metadataSize.height);
|
||||
|
||||
bool expectTransparency = aBMPWithinICO == BMPWithinICO::YES
|
||||
? true
|
||||
: bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT);
|
||||
EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
|
||||
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
|
||||
bool(metadataProgress & FLAG_IS_ANIMATED));
|
||||
|
||||
// Create a full decoder, so we can compare the result.
|
||||
decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
||||
DefaultSurfaceFlags());
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
if (aBMPWithinICO == BMPWithinICO::YES) {
|
||||
static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
|
||||
}
|
||||
|
||||
// Run the full decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
|
||||
Progress fullProgress = decoder->TakeProgress();
|
||||
|
||||
// If the metadata decoder set a progress bit, the full decoder should also
|
||||
// have set the same bit.
|
||||
EXPECT_EQ(fullProgress, metadataProgress | fullProgress);
|
||||
|
||||
// The full decoder and the metadata decoder should agree on the image's size.
|
||||
IntSize fullSize = decoder->Size();
|
||||
EXPECT_EQ(metadataSize.width, fullSize.width);
|
||||
EXPECT_EQ(metadataSize.height, fullSize.height);
|
||||
|
||||
// We should not discover transparency during the full decode that we didn't
|
||||
// discover during the metadata decode, unless the image is animated.
|
||||
EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) ||
|
||||
(metadataProgress & FLAG_HAS_TRANSPARENCY) ||
|
||||
(fullProgress & FLAG_IS_ANIMATED));
|
||||
}
|
||||
|
||||
class ImageDecoderMetadata : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDecoderMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, ICO) { CheckMetadata(GreenICOTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, Icon) { CheckMetadata(GreenIconTestCase()); }
|
||||
|
||||
TEST_F(ImageDecoderMetadata, AnimatedGIF)
|
||||
{
|
||||
CheckMetadata(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, AnimatedPNG)
|
||||
{
|
||||
CheckMetadata(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, FirstFramePaddingGIF)
|
||||
{
|
||||
CheckMetadata(FirstFramePaddingGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPNotWithinICO)
|
||||
{
|
||||
CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_DEFAULT_FLAGS),
|
||||
BMPWithinICO::NO);
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPWithinICO)
|
||||
{
|
||||
CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_IS_TRANSPARENT),
|
||||
BMPWithinICO::YES);
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); }
|
||||
|
||||
TEST_F(ImageDecoderMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); }
|
||||
|
||||
TEST_F(ImageDecoderMetadata, NoFrameDelayGIF)
|
||||
{
|
||||
CheckMetadata(NoFrameDelayGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, NoFrameDelayGIFFullDecode)
|
||||
{
|
||||
ImageTestCase testCase = NoFrameDelayGIFTestCase();
|
||||
|
||||
// The previous test (NoFrameDelayGIF) verifies that we *don't* detect that
|
||||
// this test case is animated, because it has a zero frame delay for the first
|
||||
// frame. This test verifies that when we do a full decode, we detect the
|
||||
// animation at that point and successfully decode all the frames.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded both frames of the image.
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
327
image/test/gtest/TestRemoveFrameRectFilter.cpp
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfaceFilters.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
template <typename Func> void
|
||||
WithRemoveFrameRectFilter(const IntSize& aSize,
|
||||
const IntRect& aFrameRect,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
RemoveFrameRectConfig { aFrameRect },
|
||||
SurfaceConfig { decoder, 0, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringRemoveFrameRectFilterFails(const IntSize& aSize,
|
||||
const IntRect& aFrameRect)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
RemoveFrameRectConfig { aFrameRect },
|
||||
SurfaceConfig { decoder, 0, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(0, 0, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_0_0)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(0, 0, 0, 0),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_50_0_0)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(-50, 50, 0, 0),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_Minus50_0_0)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(50, -50, 0, 0),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_150_50_0_0)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(150, 50, 0, 0),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_150_0_0)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(50, 150, 0, 0),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_200_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(200, 200, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows
|
||||
// unfortunately can't be ignored.)
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_25_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(-200, 25, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows
|
||||
// unfortunately can't be ignored.)
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus200_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(25, -200, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows
|
||||
// unfortunately can't be ignored.)
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_25_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(200, 25, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows
|
||||
// unfortunately can't be ignored.)
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_200_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(25, 200, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Note that aInputRect is zero-size because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows
|
||||
// unfortunately can't be ignored.)
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_Minus200_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(-200, -200, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_Minus50_100_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(-50, -50, 100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 50, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_25_100_50)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(-50, 25, 100, 50),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 25, 50, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus50_50_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(25, -50, 50, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 50, 100)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(25, 0, 50, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_25_100_50)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(50, 25, 100, 50),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(50, 25, 50, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_50_50_100)
|
||||
{
|
||||
WithRemoveFrameRectFilter(IntSize(100, 100),
|
||||
IntRect(25, 50, 50, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// Note that aInputRect is 50x50 because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows
|
||||
// unfortunately can't be ignored.)
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 50, 50)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(25, 50, 50, 100)));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor0_0_to_0_0_100_100)
|
||||
{
|
||||
// A zero-size image is disallowed.
|
||||
AssertConfiguringRemoveFrameRectFilterFails(IntSize(0, 0),
|
||||
IntRect(0, 0, 100, 100));
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsForMinus1_Minus1_to_0_0_100_100)
|
||||
{
|
||||
// A negative-size image is disallowed.
|
||||
AssertConfiguringRemoveFrameRectFilterFails(IntSize(-1, -1),
|
||||
IntRect(0, 0, 100, 100));
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_0_0)
|
||||
{
|
||||
// A zero size frame rect is disallowed.
|
||||
AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100),
|
||||
IntRect(0, 0, -1, -1));
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_Minus1_Minus1)
|
||||
{
|
||||
// A negative size frame rect is disallowed.
|
||||
AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100),
|
||||
IntRect(0, 0, -1, -1));
|
||||
}
|
||||
|
||||
TEST(ImageRemoveFrameRectFilter, ConfiguringPalettedRemoveFrameRectFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// RemoveFrameRectFilter does not support paletted images, so configuration
|
||||
// should fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
RemoveFrameRectConfig { IntRect(0, 0, 50, 50) },
|
||||
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
IntRect(0, 0, 50, 50),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
810
image/test/gtest/TestSourceBuffer.cpp
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
using std::min;
|
||||
|
||||
void
|
||||
ExpectChunkAndByteCount(const SourceBufferIterator& aIterator,
|
||||
uint32_t aChunks,
|
||||
size_t aBytes)
|
||||
{
|
||||
EXPECT_EQ(aChunks, aIterator.ChunkCount());
|
||||
EXPECT_EQ(aBytes, aIterator.ByteCount());
|
||||
}
|
||||
|
||||
void
|
||||
ExpectRemainingBytes(const SourceBufferIterator& aIterator, size_t aBytes)
|
||||
{
|
||||
EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes));
|
||||
EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes + 1));
|
||||
|
||||
if (aBytes > 0) {
|
||||
EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(0));
|
||||
EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(aBytes - 1));
|
||||
}
|
||||
}
|
||||
|
||||
char
|
||||
GenerateByte(size_t aIndex)
|
||||
{
|
||||
uint8_t byte = aIndex % 256;
|
||||
return *reinterpret_cast<char*>(&byte);
|
||||
}
|
||||
|
||||
void
|
||||
GenerateData(char* aOutput, size_t aOffset, size_t aLength)
|
||||
{
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
aOutput[i] = GenerateByte(aOffset + i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GenerateData(char* aOutput, size_t aLength)
|
||||
{
|
||||
GenerateData(aOutput, 0, aLength);
|
||||
}
|
||||
|
||||
void
|
||||
CheckData(const char* aData, size_t aOffset, size_t aLength)
|
||||
{
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
ASSERT_EQ(GenerateByte(aOffset + i), aData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
enum class AdvanceMode
|
||||
{
|
||||
eAdvanceAsMuchAsPossible,
|
||||
eAdvanceByLengthExactly
|
||||
};
|
||||
|
||||
class ImageSourceBuffer : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
ImageSourceBuffer()
|
||||
: mSourceBuffer(new SourceBuffer)
|
||||
, mExpectNoResume(new ExpectNoResume)
|
||||
, mCountResumes(new CountResumes)
|
||||
{
|
||||
GenerateData(mData, sizeof(mData));
|
||||
EXPECT_FALSE(mSourceBuffer->IsComplete());
|
||||
}
|
||||
|
||||
protected:
|
||||
void CheckedAppendToBuffer(const char* aData, size_t aLength)
|
||||
{
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->Append(aData, aLength)));
|
||||
}
|
||||
|
||||
void CheckedAppendToBufferLastByteForLength(size_t aLength)
|
||||
{
|
||||
const char lastByte = GenerateByte(aLength);
|
||||
CheckedAppendToBuffer(&lastByte, 1);
|
||||
}
|
||||
|
||||
void CheckedAppendToBufferInChunks(size_t aChunkLength, size_t aTotalLength)
|
||||
{
|
||||
char* data = new char[aChunkLength];
|
||||
|
||||
size_t bytesWritten = 0;
|
||||
while (bytesWritten < aTotalLength) {
|
||||
GenerateData(data, bytesWritten, aChunkLength);
|
||||
size_t toWrite = min(aChunkLength, aTotalLength - bytesWritten);
|
||||
CheckedAppendToBuffer(data, toWrite);
|
||||
bytesWritten += toWrite;
|
||||
}
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
void CheckedCompleteBuffer(nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
mSourceBuffer->Complete(aCompletionStatus);
|
||||
EXPECT_TRUE(mSourceBuffer->IsComplete());
|
||||
}
|
||||
|
||||
void CheckedCompleteBuffer(SourceBufferIterator& aIterator,
|
||||
size_t aLength,
|
||||
nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
CheckedCompleteBuffer(aCompletionStatus);
|
||||
ExpectRemainingBytes(aIterator, aLength);
|
||||
}
|
||||
|
||||
void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator,
|
||||
size_t aLength,
|
||||
uint32_t aChunks,
|
||||
size_t aTotalLength,
|
||||
AdvanceMode aAdvanceMode
|
||||
= AdvanceMode::eAdvanceAsMuchAsPossible)
|
||||
{
|
||||
const size_t advanceBy = aAdvanceMode == AdvanceMode::eAdvanceAsMuchAsPossible
|
||||
? SIZE_MAX
|
||||
: aLength;
|
||||
|
||||
auto state = aIterator.AdvanceOrScheduleResume(advanceBy, mExpectNoResume);
|
||||
ASSERT_EQ(SourceBufferIterator::READY, state);
|
||||
EXPECT_TRUE(aIterator.Data());
|
||||
EXPECT_EQ(aLength, aIterator.Length());
|
||||
|
||||
ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength);
|
||||
}
|
||||
|
||||
void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator,
|
||||
size_t aLength)
|
||||
{
|
||||
CheckedAdvanceIteratorStateOnly(aIterator, aLength, 1, aLength);
|
||||
}
|
||||
|
||||
void CheckedAdvanceIterator(SourceBufferIterator& aIterator,
|
||||
size_t aLength,
|
||||
uint32_t aChunks,
|
||||
size_t aTotalLength,
|
||||
AdvanceMode aAdvanceMode
|
||||
= AdvanceMode::eAdvanceAsMuchAsPossible)
|
||||
{
|
||||
// Check that the iterator is in the expected state.
|
||||
CheckedAdvanceIteratorStateOnly(aIterator, aLength, aChunks,
|
||||
aTotalLength, aAdvanceMode);
|
||||
|
||||
// Check that we read the expected data. To do this, we need to compute our
|
||||
// offset in the SourceBuffer, but fortunately that's pretty easy: it's the
|
||||
// total number of bytes the iterator has advanced through, minus the length
|
||||
// of the current chunk.
|
||||
const size_t offset = aIterator.ByteCount() - aIterator.Length();
|
||||
CheckData(aIterator.Data(), offset, aIterator.Length());
|
||||
}
|
||||
|
||||
void CheckedAdvanceIterator(SourceBufferIterator& aIterator, size_t aLength)
|
||||
{
|
||||
CheckedAdvanceIterator(aIterator, aLength, 1, aLength);
|
||||
}
|
||||
|
||||
void CheckIteratorMustWait(SourceBufferIterator& aIterator,
|
||||
IResumable* aOnResume)
|
||||
{
|
||||
auto state = aIterator.AdvanceOrScheduleResume(1, aOnResume);
|
||||
EXPECT_EQ(SourceBufferIterator::WAITING, state);
|
||||
}
|
||||
|
||||
void CheckIteratorIsComplete(SourceBufferIterator& aIterator,
|
||||
uint32_t aChunks,
|
||||
size_t aTotalLength,
|
||||
nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
ASSERT_TRUE(mSourceBuffer->IsComplete());
|
||||
auto state = aIterator.AdvanceOrScheduleResume(1, mExpectNoResume);
|
||||
ASSERT_EQ(SourceBufferIterator::COMPLETE, state);
|
||||
EXPECT_EQ(aCompletionStatus, aIterator.CompletionStatus());
|
||||
ExpectRemainingBytes(aIterator, 0);
|
||||
ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength);
|
||||
}
|
||||
|
||||
void CheckIteratorIsComplete(SourceBufferIterator& aIterator,
|
||||
size_t aTotalLength)
|
||||
{
|
||||
CheckIteratorIsComplete(aIterator, 1, aTotalLength);
|
||||
}
|
||||
|
||||
AutoInitializeImageLib mInit;
|
||||
char mData[9];
|
||||
RefPtr<SourceBuffer> mSourceBuffer;
|
||||
RefPtr<ExpectNoResume> mExpectNoResume;
|
||||
RefPtr<CountResumes> mCountResumes;
|
||||
};
|
||||
|
||||
TEST_F(ImageSourceBuffer, InitialState)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// RemainingBytesIsNoMoreThan() should always return false in the initial
|
||||
// state, since we can't know the answer until Complete() has been called.
|
||||
EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(0));
|
||||
EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(SIZE_MAX));
|
||||
|
||||
// We haven't advanced our iterator at all, so its counters should be zero.
|
||||
ExpectChunkAndByteCount(iterator, 0, 0);
|
||||
|
||||
// Attempt to advance; we should fail, and end up in the WAITING state. We
|
||||
// expect no resumes because we don't actually append anything to the
|
||||
// SourceBuffer in this test.
|
||||
CheckIteratorMustWait(iterator, mExpectNoResume);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ZeroLengthBufferAlwaysFails)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Complete the buffer without writing to it, providing a successful
|
||||
// completion status.
|
||||
CheckedCompleteBuffer(iterator, 0);
|
||||
|
||||
// Completing a buffer without writing to it results in an automatic failure;
|
||||
// make sure that the actual completion status we get from the iterator
|
||||
// reflects this.
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompleteSuccess)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write a single byte to the buffer and complete the buffer. (We have to
|
||||
// write at least one byte because completing a zero length buffer always
|
||||
// fails; see the ZeroLengthBufferAlwaysFails test.)
|
||||
CheckedAppendToBuffer(mData, 1);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// We should be able to advance once (to read the single byte) and then should
|
||||
// reach the COMPLETE state with a successful status.
|
||||
CheckedAdvanceIterator(iterator, 1);
|
||||
CheckIteratorIsComplete(iterator, 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompleteFailure)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write a single byte to the buffer and complete the buffer. (We have to
|
||||
// write at least one byte because completing a zero length buffer always
|
||||
// fails; see the ZeroLengthBufferAlwaysFails test.)
|
||||
CheckedAppendToBuffer(mData, 1);
|
||||
CheckedCompleteBuffer(iterator, 1, NS_ERROR_FAILURE);
|
||||
|
||||
// Advance the iterator. Because a failing status is propagated to the
|
||||
// iterator as soon as it advances, we won't be able to read the single byte
|
||||
// that we wrote above; we go directly into the COMPLETE state.
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, Append)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write test data to the buffer.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData))));
|
||||
CheckedAppendToBuffer(mData, sizeof(mData));
|
||||
CheckedCompleteBuffer(iterator, sizeof(mData));
|
||||
|
||||
// Verify that we can read it back via the iterator, and that the final state
|
||||
// is what we expect.
|
||||
CheckedAdvanceIterator(iterator, sizeof(mData));
|
||||
CheckIteratorIsComplete(iterator, sizeof(mData));
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, HugeAppendFails)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// We should fail to append anything bigger than what the SurfaceCache can
|
||||
// hold, so use the SurfaceCache's maximum capacity to calculate what a
|
||||
// "massive amount of data" (see below) consists of on this platform.
|
||||
ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX);
|
||||
const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1;
|
||||
|
||||
// Attempt to write a massive amount of data and verify that it fails. (We'd
|
||||
// get a buffer overrun during the test if it succeeds, but if it succeeds
|
||||
// that's the least of our problems.)
|
||||
EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, hugeSize)));
|
||||
EXPECT_TRUE(mSourceBuffer->IsComplete());
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, AppendFromInputStream)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Construct an input stream with some arbitrary data. (We use test data from
|
||||
// one of the decoder tests.)
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(GreenPNGTestCase().mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
ASSERT_TRUE(NS_SUCCEEDED(inputStream->Available(&length)));
|
||||
|
||||
// Write test data to the buffer.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->AppendFromInputStream(inputStream,
|
||||
length)));
|
||||
CheckedCompleteBuffer(iterator, length);
|
||||
|
||||
// Verify that the iterator sees the appropriate amount of data.
|
||||
CheckedAdvanceIteratorStateOnly(iterator, length);
|
||||
CheckIteratorIsComplete(iterator, length);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, AppendAfterComplete)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write test data to the buffer.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData))));
|
||||
CheckedAppendToBuffer(mData, sizeof(mData));
|
||||
CheckedCompleteBuffer(iterator, sizeof(mData));
|
||||
|
||||
// Verify that we can read it back via the iterator, and that the final state
|
||||
// is what we expect.
|
||||
CheckedAdvanceIterator(iterator, sizeof(mData));
|
||||
CheckIteratorIsComplete(iterator, sizeof(mData));
|
||||
|
||||
// Write more data to the completed buffer.
|
||||
EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, sizeof(mData))));
|
||||
|
||||
// Try to read with a new iterator and verify that the new data got ignored.
|
||||
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
|
||||
CheckedAdvanceIterator(iterator2, sizeof(mData));
|
||||
CheckIteratorIsComplete(iterator2, sizeof(mData));
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, MinChunkCapacity)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write test data to the buffer using many small appends. Since
|
||||
// ExpectLength() isn't being called, we should be able to write up to
|
||||
// SourceBuffer::MIN_CHUNK_CAPACITY bytes without a second chunk being
|
||||
// allocated.
|
||||
CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Verify that the iterator sees the appropriate amount of data.
|
||||
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1);
|
||||
CheckIteratorIsComplete(iterator, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ExpectLengthDoesNotShrinkBelowMinCapacity)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the buffer,
|
||||
// but call ExpectLength() first to make SourceBuffer expect only a single
|
||||
// byte. We expect this to still result in only one chunk, because
|
||||
// regardless of ExpectLength() we won't allocate a chunk smaller than
|
||||
// MIN_CHUNK_CAPACITY bytes.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(1)));
|
||||
CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
CheckedCompleteBuffer(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Verify that the iterator sees a single chunk.
|
||||
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
CheckIteratorIsComplete(iterator, 1, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ExpectLengthGrowsAboveMinCapacity)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer, calling ExpectLength() with the correct length first. We expect
|
||||
// this to result in only one chunk, because ExpectLength() allows us to
|
||||
// allocate a larger first chunk than MIN_CHUNK_CAPACITY bytes.
|
||||
const size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(length)));
|
||||
CheckedAppendToBufferInChunks(10, length);
|
||||
|
||||
// Verify that the iterator sees a single chunk.
|
||||
CheckedAdvanceIterator(iterator, length);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(length);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 2, length + 1);
|
||||
CheckIteratorIsComplete(iterator, 2, length + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, HugeExpectLengthFails)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// ExpectLength() should fail if the length is bigger than what the
|
||||
// SurfaceCache can hold, so use the SurfaceCache's maximum capacity to
|
||||
// calculate what a "massive amount of data" (see below) consists of on this
|
||||
// platform.
|
||||
ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX);
|
||||
const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1;
|
||||
|
||||
// Attempt to write a massive amount of data and verify that it fails. (We'd
|
||||
// get a buffer overrun during the test if it succeeds, but if it succeeds
|
||||
// that's the least of our problems.)
|
||||
EXPECT_TRUE(NS_FAILED(mSourceBuffer->ExpectLength(hugeSize)));
|
||||
EXPECT_TRUE(mSourceBuffer->IsComplete());
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, LargeAppendsAllocateOnlyOneChunk)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. We expect this to result in only one
|
||||
// chunk even though ExpectLength() wasn't called, because we should always
|
||||
// allocate a new chunk large enough to store the data we have at hand.
|
||||
constexpr size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
char data[length];
|
||||
GenerateData(data, sizeof(data));
|
||||
CheckedAppendToBuffer(data, length);
|
||||
|
||||
// Verify that the iterator sees a single chunk.
|
||||
CheckedAdvanceIterator(iterator, length);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(length);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 2, length + 1);
|
||||
CheckIteratorIsComplete(iterator, 2, length + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, LargeAppendsAllocateAtMostOneChunk)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Allocate some data we'll use below.
|
||||
constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2;
|
||||
constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = firstWriteLength + secondWriteLength;
|
||||
char data[totalLength];
|
||||
GenerateData(data, sizeof(data));
|
||||
|
||||
// Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. This should fill half of the first chunk.
|
||||
CheckedAppendToBuffer(data, firstWriteLength);
|
||||
|
||||
// Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. We expect this to result in the first of
|
||||
// the first chunk being filled and a new chunk being allocated for the
|
||||
// remainder.
|
||||
CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength);
|
||||
|
||||
// Verify that the iterator sees a MIN_CHUNK_CAPACITY-length chunk.
|
||||
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Verify that the iterator sees a second chunk of the length we expect.
|
||||
const size_t expectedSecondChunkLength =
|
||||
totalLength - SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
CheckedAdvanceIterator(iterator, expectedSecondChunkLength, 2, totalLength);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(totalLength);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 3, totalLength + 1);
|
||||
CheckIteratorIsComplete(iterator, 3, totalLength + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompactionHappensWhenBufferIsComplete)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks.
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Verify that the iterator sees two chunks.
|
||||
CheckedAdvanceIterator(iterator, chunkLength);
|
||||
CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength);
|
||||
}
|
||||
|
||||
// Complete the buffer, which should trigger compaction implicitly.
|
||||
CheckedCompleteBuffer();
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Verify that compaction happened and there's now only one chunk.
|
||||
CheckedAdvanceIterator(iterator, totalLength);
|
||||
CheckIteratorIsComplete(iterator, 1, totalLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompactionIsDelayedWhileIteratorsExist)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
{
|
||||
SourceBufferIterator outerIterator = mSourceBuffer->Iterator();
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write enough data to create two chunks.
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
// Verify that the iterator sees two chunks. Since there are live
|
||||
// iterators, compaction shouldn't have happened when we completed the
|
||||
// buffer.
|
||||
CheckedAdvanceIterator(iterator, chunkLength);
|
||||
CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength);
|
||||
CheckIteratorIsComplete(iterator, 2, totalLength);
|
||||
}
|
||||
|
||||
// Now |iterator| has been destroyed, but |outerIterator| still exists, so
|
||||
// we expect no compaction to have occurred at this point.
|
||||
CheckedAdvanceIterator(outerIterator, chunkLength);
|
||||
CheckedAdvanceIterator(outerIterator, chunkLength, 2, totalLength);
|
||||
CheckIteratorIsComplete(outerIterator, 2, totalLength);
|
||||
}
|
||||
|
||||
// Now all iterators have been destroyed. Since the buffer was already
|
||||
// complete, we expect compaction to happen implicitly here.
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Verify that compaction happened and there's now only one chunk.
|
||||
CheckedAdvanceIterator(iterator, totalLength);
|
||||
CheckIteratorIsComplete(iterator, 1, totalLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SourceBufferIteratorsCanBeMoved)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks. We create an iterator here to make
|
||||
// sure that compaction doesn't happen during the test.
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
auto GetIterator = [&]{
|
||||
SourceBufferIterator lambdaIterator = mSourceBuffer->Iterator();
|
||||
CheckedAdvanceIterator(lambdaIterator, chunkLength);
|
||||
return lambdaIterator;
|
||||
};
|
||||
|
||||
// Move-construct |movedIterator| from the iterator returned from
|
||||
// GetIterator() and check that its state is as we expect.
|
||||
SourceBufferIterator movedIterator = Move(GetIterator());
|
||||
EXPECT_TRUE(movedIterator.Data());
|
||||
EXPECT_EQ(chunkLength, movedIterator.Length());
|
||||
ExpectChunkAndByteCount(movedIterator, 1, chunkLength);
|
||||
|
||||
// Make sure that we can advance the iterator.
|
||||
CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength);
|
||||
|
||||
// Make sure that the iterator handles completion properly.
|
||||
CheckIteratorIsComplete(movedIterator, 2, totalLength);
|
||||
|
||||
// Move-assign |movedIterator| from the iterator returned from
|
||||
// GetIterator() and check that its state is as we expect.
|
||||
movedIterator = Move(GetIterator());
|
||||
EXPECT_TRUE(movedIterator.Data());
|
||||
EXPECT_EQ(chunkLength, movedIterator.Length());
|
||||
ExpectChunkAndByteCount(movedIterator, 1, chunkLength);
|
||||
|
||||
// Make sure that we can advance the iterator.
|
||||
CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength);
|
||||
|
||||
// Make sure that the iterator handles completion properly.
|
||||
CheckIteratorIsComplete(movedIterator, 2, totalLength);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SubchunkAdvance)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks. We create our iterator here to make
|
||||
// sure that compaction doesn't happen during the test.
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
// Advance through the first chunk. The chunk count should not increase.
|
||||
// We check that by always passing 1 for the |aChunks| parameter of
|
||||
// CheckedAdvanceIteratorStateOnly(). We have to call CheckData() manually
|
||||
// because the offset calculation in CheckedAdvanceIterator() assumes that
|
||||
// we're advancing a chunk at a time.
|
||||
size_t offset = 0;
|
||||
while (offset < chunkLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
}
|
||||
|
||||
// Read the first byte of the second chunk. This is the point at which we
|
||||
// can't advance within the same chunk, so the chunk count should increase. We
|
||||
// check that by passing 2 for the |aChunks| parameter of
|
||||
// CheckedAdvanceIteratorStateOnly().
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
|
||||
// Read the rest of the second chunk. The chunk count should not increase.
|
||||
while (offset < totalLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
}
|
||||
|
||||
// Make sure we reached the end.
|
||||
CheckIteratorIsComplete(iterator, 2, totalLength);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvance)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks. We create our iterator here to make
|
||||
// sure that compaction doesn't happen during the test.
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
// Make an initial zero-length advance. Although a zero-length advance
|
||||
// normally won't cause us to read a chunk from the SourceBuffer, we'll do so
|
||||
// if the iterator is in the initial state to keep the invariant that
|
||||
// SourceBufferIterator in the READY state always returns a non-null pointer
|
||||
// from Data().
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
|
||||
// Advance through the first chunk. As in the |SubchunkAdvance| test, the
|
||||
// chunk count should not increase. We do a zero-length advance after each
|
||||
// normal advance to ensure that zero-length advances do not change the
|
||||
// iterator's position or cause a new chunk to be read.
|
||||
size_t offset = 0;
|
||||
while (offset < chunkLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
}
|
||||
|
||||
// Read the first byte of the second chunk. This is the point at which we
|
||||
// can't advance within the same chunk, so the chunk count should increase. As
|
||||
// before, we do a zero-length advance afterward.
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
|
||||
// Read the rest of the second chunk. The chunk count should not increase. As
|
||||
// before, we do a zero-length advance after each normal advance.
|
||||
while (offset < totalLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
}
|
||||
|
||||
// Make sure we reached the end.
|
||||
CheckIteratorIsComplete(iterator, 2, totalLength);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvanceWithNoData)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that advancing by zero bytes still makes us enter the WAITING state.
|
||||
// This is because if we entered the READY state before reading any data at
|
||||
// all, we'd break the invariant that SourceBufferIterator::Data() always
|
||||
// returns a non-null pointer in the READY state.
|
||||
auto state = iterator.AdvanceOrScheduleResume(0, mCountResumes);
|
||||
EXPECT_EQ(SourceBufferIterator::WAITING, state);
|
||||
|
||||
// Call Complete(). This should trigger a resume.
|
||||
CheckedCompleteBuffer();
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, NullIResumable)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, nullptr);
|
||||
|
||||
// Append to the buffer, which would cause a resume if we had passed a
|
||||
// non-null IResumable.
|
||||
CheckedAppendToBuffer(mData, sizeof(mData));
|
||||
CheckedCompleteBuffer(iterator, sizeof(mData));
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, AppendTriggersResume)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Call Append(). This should trigger a resume.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, OnlyOneResumeTriggeredPerAppend)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Allocate some data we'll use below.
|
||||
constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2;
|
||||
constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = firstWriteLength + secondWriteLength;
|
||||
char data[totalLength];
|
||||
GenerateData(data, sizeof(data));
|
||||
|
||||
// Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. This should fill half of the first chunk.
|
||||
// This should trigger a resume.
|
||||
CheckedAppendToBuffer(data, firstWriteLength);
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
|
||||
// Advance past the new data and wait again.
|
||||
CheckedAdvanceIterator(iterator, firstWriteLength);
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. We expect this to result in the first of
|
||||
// the first chunk being filled and a new chunk being allocated for the
|
||||
// remainder. Even though two chunks are getting written to here, only *one*
|
||||
// resume should get triggered, for a total of two in this test.
|
||||
CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength);
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompleteTriggersResume)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Call Complete(). This should trigger a resume.
|
||||
CheckedCompleteBuffer();
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ExpectLengthDoesNotTriggerResume)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mExpectNoResume);
|
||||
|
||||
// Call ExpectLength(). If this triggers a resume, |mExpectNoResume| will
|
||||
// ensure that the test fails.
|
||||
mSourceBuffer->ExpectLength(1000);
|
||||
}
|
||||
973
image/test/gtest/TestStreamingLexer.cpp
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
#include "StreamingLexer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
enum class TestState
|
||||
{
|
||||
ONE,
|
||||
TWO,
|
||||
THREE,
|
||||
UNBUFFERED,
|
||||
TRUNCATED_SUCCESS,
|
||||
TRUNCATED_FAILURE
|
||||
};
|
||||
|
||||
void
|
||||
CheckLexedData(const char* aData,
|
||||
size_t aLength,
|
||||
size_t aOffset,
|
||||
size_t aExpectedLength)
|
||||
{
|
||||
EXPECT_TRUE(aLength == aExpectedLength);
|
||||
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
EXPECT_EQ(aData[i], char(aOffset + i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLex(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::To(TestState::TWO, 3);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(aData, aLength, 3, 3);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 6, 3);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::TRUNCATED_SUCCESS:
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::TRUNCATED_FAILURE:
|
||||
return Transition::TerminateFailure();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
|
||||
Vector<char>& aUnbufferedVector)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(aUnbufferedVector.begin(), aUnbufferedVector.length(), 3, 3);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 6, 3);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
EXPECT_TRUE(aLength <= 3);
|
||||
EXPECT_TRUE(aUnbufferedVector.append(aData, aLength));
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::UNBUFFERED:
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithYield(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToAfterYield(TestState::TWO);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::To(TestState::THREE, 6);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 3, 6);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithTerminateAfterYield(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToAfterYield(TestState::TWO);
|
||||
case TestState::TWO:
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStates(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::TWO, 0);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 9);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStatesAtEnd(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::To(TestState::TWO, 0);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 0);
|
||||
case TestState::THREE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthYield(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_EQ(0u, aLength);
|
||||
return Transition::ToAfterYield(TestState::TWO);
|
||||
case TestState::TWO:
|
||||
EXPECT_EQ(0u, aLength);
|
||||
return Transition::To(TestState::THREE, 9);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStatesUnbuffered(TestState aState,
|
||||
const char* aData,
|
||||
size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 0);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 9);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
ADD_FAILURE() << "Should not enter zero-length unbuffered state";
|
||||
return Transition::TerminateFailure();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStatesAfterUnbuffered(TestState aState,
|
||||
const char* aData,
|
||||
size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 9);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 0);
|
||||
case TestState::THREE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
class ImageStreamingLexer : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
// Note that mLexer is configured to enter TerminalState::FAILURE immediately
|
||||
// if the input data is truncated. We don't expect that to happen in most
|
||||
// tests, so we want to detect that issue. If a test needs a different
|
||||
// behavior, we create a special StreamingLexer just for that test.
|
||||
ImageStreamingLexer()
|
||||
: mLexer(Transition::To(TestState::ONE, 3), Transition::TerminateFailure())
|
||||
, mSourceBuffer(new SourceBuffer)
|
||||
, mIterator(mSourceBuffer->Iterator())
|
||||
, mExpectNoResume(new ExpectNoResume)
|
||||
, mCountResumes(new CountResumes)
|
||||
{ }
|
||||
|
||||
protected:
|
||||
void CheckTruncatedState(StreamingLexer<TestState>& aLexer,
|
||||
TerminalState aExpectedTerminalState,
|
||||
nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
if (i < 2) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
} else if (i == 2) {
|
||||
mSourceBuffer->Complete(aCompletionStatus);
|
||||
}
|
||||
|
||||
LexerResult result = aLexer.Lex(mIterator, mCountResumes, DoLex);
|
||||
|
||||
if (i >= 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(aExpectedTerminalState, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
AutoInitializeImageLib mInit;
|
||||
const char mData[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
StreamingLexer<TestState> mLexer;
|
||||
RefPtr<SourceBuffer> mSourceBuffer;
|
||||
SourceBufferIterator mIterator;
|
||||
RefPtr<ExpectNoResume> mExpectNoResume;
|
||||
RefPtr<CountResumes> mCountResumes;
|
||||
};
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthData)
|
||||
{
|
||||
// Test a zero-length input.
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthDataUnbuffered)
|
||||
{
|
||||
// Test a zero-length input.
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be unbuffered.
|
||||
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
|
||||
TestState::UNBUFFERED,
|
||||
sizeof(mData)),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, StartWithTerminal)
|
||||
{
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be a terminal state. This doesn't really make sense, but we should
|
||||
// handle it.
|
||||
StreamingLexer<TestState> lexer(Transition::TerminateSuccess(),
|
||||
Transition::TerminateFailure());
|
||||
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SingleChunk)
|
||||
{
|
||||
// Test delivering all the data at once.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SingleChunkWithUnbuffered)
|
||||
{
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering all the data at once.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SingleChunkWithYield)
|
||||
{
|
||||
// Test delivering all the data at once.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield);
|
||||
ASSERT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerState)
|
||||
{
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
mSourceBuffer->Append(mData + 3 * i, 3);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex);
|
||||
|
||||
if (i == 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
|
||||
{
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
mSourceBuffer->Append(mData + 3 * i, 3);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
if (i == 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerStateWithYield)
|
||||
{
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
mSourceBuffer->Append(mData, 3);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
|
||||
mSourceBuffer->Append(mData + 3, 6);
|
||||
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbufferedYield)
|
||||
{
|
||||
size_t unbufferedCallCount = 0;
|
||||
Vector<char> unbufferedVector;
|
||||
auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength)
|
||||
-> LexerTransition<TestState> {
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(unbufferedVector.begin(), unbufferedVector.length(), 3, 3);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 6, 3);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
switch (unbufferedCallCount) {
|
||||
case 0:
|
||||
CheckLexedData(aData, aLength, 3, 3);
|
||||
EXPECT_TRUE(unbufferedVector.append(aData, 2));
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 2 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2);
|
||||
|
||||
case 1:
|
||||
CheckLexedData(aData, aLength, 5, 1);
|
||||
EXPECT_TRUE(unbufferedVector.append(aData, 1));
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 1 byte.
|
||||
// We should end up in the TWO state.
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
}
|
||||
ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED";
|
||||
return Transition::TerminateFailure();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
};
|
||||
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
mSourceBuffer->Append(mData + 3 * i, 3);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
EXPECT_EQ(0u, unbufferedCallCount);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(1u, unbufferedCallCount);
|
||||
|
||||
result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
EXPECT_EQ(2u, unbufferedCallCount);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, OneByteChunks)
|
||||
{
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex);
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(8u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, OneByteChunksWithUnbuffered)
|
||||
{
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(8u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, OneByteChunksWithYield)
|
||||
{
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
|
||||
switch (i) {
|
||||
case 2:
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
break;
|
||||
|
||||
case 8:
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
break;
|
||||
|
||||
default:
|
||||
EXPECT_TRUE(i < 9);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(8u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthState)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be zero length.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStates);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStatesAtEnd)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to consume the full input.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 9),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAtEnd);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateWithYield)
|
||||
{
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be zero length.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
mSourceBuffer->Append(mData, 3);
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = lexer.Lex(mIterator, mCountResumes, DoLexWithZeroLengthYield);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
|
||||
mSourceBuffer->Append(mData + 3, sizeof(mData) - 3);
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield);
|
||||
ASSERT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbuffered)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be both zero length and unbuffered.
|
||||
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
|
||||
TestState::UNBUFFERED,
|
||||
0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesUnbuffered);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateAfterUnbuffered)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be zero length.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAfterUnbuffered);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbufferedYield)
|
||||
{
|
||||
size_t unbufferedCallCount = 0;
|
||||
auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength)
|
||||
-> LexerTransition<TestState> {
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_EQ(0u, aLength);
|
||||
return Transition::TerminateSuccess();
|
||||
|
||||
case TestState::UNBUFFERED:
|
||||
switch (unbufferedCallCount) {
|
||||
case 0:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 0 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 0);
|
||||
|
||||
case 1:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 2 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2);
|
||||
|
||||
case 2:
|
||||
EXPECT_EQ(1u, aLength);
|
||||
CheckLexedData(aData, aLength, 2, 1);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 1 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 1);
|
||||
|
||||
case 3:
|
||||
CheckLexedData(aData, aLength, 3, 6);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 6 bytes.
|
||||
// We should transition to TestState::ONE when we return from the
|
||||
// yield.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 6);
|
||||
}
|
||||
|
||||
ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED";
|
||||
return Transition::TerminateFailure();
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
};
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be unbuffered.
|
||||
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
|
||||
TestState::UNBUFFERED,
|
||||
sizeof(mData)),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
mSourceBuffer->Append(mData, 3);
|
||||
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(1u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(2u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(3u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
EXPECT_EQ(3u, unbufferedCallCount);
|
||||
|
||||
mSourceBuffer->Append(mData + 3, 6);
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(4u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateSuccess)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Test that Terminate is "sticky".
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
LexerResult result =
|
||||
mLexer.Lex(iterator, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(aState == TestState::ONE);
|
||||
return Transition::TerminateSuccess();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
|
||||
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
|
||||
result =
|
||||
mLexer.Lex(iterator2, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::TerminateFailure();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateFailure)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Test that Terminate is "sticky".
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
LexerResult result =
|
||||
mLexer.Lex(iterator, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(aState == TestState::ONE);
|
||||
return Transition::TerminateFailure();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
|
||||
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
|
||||
result =
|
||||
mLexer.Lex(iterator2, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::TerminateFailure();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateUnbuffered)
|
||||
{
|
||||
// Test that Terminate works during an unbuffered read.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes, DoLexWithUnbufferedTerminate);
|
||||
|
||||
if (i > 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
// We expect 3 resumes because TestState::ONE consumes 3 bytes and then
|
||||
// transitions to TestState::UNBUFFERED, which calls TerminateSuccess() as
|
||||
// soon as it receives a single byte. That's four bytes total, which are
|
||||
// delivered one at a time, requiring 3 resumes.
|
||||
EXPECT_EQ(3u, mCountResumes->Count());
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateAfterYield)
|
||||
{
|
||||
// Test that Terminate works after yielding.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes, DoLexWithTerminateAfterYield);
|
||||
|
||||
if (i > 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else if (i == 2) {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
// We expect 2 resumes because TestState::ONE consumes 3 bytes and then
|
||||
// yields. When the lexer resumes at TestState::TWO, which receives the same 3
|
||||
// bytes, TerminateSuccess() gets called immediately. That's three bytes
|
||||
// total, which are delivered one at a time, requiring 2 resumes.
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferImmediateComplete)
|
||||
{
|
||||
// Test calling SourceBuffer::Complete() without appending any data. This
|
||||
// causes the SourceBuffer to automatically have a failing completion status,
|
||||
// no matter what you pass, so we expect TerminalState::FAILURE below.
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateSuccess)
|
||||
{
|
||||
// Test that using a terminal state (in this case TerminalState::SUCCESS) as a
|
||||
// truncated state works.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::TerminateSuccess());
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::SUCCESS);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateFailure)
|
||||
{
|
||||
// Test that using a terminal state (in this case TerminalState::FAILURE) as a
|
||||
// truncated state works.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningSuccess)
|
||||
{
|
||||
// Test that a truncated state that returns TerminalState::SUCCESS works. When
|
||||
// |lexer| discovers that the data is truncated, it invokes the
|
||||
// TRUNCATED_SUCCESS state, which returns TerminalState::SUCCESS.
|
||||
// CheckTruncatedState() verifies that this happens.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::To(TestState::TRUNCATED_SUCCESS, 0));
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::SUCCESS);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningFailure)
|
||||
{
|
||||
// Test that a truncated state that returns TerminalState::FAILURE works. When
|
||||
// |lexer| discovers that the data is truncated, it invokes the
|
||||
// TRUNCATED_FAILURE state, which returns TerminalState::FAILURE.
|
||||
// CheckTruncatedState() verifies that this happens.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::To(TestState::TRUNCATED_FAILURE, 0));
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedFailingCompleteStatus)
|
||||
{
|
||||
// Test that calling SourceBuffer::Complete() with a failing status results in
|
||||
// an immediate TerminalState::FAILURE result. (Note that |lexer|'s truncated
|
||||
// state is TerminalState::SUCCESS, so if we ignore the failing status, the
|
||||
// test will fail.)
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::TerminateSuccess());
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::FAILURE, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, NoSourceBufferResumable)
|
||||
{
|
||||
// Test delivering in one byte chunks with no IResumable.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result = mLexer.Lex(mIterator, nullptr, DoLex);
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
508
image/test/gtest/TestSurfacePipeIntegration.cpp
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfacePipe.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class TestSurfacePipeFactory
|
||||
{
|
||||
public:
|
||||
static SurfacePipe SimpleSurfacePipe()
|
||||
{
|
||||
SurfacePipe pipe;
|
||||
return Move(pipe);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline)
|
||||
{
|
||||
return SurfacePipe { Move(aPipeline) };
|
||||
}
|
||||
|
||||
private:
|
||||
TestSurfacePipeFactory() { }
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
void
|
||||
CheckSurfacePipeMethodResults(SurfacePipe* aPipe,
|
||||
Decoder* aDecoder,
|
||||
const IntRect& aRect = IntRect(0, 0, 100, 100))
|
||||
{
|
||||
// Check that the pipeline ended up in the state we expect. Note that we're
|
||||
// explicitly testing the SurfacePipe versions of these methods, so we don't
|
||||
// want to use AssertCorrectPipelineFinalState() here.
|
||||
EXPECT_TRUE(aPipe->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
|
||||
|
||||
// Check the generated image.
|
||||
CheckGeneratedImage(aDecoder, aRect);
|
||||
|
||||
// Reset and clear the image before the next test.
|
||||
aPipe->ResetToFirstRow();
|
||||
EXPECT_FALSE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
uint32_t count = 0;
|
||||
auto result = aPipe->WritePixels<uint32_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
|
||||
EXPECT_TRUE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
|
||||
|
||||
aPipe->ResetToFirstRow();
|
||||
EXPECT_FALSE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
}
|
||||
|
||||
void
|
||||
CheckPalettedSurfacePipeMethodResults(SurfacePipe* aPipe,
|
||||
Decoder* aDecoder,
|
||||
const IntRect& aRect
|
||||
= IntRect(0, 0, 100, 100))
|
||||
{
|
||||
// Check that the pipeline ended up in the state we expect. Note that we're
|
||||
// explicitly testing the SurfacePipe versions of these methods, so we don't
|
||||
// want to use AssertCorrectPipelineFinalState() here.
|
||||
EXPECT_TRUE(aPipe->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
|
||||
|
||||
// Check the generated image.
|
||||
CheckGeneratedPalettedImage(aDecoder, aRect);
|
||||
|
||||
// Reset and clear the image before the next test.
|
||||
aPipe->ResetToFirstRow();
|
||||
EXPECT_FALSE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
|
||||
uint32_t count = 0;
|
||||
auto result = aPipe->WritePixels<uint8_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(uint8_t(0));
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
|
||||
EXPECT_TRUE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
|
||||
|
||||
aPipe->ResetToFirstRow();
|
||||
EXPECT_FALSE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
}
|
||||
|
||||
class ImageSurfacePipeIntegration : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, SurfacePipe)
|
||||
{
|
||||
// Test that SurfacePipe objects can be initialized and move constructed.
|
||||
SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
|
||||
|
||||
// Test that SurfacePipe objects can be move assigned.
|
||||
pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
|
||||
|
||||
// Test that SurfacePipe objects can be initialized with a pipeline.
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto sink = MakeUnique<SurfaceSink>();
|
||||
nsresult rv =
|
||||
sink->Configure(SurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
|
||||
|
||||
// Test that WritePixels() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
auto result = pipe.WritePixels<uint32_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
CheckSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Create a buffer the same size as one row of the surface, containing all
|
||||
// green pixels. We'll use this for the WriteBuffer() tests.
|
||||
uint32_t buffer[100];
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
buffer[i] = BGRAColor::Green().AsPixel();
|
||||
}
|
||||
|
||||
// Test that WriteBuffer() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
while (result == WriteState::NEED_MORE_DATA) {
|
||||
result = pipe.WriteBuffer(buffer);
|
||||
++count;
|
||||
}
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u, count);
|
||||
CheckSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Test that the 3 argument version of WriteBuffer() gets passed through to
|
||||
// the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
while (result == WriteState::NEED_MORE_DATA) {
|
||||
result = pipe.WriteBuffer(buffer, 0, 100);
|
||||
++count;
|
||||
}
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u, count);
|
||||
CheckSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Test that WriteEmptyRow() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
while (result == WriteState::NEED_MORE_DATA) {
|
||||
result = pipe.WriteEmptyRow();
|
||||
++count;
|
||||
}
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u, count);
|
||||
CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
// Mark the frame as finished so we don't get an assertion.
|
||||
RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
|
||||
currentFrame->Finish();
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, PalettedSurfacePipe)
|
||||
{
|
||||
// Create a SurfacePipe containing a PalettedSurfaceSink.
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto sink = MakeUnique<PalettedSurfaceSink>();
|
||||
nsresult rv =
|
||||
sink->Configure(PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
IntRect(0, 0, 100, 100),
|
||||
SurfaceFormat::B8G8R8A8,
|
||||
8, false });
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
SurfacePipe pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
|
||||
|
||||
// Test that WritePixels() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
auto result = pipe.WritePixels<uint8_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(uint8_t(255));
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Create a buffer the same size as one row of the surface, containing all
|
||||
// 255 pixels. We'll use this for the WriteBuffer() tests.
|
||||
uint8_t buffer[100];
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
buffer[i] = 255;
|
||||
}
|
||||
|
||||
// Test that WriteBuffer() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
while (result == WriteState::NEED_MORE_DATA) {
|
||||
result = pipe.WriteBuffer(buffer);
|
||||
++count;
|
||||
}
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u, count);
|
||||
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Test that the 3 argument version of WriteBuffer() gets passed through to
|
||||
// the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
while (result == WriteState::NEED_MORE_DATA) {
|
||||
result = pipe.WriteBuffer(buffer, 0, 100);
|
||||
++count;
|
||||
}
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u, count);
|
||||
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Test that WriteEmptyRow() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
while (result == WriteState::NEED_MORE_DATA) {
|
||||
result = pipe.WriteEmptyRow();
|
||||
++count;
|
||||
}
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u, count);
|
||||
CheckPalettedSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
// Mark the frame as finished so we don't get an assertion.
|
||||
RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
|
||||
currentFrame->Finish();
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 25, 25)));
|
||||
};
|
||||
|
||||
WithFilterPipeline(decoder, test,
|
||||
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, IntSize(25, 25),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectBottomRightDownscaleWritePixels)
|
||||
{
|
||||
// This test case uses a frame rect that extends beyond the borders of the
|
||||
// image to the bottom and to the right. It looks roughly like this (with the
|
||||
// box made of '#'s representing the frame rect):
|
||||
//
|
||||
// +------------+
|
||||
// + +
|
||||
// + +------------+
|
||||
// + +############+
|
||||
// +------+############+
|
||||
// +############+
|
||||
// +------------+
|
||||
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// Note that aInputWriteRect is 100x50 because RemoveFrameRectFilter ignores
|
||||
// trailing rows that don't show up in the output. (Leading rows unfortunately
|
||||
// can't be ignored.) So the action of the pipeline is as follows:
|
||||
//
|
||||
// (1) RemoveFrameRectFilter reads a 100x50 region of the input.
|
||||
// (aInputWriteRect captures this fact.) The remaining 50 rows are ignored
|
||||
// because they extend off the bottom of the image due to the frame rect's
|
||||
// (50, 50) offset. The 50 columns on the right also don't end up in the
|
||||
// output, so ultimately only a 50x50 region in the output contains data
|
||||
// from the input. The filter's output is not 50x50, though, but 100x100,
|
||||
// because what RemoveFrameRectFilter does is introduce blank rows or
|
||||
// columns as necessary to transform an image that needs a frame rect into
|
||||
// an image that doesn't.
|
||||
//
|
||||
// (2) DownscalingFilter reads the output of RemoveFrameRectFilter (100x100)
|
||||
// and downscales it to 20x20.
|
||||
//
|
||||
// (3) The surface owned by SurfaceSink logically has only a 10x10 region
|
||||
// region in it that's non-blank; this is the downscaled version of the
|
||||
// 50x50 region discussed in (1). (aOutputWriteRect captures this fact.)
|
||||
// Some fuzz, as usual, is necessary when dealing with Lanczos downscaling.
|
||||
|
||||
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)),
|
||||
/* aFuzz = */ 0x33);
|
||||
};
|
||||
|
||||
WithFilterPipeline(decoder, test,
|
||||
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, IntSize(20, 20),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectTopLeftDownscaleWritePixels)
|
||||
{
|
||||
// This test case uses a frame rect that extends beyond the borders of the
|
||||
// image to the top and to the left. It looks roughly like this (with the
|
||||
// box made of '#'s representing the frame rect):
|
||||
//
|
||||
// +------------+
|
||||
// +############+
|
||||
// +############+------+
|
||||
// +############+ +
|
||||
// +------------+ +
|
||||
// + +
|
||||
// +------------+
|
||||
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(0, 0, 10, 10)),
|
||||
/* aFuzz = */ 0x21);
|
||||
};
|
||||
|
||||
WithFilterPipeline(decoder, test,
|
||||
RemoveFrameRectConfig { IntRect(-50, -50, 100, 100) },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, IntSize(20, 20),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// Note that aInputRect is the full 100x100 size even though
|
||||
// RemoveFrameRectFilter is part of this pipeline, because deinterlacing
|
||||
// requires reading every row.
|
||||
|
||||
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50)));
|
||||
};
|
||||
|
||||
WithFilterPipeline(decoder, test,
|
||||
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
|
||||
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
|
||||
SurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixels)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(IntRect(0, 0, 20, 20)),
|
||||
/* aInputRect = */ Some(IntRect(0, 0, 100, 100)),
|
||||
/* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)),
|
||||
/* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)),
|
||||
/* aFuzz = */ 33);
|
||||
};
|
||||
|
||||
WithFilterPipeline(decoder, test,
|
||||
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true },
|
||||
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, IntSize(20, 20),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedRemoveFrameRectDownscaleFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// This is an invalid pipeline for paletted images, so configuration should
|
||||
// fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
RemoveFrameRectConfig { IntRect(0, 0, 50, 50) },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
IntRect(0, 0, 50, 50),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedDeinterlaceDownscaleFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// This is an invalid pipeline for paletted images, so configuration should
|
||||
// fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true},
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
|
||||
IntRect(0, 0, 20, 20),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// When DownscalingFilter is used, we may succeed in allocating an output
|
||||
// surface for huge images, because we only need to store the scaled-down
|
||||
// version of the image. However, regardless of downscaling,
|
||||
// DeinterlacingFilter needs to allocate a buffer as large as the size of the
|
||||
// input. This can cause OOMs on operating systems that allow overcommit. This
|
||||
// test makes sure that we reject such allocations.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
|
||||
DownscalingConfig { IntSize(60000, 60000),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 0, IntSize(600, 600),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
1491
image/test/gtest/TestSurfaceSink.cpp
Normal file
BIN
image/test/gtest/animated-with-extra-image-sub-blocks.gif
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
image/test/gtest/corrupt-with-bad-bmp-height.ico
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image/test/gtest/corrupt-with-bad-bmp-width.ico
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image/test/gtest/corrupt.jpg
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
image/test/gtest/downscaled.bmp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
image/test/gtest/downscaled.gif
Normal file
|
After Width: | Height: | Size: 223 B |
BIN
image/test/gtest/downscaled.ico
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image/test/gtest/downscaled.icon
Normal file
BIN
image/test/gtest/downscaled.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
image/test/gtest/downscaled.png
Normal file
|
After Width: | Height: | Size: 1,015 B |
BIN
image/test/gtest/first-frame-green.gif
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
image/test/gtest/first-frame-green.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
image/test/gtest/first-frame-padding.gif
Normal file
|
After Width: | Height: | Size: 49 B |
BIN
image/test/gtest/green-1x1-truncated.gif
Normal file
|
After Width: | Height: | Size: 53 B |
BIN
image/test/gtest/green.bmp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
image/test/gtest/green.gif
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
image/test/gtest/green.ico
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image/test/gtest/green.icon
Normal file
BIN
image/test/gtest/green.jpg
Normal file
|
After Width: | Height: | Size: 361 B |
BIN
image/test/gtest/green.png
Normal file
|
After Width: | Height: | Size: 255 B |
BIN
image/test/gtest/invalid-truncated-metadata.bmp
Normal file
|
After Width: | Height: | Size: 54 B |
78
image/test/gtest/moz.build
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Library('imagetest')
|
||||
|
||||
UNIFIED_SOURCES = [
|
||||
'Common.cpp',
|
||||
'TestADAM7InterpolatingFilter.cpp',
|
||||
'TestCopyOnWrite.cpp',
|
||||
'TestDecoders.cpp',
|
||||
'TestDecodeToSurface.cpp',
|
||||
'TestDeinterlacingFilter.cpp',
|
||||
'TestMetadata.cpp',
|
||||
'TestRemoveFrameRectFilter.cpp',
|
||||
'TestSourceBuffer.cpp',
|
||||
'TestStreamingLexer.cpp',
|
||||
'TestSurfaceSink.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ENABLE_SKIA']:
|
||||
UNIFIED_SOURCES += [
|
||||
'TestDownscalingFilter.cpp',
|
||||
'TestSurfacePipeIntegration.cpp',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
# Can't be unified because it manipulates the preprocessor environment.
|
||||
'TestDownscalingFilterNoSkia.cpp',
|
||||
]
|
||||
|
||||
TEST_HARNESS_FILES.gtest += [
|
||||
'animated-with-extra-image-sub-blocks.gif',
|
||||
'corrupt-with-bad-bmp-height.ico',
|
||||
'corrupt-with-bad-bmp-width.ico',
|
||||
'corrupt.jpg',
|
||||
'downscaled.bmp',
|
||||
'downscaled.gif',
|
||||
'downscaled.ico',
|
||||
'downscaled.icon',
|
||||
'downscaled.jpg',
|
||||
'downscaled.png',
|
||||
'first-frame-green.gif',
|
||||
'first-frame-green.png',
|
||||
'first-frame-padding.gif',
|
||||
'green-1x1-truncated.gif',
|
||||
'green.bmp',
|
||||
'green.gif',
|
||||
'green.ico',
|
||||
'green.icon',
|
||||
'green.jpg',
|
||||
'green.png',
|
||||
'invalid-truncated-metadata.bmp',
|
||||
'no-frame-delay.gif',
|
||||
'rle4.bmp',
|
||||
'rle8.bmp',
|
||||
'transparent-ico-with-and-mask.ico',
|
||||
'transparent-if-within-ico.bmp',
|
||||
'transparent.gif',
|
||||
'transparent.png',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/gfx/2d',
|
||||
'/image',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
BIN
image/test/gtest/no-frame-delay.gif
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
image/test/gtest/rle4.bmp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
image/test/gtest/rle8.bmp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
image/test/gtest/transparent-ico-with-and-mask.ico
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
image/test/gtest/transparent-if-within-ico.bmp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
image/test/gtest/transparent.gif
Normal file
|
After Width: | Height: | Size: 355 B |