Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,587 @@
'use strict';
/* global ImageUtils */
require('/shared/js/image_utils.js');
suite('ImageUtils', function() {
const testImages = [
{
type: 'image/jpeg',
width: 25,
height: 24,
base64: '/9j/4AAQSkZJRgABAQEASABIAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAAG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAHMAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFkAAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAoAAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUAAAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtvS1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAADHmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAaAAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwAAAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZpRkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDoAHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8EFwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIARwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkAbMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAgAHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEAbABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIARwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBvAGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACADwAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRNAAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBs/+EAnkV4aWYAAE1NACoAAAAIAAYBEgADAAAAAQABAAABGgAFAAAAAQAAAFYBGwAFAAAAAQAAAF4BKAADAAAAAQACAAABMQACAAAAEQAAAGaHaQAEAAAAAQAAAHgAAAAAAAAASAAAAAEAAABIAAAAAUFkb2JlIEltYWdlUmVhZHkAAAACoAIABAAAAAEAAAAZoAMABAAAAAEAAAAYAAAAAP/bAEMAIBYYHBgUIBwaHCQiICYwUDQwLCwwYkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxv/bAEMBIiQkMCowXjQ0XsaEcITGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxv/AABEIABgAGQMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANu5m8iEyYzjoPWsuXU5YzuJHrjHFaF/C01qyp94cgetc5JGJDtOFJ4OQcikpcstdi/Z88dNzqIZBLGrjuM1JVWxD+SGcbc4CjvirVEW2tSWrMrOblXYqoYZOAfSmZuASxhRienHSiimIlJm38Zxnpip6KKAP//Z' // jshint ignore:line
},
{
type: 'image/png',
width: 25,
height: 24,
base64: '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' // jshint ignore:line
},
{
type: 'image/gif',
width: 25,
height: 24,
base64: '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' // jshint ignore:line
},
{
type: 'image/bmp',
width: 25,
height: 24,
base64: 'Qk1WBwAAAAAAADYAAAAoAAAAGQAAABgAAAABABgAAAAAACAHAAATCwAAEwsAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////AP///0c5OUBAQAAAAEY+PkBAQFUrK0BAQEU9PUQ8PEBAQEY6OkRAPEBAOkNDNkY+Pkc9Pf///0M/P0BAQEBAQERAPEY+Pv///wAAAAD///9DPT1DPj7///9CPjxEPj5DPz9CPT1CPzxDQD1EPz1CPj5DPz1CPT1CPjxDPjxHPT1EQDxEPj5DPj5CPz9AQEBDPj5EPj7///8A////Qj87Qj48QTw8Qz89Qj09QT09Qz49Qz09RD49Qz89Qj48Qj87Qz09Qz09Qz48////RD4+////Qz09Qj09RDw8Qj48QTs7////AP///0M8PEM+PUM9PUI8PEM/PUI+PEQ9PUE9PUM9PUM+PEM+PkM9PUI/PEI9PUM+PkNDQ0I9PUU7O0E8PEM+PkI+PkBAQP///wAAAAD///9CPT1DPjxDPj5CPT1HOTn///9AQED///9EPj5CPT9COkoAAP8AAP////////////9EPj5DPj5EPj5CPT1DPj5EPT1GRkb///8A////////Szw8QEBAQjk5QEBA////////////////SkoggIAA////////////////////////REQzVSsr////SUk3Q0ND////////AP///////////////////////wAAAAAAqgBAAAAAAAAAMgoAXBcNaBAKVg4AOwAAAP///0BAv////////0BAQP///////////////wD///8AAABVVVVVVVVVVVX///8AAGYAAAAMAD8XEHobGJAcH5sdJKQcJaMcJqMeIIoYAEgAAAAkJEmAgID///+AgICAgID///////8A////////////////////AAD/////Fw5xHBaRHyOsHyuyHzK4Hze9HzvCHz3EIznAHyyhFCKM////////AAAA////////////////AP///////////////wAAv////x0Xlx0bnCAosR8yth06wRxDyxxIzB1Lyx1X0x5a1B9JzSA6uiIxrP///wAAgP///////////////wD///////////8AAID///////8dIaIgKrYeNLkePsMgRMIqPJ8xLGkuKWMpP5Uha84gctgkWM4lNKAhC5D///8AAID///////////8A////////////MzOZ////HCiqHimrHjS7Hj7DH0TGID+1JSmEMwA9PAAAPAAAL0VjGaLcGpjdI2bFJBuy////QEC/////////////AP///////wAA/wAAgP///x8zsyA1wR09wR9CwxlPzCFUxS1TuhxnxDVmm0whIz0NAC+WuyHL8Ra37Rh84P///wCAv////////////wD///////8AgP////////8eOrsgPcshRcUYPr4tP7BeNVdmMCdeVG9eUmZWJyJIAAA7Z3o02/cq5P4plMn///////8AAP////////8A////////AID/////////HkPFIEPQIU3IHE/GKkmsZExihC4AhSsAeSUAajEjVgAAdnp5d+36cO//SpK2////////AP//////////AP///////wD//////////x5PzSFL1x9QyCRczAd42j5+x5JiX51RKJNKMII9JnQnAI1aUHrN14r6/0y33v///////wAAAP///////wD///////////8AQL////8dVdUeT84iU8smZM8jfdAlaMdeer6iaEuuXRGcVjKYRBWMQyOL1d1r/P8A6vz///8zzMz///////////8A////////////AEC/////IjOqIES+AFTaAGjZAIzheXSTrHBTu3w4w3oct28upEwAwJmNv///ZN7wANbr////Vaqq////////////AP///////////wCA/////1VVqg5Ev3JgoIxogGGDq3ycpNaPBNmRI9COLsF6HrNlIszHv7Td7Wedx////////////////////////wD///////////////////////8AY+/BlHXRhiLXiTDGnGjaoFbmoUfin0TfljDYo3HOx7mfpsL///////8AAP////////////////8A/////////////////////////////////9eu5KVb56BC5aRT5ahb46VW36BV57qG/+Nx////////AAD/////////////////////AP///////////////////////wCA/7+AgP//////////0fO/eu22bfO7dv//tv///////8zMzP///////////////////////////wD/////////////////////////////////qlWqVQD///////////////////+/v4D/qqr///////////////////////////////8A' // jshint ignore:line
}
];
suite('MIME type constants', function() {
test('JPEG', function() { assert.equal(ImageUtils.JPEG, 'image/jpeg'); });
test('PNG', function() { assert.equal(ImageUtils.PNG, 'image/png'); });
test('GIF', function() { assert.equal(ImageUtils.GIF, 'image/gif'); });
test('BMP', function() { assert.equal(ImageUtils.BMP, 'image/bmp'); });
});
suite('getSizeAndType', function() {
function makeBlobFromString(binaryString, type) {
var buffer = new ArrayBuffer(binaryString.length);
var bytes = new Uint8Array(buffer);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new Blob([buffer], { type: type });
}
suiteSetup(function() {
// Create blobs for each of the test images
testImages.forEach(function(image) {
image.blob = makeBlobFromString(atob(image.base64), image.type);
});
});
test('rejects string argument', function(done) {
ImageUtils.getSizeAndType('data:foo').then(null, function() { done(); });
});
test('rejects numeric argument', function(done) {
ImageUtils.getSizeAndType(1).then(null, function() { done(); });
});
test('rejects object argument', function(done) {
ImageUtils.getSizeAndType({size:100}).then(null, function() { done(); });
});
// Test that we can get the width and height of each test image
testImages.forEach(function(image) {
test(image.type, function(done) {
ImageUtils.getSizeAndType(image.blob).then(function resolve(data) {
try {
assert.equal(data.type, image.type);
assert.equal(data.width, image.width);
assert.equal(data.height, image.height);
done();
}
catch (e) {
done(e);
}
}, function reject(e) { done(new Error(e)); });
});
});
// Test that we can get the size of a JPEG file even if it has a large
// EXIF metadata segment before the segments that hold image size.
test('jpeg with large EXIF segments', function(done) {
// Splice some fake EXIF data in at the start of the JPEG file
// to force the image size data to be beyond the 32kb boundary
var jpeg = testImages[0];
var exif = new ArrayBuffer(40 * 1024);
var view = new DataView(exif);
view.setUint8(0, 0xFF);
view.setUint8(1, 0xE0); // APP0 segment for EXIF
view.setUint16(2, (40 * 1024) - 2);
var newblob = new Blob([jpeg.blob.slice(0, 2),
exif, exif, jpeg.blob.slice(2)],
{ type: 'image/jpeg' });
ImageUtils.getSizeAndType(newblob).then(
function resolve(data) {
try {
assert.equal(data.type, jpeg.type);
assert.equal(data.width, jpeg.width);
assert.equal(data.height, jpeg.height);
done();
}
catch (e) {
done(e);
}
},
function reject(e) {
done(new Error(e));
}
);
});
test('reject zero-length blob', function(done) {
var empty = new Blob([''], { type: 'image/jpeg' });
ImageUtils.getSizeAndType(empty).then(
function resolve() {
done(new Error('failed to reject an empty blob'));
},
function reject() {
done();
}
);
});
test('reject any blob < 16 bytes', function(done) {
var empty = new Blob(['0123456789012345'], { type: 'image/jpeg' });
ImageUtils.getSizeAndType(empty).then(
function resolve() {
done(new Error('failed to reject a short blob'));
},
function reject() {
done();
}
);
});
// Test that if we truncate the test images we can't get their size
testImages.forEach(function(image) {
// We can't meaningfully test truncated gif files
if (image.type === 'image/gif') {
return;
}
test('reject truncated ' + image.type, function(done) {
var truncated = image.blob.slice(0, 20);
ImageUtils.getSizeAndType(truncated).then(
function resolve(data) {
done(new Error('failed to reject a truncated blob'));
},
function reject() {
done();
}
);
});
});
// Here are some files that are not image files and should be rejected
var bogusFiles = [
'This is a text file not an image file. It does not have a size',
'GIF85a This is not a gif: the header is not correct',
'\xff\xd8 Not a jpeg file because it does not have segments',
'BM not really a BMP file since. File length data is not correct',
'\x89PNG\r\n\x1a\n Not a PNG file: does not have an ihdr segment'
];
bogusFiles.forEach(function(file, index) {
test('Reject bogus file ' + index, function(done) {
var bogus = makeBlobFromString(file, 'text/plain');
ImageUtils.getSizeAndType(bogus).then(
function resolve(data) {
done(new Error('failed to reject bogus text:' + file));
},
function reject() {
done();
}
);
});
});
});
suite('Downsample tests', function() {
test('Check API', function() {
assert.typeOf(ImageUtils.Downsample, 'object');
assert.typeOf(ImageUtils.Downsample.sizeAtLeast, 'function');
assert.typeOf(ImageUtils.Downsample.sizeNoMoreThan, 'function');
assert.typeOf(ImageUtils.Downsample.areaAtLeast, 'function');
assert.typeOf(ImageUtils.Downsample.areaNoMoreThan, 'function');
assert.typeOf(ImageUtils.Downsample.MAX_SIZE_REDUCTION, 'number');
assert.typeOf(ImageUtils.Downsample.MAX_AREA_REDUCTION, 'number');
assert.typeOf(ImageUtils.Downsample.NONE, 'object');
assert.equal(ImageUtils.Downsample.NONE.dimensionScale, 1);
assert.equal(ImageUtils.Downsample.NONE.areaScale, 1);
assert.equal(ImageUtils.Downsample.NONE.toString(), '');
});
var scales = [
10, 2, 1,
1 / 8, 2 / 8, 3 / 8, 4 / 8, 5 / 8, 6 / 8, 7 / 8,
0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1,
0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01
];
// This is the same function we use in downsample.js
function round(x) {
return Math.round(x * 100) / 100;
}
test('sizeAtLeast', function() {
var min = 1 / ImageUtils.Downsample.MAX_SIZE_REDUCTION;
scales.forEach(function(scale) {
var ds = ImageUtils.Downsample.sizeAtLeast(scale);
assert.operator(ds.dimensionScale, '<=', round(Math.max(scale, min)));
assert.equal(ds.scale(100), ds.dimensionScale * 100);
assert.ok(ds === ImageUtils.Downsample.NONE ||
ds.toString()[0] === '#');
});
});
test('areaAtLeast', function() {
var min = 1 / ImageUtils.Downsample.MAX_AREA_REDUCTION;
scales.forEach(function(scale) {
var ds = ImageUtils.Downsample.areaAtLeast(scale);
assert.operator(ds.areaScale, '<=', round(Math.max(scale, min)));
ds = ImageUtils.Downsample.areaAtLeast(scale * scale);
assert.operator(ds.areaScale, '<=',
round(Math.max(scale * scale, min)));
assert.equal(ds.scale(100), ds.dimensionScale * 100);
assert.ok(ds === ImageUtils.Downsample.NONE ||
ds.toString()[0] === '#');
});
});
test('sizeNoMoreThan', function() {
scales.forEach(function(scale) {
var ds = ImageUtils.Downsample.sizeNoMoreThan(scale);
assert.operator(ds.dimensionScale, '>=', round(Math.min(scale, 1)));
assert.equal(ds.scale(100), ds.dimensionScale * 100);
assert.ok(ds === ImageUtils.Downsample.NONE ||
ds.toString()[0] === '#');
});
});
test('areaNoMoreThan', function() {
scales.forEach(function(scale) {
var ds = ImageUtils.Downsample.areaNoMoreThan(scale);
assert.operator(ds.areaScale, '>=', round(Math.min(scale, 1)));
ds = ImageUtils.Downsample.areaNoMoreThan(scale * scale);
assert.operator(ds.areaScale, '>=', round(Math.min(scale * scale, 1)));
assert.equal(ds.scale(100), ds.dimensionScale * 100);
assert.ok(ds === ImageUtils.Downsample.NONE ||
ds.toString()[0] === '#');
});
});
test('max reduction amounts', function() {
var ds = ImageUtils.Downsample.sizeNoMoreThan(0);
assert.equal(ds.dimensionScale,
round(1 / ImageUtils.Downsample.MAX_SIZE_REDUCTION));
assert.equal(ds.areaScale,
round(1 / ImageUtils.Downsample.MAX_AREA_REDUCTION));
});
});
function runResizeAndCropToCoverTests(imageWidth, imageHeight, imageType,
encoderOptions) {
var suitename = 'resizeAndCropToCover ' + imageType + ' ' +
imageWidth + 'x' + imageHeight;
suite(suitename, function() {
const W = imageWidth, H = imageHeight; // The size of the test image
const inputImageType = imageType;
const encoderOptions = encoderOptions;
suiteSetup(function(done) {
// We begin by creating a special image where each pixel value
// encodes the coordinates of that pixel. This allows us to inspect
// the pixels in the output image to verify that cropping
// was done correctly.
var canvas = document.createElement('canvas');
canvas.width = W;
canvas.height = H;
var context = canvas.getContext('2d');
var imageData = context.createImageData(W, H);
var pixels = imageData.data;
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.height; y++) {
var offset = (x + y * canvas.width) * 4;
pixels[offset] = x; // Encode x coordinate as red value
pixels[offset + 1] = y; // Encode y coodrdinate as green value
imageData.data[offset + 3] = 255; // Make it opaque
}
}
context.putImageData(imageData, 0, 0);
// Create an encoded version of the input image
var self = this;
canvas.toBlob(function(blob) {
self.inputBlob = blob;
done();
}, inputImageType);
});
// Decode the image blob and verify that its size is as expected and
// that its upper-left pixel has red and green values near r0 and g0
// and that its lower-right pixel has values near r1 and g1. We do a
// fuzzy match because jpeg is a lossy format and because sometimes
// we're working with downsampled or upscaled images.
function verifyImage(blob, expectedWidth, expectedHeight,
r0, g0, r1, g1, tolerance, callback) {
var image = new Image();
var url = URL.createObjectURL(blob);
image.src = url;
image.onerror = function() {
callback(Error('failed to load image from blob'));
};
image.onload = function() {
try {
assert.equal(image.width, expectedWidth, 'image width');
assert.equal(image.height, expectedHeight, 'image height');
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
var imageData = context.getImageData(0, 0,
canvas.width, canvas.height);
var pixels = imageData.data;
isNear(pixels[0], r0, 'red component of upper-left');
isNear(pixels[1], g0, 'green component of upper-left');
var offset = 4 * (image.width * image.height - 1);
isNear(pixels[offset], r1,
'red component of lower right');
isNear(pixels[offset + 1], g1,
'green component of lower right');
image.src = '';
URL.revokeObjectURL(url);
canvas.width = 0;
callback();
}
catch (e) {
callback(e);
}
};
// Return true if x is close enough to the target
function isNear(x, target, message) {
if (target - tolerance <= x && x <= target + tolerance) {
return;
}
throw Error(message + ' ' + x + ' is not near ' + target);
}
}
test('test images created', function(done) {
assert.instanceOf(this.inputBlob, Blob, 'got Blob');
assert.equal(this.inputBlob.type, inputImageType);
verifyImage(this.inputBlob, W, H, 0, 0, W - 1, H - 1, 4, done);
});
function onResolve(value) {
throw Error('Must be rejected');
}
test('throw for invalid input image', function(done) {
function checkNotBlob(promise) {
return promise.then(
onResolve,
(err) => { assert.equal(err.name, 'TypeError'); }
);
}
Promise.all([
checkNotBlob(ImageUtils.resizeAndCropToCover(null, 1, 10)),
checkNotBlob(ImageUtils.resizeAndCropToCover('not a blob', 1, 10))
]).then(() => done(), done);
});
test('throw for invalid input dimension', function(done) {
function ensureReject(promise) {
return promise.then(
onResolve,
(err) => { assert.equal(err.message, 'invalid output dimensions'); }
);
}
Promise.all([
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, 0, 10)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 0)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
-10, 10)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
10, -10)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
Infinity, 10)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
10, Infinity)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, {}, 10)),
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, 10, {}))
]).then(() => done(), done);
});
// Verify that if the output size is the same as the input size then
// the output blob is the same as the input blob
test('input returned if no resize needed', function(done) {
var self = this;
ImageUtils.resizeAndCropToCover(this.inputBlob, W, H).then(
function resolve(outputBlob) {
try {
assert.equal(self.inputBlob, outputBlob);
done();
}
catch (ex) {
done(ex);
}
});
});
test('unspecified output type', function(done) {
var self = this;
ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 10)
.then(function resolve(outputBlob) {
assert.equal(outputBlob.type, self.inputBlob.type);
done();
});
});
test('jpeg output type with encondig options', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 10, ImageUtils.JPEG,
encoderOptions)
.then(function resolve(outputBlob) {
assert.equal(outputBlob.type, ImageUtils.JPEG);
done();
});
});
test('png output type', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 10, ImageUtils.PNG)
.then(function resolve(outputBlob) {
assert.equal(outputBlob.type, ImageUtils.PNG);
done();
});
});
test('shrink no crop', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W / 3, H / 3)
.then(function resolve(outputBlob) {
verifyImage(outputBlob, Math.round(W / 3), Math.round(H / 3),
0, 0, W, H, 4, done);
});
});
test('enlarge no crop', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W * 3, H * 3)
.then(function resolve(outputBlob) {
verifyImage(outputBlob, W * 3, H * 3, 0, 0, W, H, 4, done);
});
});
var delta = 100;
test('reduce height', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W, H - delta)
.then(function resolve(outputBlob) {
verifyImage(outputBlob, W, H - delta,
0, delta / 2, W, H - delta / 2, 4, done);
});
});
test('reduce width', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W - delta, H)
.then(function resolve(outputBlob) {
verifyImage(outputBlob, W - delta, H,
delta / 2, 0, W - delta / 2, H, 4, done);
});
});
test('enlarge height', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W, H + delta)
.then(function resolve(outputBlob) {
var ratio = (H + delta) / H;
var margin = ((ratio - 1) * W) / (2 * ratio);
verifyImage(outputBlob, W, H + delta,
margin, 0, W - margin, H,
4, done);
});
});
test('enlarge width', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W + delta, H)
.then(function resolve(outputBlob) {
var ratio = (W + delta) / W;
var margin = ((ratio - 1) * H) / (2 * ratio);
verifyImage(outputBlob, W + delta, H,
0, margin, W, H - margin,
4, done);
});
});
suite('#-moz-samplesize', function() {
var spy;
setup(function() {
spy = this.sinon.spy(ImageUtils.Downsample, 'sizeNoMoreThan');
});
function checkResolve(promise, checkValue) {
return promise.then(
function resolve(outputBlob) {
sinon.assert.calledOnce(spy);
assert.equal(spy.returnValues[0].toString(), checkValue);
},
function reject(outputBlob) {
throw Error('Must be resolved');
}
);
}
if (inputImageType === 'image/jpeg') {
test('uses it at half size', function(done) {
checkResolve(ImageUtils.resizeAndCropToCover(
this.inputBlob, W / 2, H / 2),
'#-moz-samplesize=2').then(done,done);
});
test('uses it at third size', function(done) {
checkResolve(ImageUtils.resizeAndCropToCover(
this.inputBlob, W / 3, H / 3),
'#-moz-samplesize=3').then(done,done);
});
test('uses it at fifth size', function(done) {
checkResolve(ImageUtils.resizeAndCropToCover(
this.inputBlob, W / 5, H / 5),
'#-moz-samplesize=4').then(done,done);
});
test('does not use at three quarters size', function(done) {
checkResolve(ImageUtils.resizeAndCropToCover(
this.inputBlob, W * 0.75, H * 0.75),
'').then(done,done);
});
}
else {
test('does not use for non-jpeg', function(done) {
ImageUtils.resizeAndCropToCover(this.inputBlob, W / 2, H / 2)
.then(() => sinon.assert.notCalled(spy)).then(done, done);
});
}
});
});
}
// We run the resizeAndCropToCover test suite six times for for three
// different input image sizes and two different input image types
([
[240, 180], // 4x3
[180, 240], // 3x4
[250, 250] // square
]).forEach(function(size) {
runResizeAndCropToCoverTests(size[0], size[1], 'image/jpeg', 0.95);
runResizeAndCropToCoverTests(size[0], size[1], 'image/png');
});
});