skia2/modules/canvaskit/tests/util.js
Kevin Lubick b8123cc877 [canvaskit] Clean up readPixels API on Canvas and Image
This makes both APIs have the same arguments with the two
source coordinates first and all the destination params
(image info, optional buffer, optional rowBytes) after.

Bug: skia:10717
Change-Id: I483e4f33f24e226793db6113d5ba5b1955cd892e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/332622
Reviewed-by: Mike Reed <reed@google.com>
2020-11-06 18:22:14 +00:00

221 lines
8.0 KiB
JavaScript

// The size of the golden images (DMs)
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 600;
const _commonGM = (it, pause, name, callback, assetsToFetchOrPromisesToWaitOn) => {
const fetchPromises = [];
for (const assetOrPromise of assetsToFetchOrPromisesToWaitOn) {
// https://stackoverflow.com/a/9436948
if (typeof assetOrPromise === 'string' || assetOrPromise instanceof String) {
const newPromise = fetch(assetOrPromise)
.then((response) => response.arrayBuffer());
fetchPromises.push(newPromise);
} else if (typeof assetOrPromise.then === 'function') {
fetchPromises.push(assetOrPromise);
} else {
throw 'Neither a string nor a promise ' + assetOrPromise;
}
}
it('draws gm '+name, (done) => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface');
if (!surface) {
done();
return;
}
// if fetchPromises is empty, the returned promise will
// resolve right away and just call the callback.
Promise.all(fetchPromises).then((values) => {
try {
// If callback returns a promise, the chained .then
// will wait for it.
return callback(surface.getCanvas(), values);
} catch (e) {
console.log(`gm ${name} failed with error`, e);
expect(e).toBeFalsy();
debugger;
done();
}
}).then(() => {
surface.flush();
if (pause) {
reportSurface(surface, name, null);
} else {
reportSurface(surface, name, done);
}
}).catch((e) => {
console.log(`could not load assets for gm ${name}`, e);
debugger;
done();
});
})
}
/**
* Takes a name, a callback, and any number of assets or promises. It executes the
* callback (presumably, the test) and reports the resulting surface to Gold.
* @param name {string}
* @param callback {Function}, has two params, the first is a CanvasKit.Canvas
* and the second is an array of results from the passed in assets or promises.
* If a given assetOrPromise was a string, the result will be an ArrayBuffer.
* @param assetsToFetchOrPromisesToWaitOn {string|Promise}. If a string, it will
* be treated as a url to fetch and return an ArrayBuffer with the contents as
* a result in the callback. Otherwise, the promise will be waited on and its
* result will be whatever the promise resolves to.
*/
const gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
_commonGM(it, false, name, callback, assetsToFetchOrPromisesToWaitOn);
}
/**
* fgm is like gm, except only tests declared with fgm, force_gm, or fit will be
* executed. This mimics the behavior of Jasmine.js.
*/
const fgm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
_commonGM(fit, false, name, callback, assetsToFetchOrPromisesToWaitOn);
}
/**
* force_gm is like gm, except only tests declared with fgm, force_gm, or fit will be
* executed. This mimics the behavior of Jasmine.js.
*/
const force_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
fgm(name, callback, assetsToFetchOrPromisesToWaitOn);
}
/**
* skip_gm does nothing. It is a convenient way to skip a test temporarily.
*/
const skip_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
console.log(`Skipping gm ${name}`);
// do nothing, skip the test for now
}
/**
* pause_gm is like fgm, except the test will not finish right away and clear,
* making it ideal for a human to manually inspect the results.
*/
const pause_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
_commonGM(fit, true, name, callback, assetsToFetchOrPromisesToWaitOn);
}
const _commonMultipleCanvasGM = (it, pause, name, callback) => {
it(`draws gm ${name} on both CanvasKit and using Canvas2D`, (done) => {
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
skcanvas._config = 'software_canvas';
const realCanvas = document.getElementById('test');
realCanvas._config = 'html_canvas';
realCanvas.width = CANVAS_WIDTH;
realCanvas.height = CANVAS_HEIGHT;
if (pause) {
console.log('debugging canvaskit version');
callback(realCanvas);
callback(skcanvas);
const png = skcanvas.toDataURL();
const img = document.createElement('img');
document.body.appendChild(img);
img.src = png;
debugger;
return;
}
const promises = [];
for (const canvas of [skcanvas, realCanvas]) {
callback(canvas);
// canvas has .toDataURL (even though skcanvas is not a real Canvas)
// so this will work.
promises.push(reportCanvas(canvas, name, canvas._config));
}
Promise.all(promises).then(() => {
skcanvas.dispose();
done();
}).catch(reportError(done));
});
}
/**
* Takes a name and a callback. It executes the callback (presumably, the test)
* for both a CanvasKit.Canvas and a native Canvas2D. The result of both will be
* uploaded to Gold.
* @param name {string}
* @param callback {Function}, has one param, either a CanvasKit.Canvas or a native
* Canvas2D object.
*/
const multipleCanvasGM = (name, callback) => {
_commonMultipleCanvasGM(it, false, name, callback);
}
/**
* fmultipleCanvasGM is like multipleCanvasGM, except only tests declared with
* fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
* mimics the behavior of Jasmine.js.
*/
const fmultipleCanvasGM = (name, callback) => {
_commonMultipleCanvasGM(fit, false, name, callback);
}
/**
* force_multipleCanvasGM is like multipleCanvasGM, except only tests declared
* with fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
* mimics the behavior of Jasmine.js.
*/
const force_multipleCanvasGM = (name, callback) => {
fmultipleCanvasGM(name, callback);
}
/**
* pause_multipleCanvasGM is like fmultipleCanvasGM, except the test will not
* finish right away and clear, making it ideal for a human to manually inspect the results.
*/
const pause_multipleCanvasGM = (name, callback) => {
_commonMultipleCanvasGM(fit, true, name, callback);
}
/**
* skip_multipleCanvasGM does nothing. It is a convenient way to skip a test temporarily.
*/
const skip_multipleCanvasGM = (name, callback) => {
console.log(`Skipping multiple canvas gm ${name}`);
}
function reportSurface(surface, testname, done) {
// In docker, the webgl canvas is blank, but the surface has the pixel
// data. So, we copy it out and draw it to a normal canvas to take a picture.
// To be consistent across CPU and GPU, we just do it for all configurations
// (even though the CPU canvas shows up after flush just fine).
let pixels = surface.getCanvas().readPixels(0, 0, {
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
colorType: CanvasKit.ColorType.RGBA_8888,
alphaType: CanvasKit.AlphaType.Unpremul,
colorSpace: CanvasKit.ColorSpace.SRGB,
});
if (!pixels) {
throw 'Could not get pixels for test '+testname;
}
pixels = new Uint8ClampedArray(pixels.buffer);
const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
const reportingCanvas = document.getElementById('report');
reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
reportCanvas(reportingCanvas, testname).then(() => {
// TODO(kjlubick): should we call surface.delete() here?
done();
}).catch(reportError(done));
}
function starPath(CanvasKit, X=128, Y=128, R=116) {
const p = new CanvasKit.Path();
p.moveTo(X + R, Y);
for (let i = 1; i < 8; i++) {
let a = 2.6927937 * i;
p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
}
p.close();
return p;
}