skia2/modules/canvaskit/tests/core.spec.js
Kevin Lubick d9b9e5ec4b [canvaskit] High performance Path APIs
https://skia-review.googlesource.com/c/skia/+/297896 draws
several paths with over 1000 points (one has ~250k) and
was pretty slow. We already had a way to provide a flat(ish)
array of all the verbs, points and weights mushed together
(although that should be cleaned up to prefer taking a 1d
array).

This adds an additional way to provide multiple verbs,
points, (and optionally weights, if conics are used) to CanvasKit.
This makes things dramatically faster because of batch copying
the values between JS and WASM (or using Malloc).

Additionally, the above CL revealed a need to be able to
use a subsection of a Malloc'd array efficiently. Thus,
I added subarray as a method of the Malloc object, which
can be used effectively as a pointer (i.e. no copying).

Change-Id: I2c1d26b25118fb9949e878b1b519d93efcfa5019
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297841
Reviewed-by: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Chris Dalton <csmartdalton@google.com>
2020-06-24 12:40:49 +00:00

990 lines
40 KiB
JavaScript

describe('Core canvas behavior', () => {
let container;
beforeEach(async () => {
await LoadCanvasKit;
container = document.createElement('div');
container.innerHTML = `
<canvas width=600 height=600 id=test></canvas>
<canvas width=600 height=600 id=report></canvas>`;
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
});
gm('picture_test', (canvas) => {
const spr = new CanvasKit.SkPictureRecorder();
const rcanvas = spr.beginRecording(
CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT));
const paint = new CanvasKit.SkPaint();
paint.setStrokeWidth(2.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
rcanvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
const font = new CanvasKit.SkFont(null, 20);
rcanvas.drawText('this picture has a round rect', 5, 100, paint, font);
const pic = spr.finishRecordingAsPicture();
spr.delete();
paint.delete();
canvas.drawPicture(pic);
// test that file saving functionality throws no errors
// Unfortunately jasmine spy objects can't fake their type so we can't verify it downloads
// a nonzero sized file.
pic.saveAsFile('foo.skp');
pic.delete();
});
const uIntColorToCanvasKitColor = (c) => {
return CanvasKit.Color(
(c >> 16) & 0xFF,
(c >> 8) & 0xFF,
(c >> 0) & 0xFF,
((c >> 24) & 0xFF) / 255
);
}
it('can compute tonal colors', () => {
const input = {
ambient: CanvasKit.BLUE,
spot: CanvasKit.RED,
};
const out = CanvasKit.computeTonalColors(input);
expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK);
const expectedSpot = [0.173, 0, 0, 0.969];
expect(out.spot.length).toEqual(4);
expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3);
expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3);
expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3);
expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3);
});
it('can compute tonal colors with malloced values', () => {
const ambientColor = CanvasKit.Malloc(Float32Array, 4);
ambientColor.toTypedArray().set(CanvasKit.BLUE);
const spotColor = CanvasKit.Malloc(Float32Array, 4);
spotColor.toTypedArray().set(CanvasKit.RED);
const input = {
ambient: ambientColor,
spot: spotColor,
};
const out = CanvasKit.computeTonalColors(input);
expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK);
const expectedSpot = [0.173, 0, 0, 0.969];
expect(out.spot.length).toEqual(4);
expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3);
expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3);
expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3);
expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3);
});
// This helper is used for all the MakeImageFromEncoded tests.
// TODO(kjlubick): rewrite this and callers to use gm
function decodeAndDrawSingleFrameImage(imgName, goldName, done) {
const imgPromise = fetch(imgName)
.then((response) => response.arrayBuffer());
Promise.all([imgPromise, LoadCanvasKit]).then((values) => {
const imgData = values[0];
expect(imgData).toBeTruthy();
catchException(done, () => {
let img = CanvasKit.MakeImageFromEncoded(imgData);
expect(img).toBeTruthy();
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface');
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.SkPaint();
canvas.drawImage(img, 0, 0, paint);
paint.delete();
img.delete();
reportSurface(surface, goldName, done);
})();
});
}
it('can decode and draw a png', (done) => {
decodeAndDrawSingleFrameImage('/assets/mandrill_512.png', 'drawImage_png', done);
});
it('can decode and draw a jpg', (done) => {
decodeAndDrawSingleFrameImage('/assets/mandrill_h1v1.jpg', 'drawImage_jpg', done);
});
it('can decode and draw a (still) gif', (done) => {
decodeAndDrawSingleFrameImage('/assets/flightAnim.gif', 'drawImage_gif', done);
});
it('can decode and draw a still webp', (done) => {
decodeAndDrawSingleFrameImage('/assets/color_wheel.webp', 'drawImage_webp', done);
});
it('can readPixels from an SkImage', (done) => {
const imgPromise = fetch('/assets/mandrill_512.png')
.then((response) => response.arrayBuffer());
Promise.all([imgPromise, LoadCanvasKit]).then((values) => {
const imgData = values[0];
expect(imgData).toBeTruthy();
catchException(done, () => {
let img = CanvasKit.MakeImageFromEncoded(imgData);
expect(img).toBeTruthy();
const imageInfo = {
alphaType: CanvasKit.AlphaType.Unpremul,
colorType: CanvasKit.ColorType.RGBA_8888,
colorSpace: CanvasKit.SkColorSpace.SRGB,
width: img.width(),
height: img.height(),
};
const pixels = img.readPixels(imageInfo, 0, 0);
// We know the image is 512 by 512 pixels in size, each pixel
// requires 4 bytes (R, G, B, A).
expect(pixels.length).toEqual(512 * 512 * 4);
img.delete();
done();
})();
});
});
gm('drawDrawable_animated_gif', (canvas, fetchedByteBuffers) => {
let aImg = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]);
expect(aImg).toBeTruthy();
expect(aImg.getRepetitionCount()).toEqual(-1); // infinite loop
expect(aImg.width()).toEqual(320);
expect(aImg.height()).toEqual(240);
expect(aImg.getFrameCount()).toEqual(60);
// TODO(kjlubick): deprecate drawAnimatedImage and have it just snap off
// an SkImage at the desired frame.
canvas.drawAnimatedImage(aImg, 0, 0);
let c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
canvas.drawAnimatedImage(aImg, 300, 0);
for(let i = 0; i < 10; i++) {
c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
}
canvas.drawAnimatedImage(aImg, 0, 300);
for(let i = 0; i < 10; i++) {
c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
}
canvas.drawAnimatedImage(aImg, 300, 300);
aImg.delete();
}, '/assets/flightAnim.gif');
gm('1x4_from_scratch', (canvas) => {
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.SkPaint();
// This creates and draws an SkImage that is 1 pixel wide, 4 pixels tall with
// the colors listed below.
const pixels = Uint8Array.from([
255, 0, 0, 255, // opaque red
0, 255, 0, 255, // opaque green
0, 0, 255, 255, // opaque blue
255, 0, 255, 100, // transparent purple
]);
const img = CanvasKit.MakeImage(pixels, 1, 4, CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888,
CanvasKit.SkColorSpace.SRGB);
canvas.drawImage(img, 1, 1, paint);
img.delete();
paint.delete();
});
gm('draw_atlas_with_builders', (canvas, fetchedByteBuffers) => {
const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(atlas).toBeTruthy();
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
const srcs = new CanvasKit.SkRectBuilder();
// left top right bottom
srcs.push( 0, 0, 256, 256);
srcs.push(256, 0, 512, 256);
srcs.push( 0, 256, 256, 512);
srcs.push(256, 256, 512, 512);
const dsts = new CanvasKit.RSXFormBuilder();
// scos, ssin, tx, ty
dsts.push(0.5, 0, 20, 20);
dsts.push(0.5, 0, 300, 20);
dsts.push(0.5, 0, 20, 300);
dsts.push(0.5, 0, 300, 300);
const colors = new CanvasKit.SkColorBuilder();
// note that the SkColorBuilder expects int colors to be pushed.
// pushing float colors to it only causes weird problems way downstream.
// It does no type checking.
colors.push(CanvasKit.ColorAsInt( 85, 170, 10, 128)); // light green
colors.push(CanvasKit.ColorAsInt( 51, 51, 191, 128)); // light blue
colors.push(CanvasKit.ColorAsInt( 0, 0, 0, 128));
colors.push(CanvasKit.ColorAsInt(256, 256, 256, 128));
canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors);
atlas.delete();
paint.delete();
}, '/assets/mandrill_512.png')
gm('draw_atlas_with_arrays', (canvas, fetchedByteBuffers) => {
const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(atlas).toBeTruthy();
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
const srcs = [
0, 0, 256, 256,
256, 0, 512, 256,
0, 256, 256, 512,
256, 256, 512, 512,
];
const dsts = [
0.5, 0, 20, 20,
0.5, 0, 300, 20,
0.5, 0, 20, 300,
0.5, 0, 300, 300,
];
const colors = Uint32Array.of(
CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green
CanvasKit.ColorAsInt( 51, 51, 191, 128), // light blue
CanvasKit.ColorAsInt( 0, 0, 0, 128),
CanvasKit.ColorAsInt(256, 256, 256, 128),
);
canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors);
atlas.delete();
paint.delete();
}, '/assets/mandrill_512.png');
gm('image_decoding_methods', async (canvas) => {
canvas.clear(CanvasKit.WHITE);
const IMAGE_FILE_PATHS = [
'/assets/brickwork-texture.jpg',
'/assets/mandrill_512.png',
'/assets/color_wheel.gif'
];
let row = 1;
// Test 4 different methods of decoding an image for each of the three images in
// IMAGE_FILE_PATHS.
// Resulting SkImages are drawn to visually show that all methods decode correctly.
for (const imageFilePath of IMAGE_FILE_PATHS) {
const response = await fetch(imageFilePath);
const arrayBuffer = await response.arrayBuffer();
// response.blob() is preferable when you don't need both a Blob *and* an ArrayBuffer.
const blob = new Blob([ arrayBuffer ]);
// Method 1 - decode TypedArray using wasm codecs:
const skImage1 = CanvasKit.MakeImageFromEncoded(arrayBuffer);
// Method 2 (slower and does not work in Safari) decode using ImageBitmap:
const imageBitmap = await createImageBitmap(blob);
// Testing showed that transferring an ImageBitmap to a canvas using the 'bitmaprenderer'
// context and passing that canvas to CanvasKit.MakeImageFromCanvasImageSource() is
// marginally faster than passing ImageBitmap to
// CanvasKit.MakeImageFromCanvasImageSource() directly.
const canvasBitmapElement = document.createElement('canvas');
canvasBitmapElement.width = imageBitmap.width;
canvasBitmapElement.height = imageBitmap.height;
const ctxBitmap = canvasBitmapElement.getContext('bitmaprenderer');
ctxBitmap.transferFromImageBitmap(imageBitmap);
const skImage2 = CanvasKit.MakeImageFromCanvasImageSource(canvasBitmapElement);
// Method 3 (slowest) decode using HTMLImageElement directly:
const image = new Image();
// Testing showed that waiting for a load event is faster than waiting on image.decode()
// HTMLImageElement.decode() reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
const promise1 = new Promise((resolve) => image.addEventListener('load', resolve));
image.src = imageFilePath;
await promise1;
const skImage3 = CanvasKit.MakeImageFromCanvasImageSource(image);
// Method 4 (roundabout, but works if all you have is a Blob) decode from Blob using
// HTMLImageElement:
const imageObjectUrl = URL.createObjectURL( blob );
const image2 = new Image();
const promise2 = new Promise((resolve) => image2.addEventListener('load', resolve));
image2.src = imageObjectUrl;
await promise2;
const skImage4 = CanvasKit.MakeImageFromCanvasImageSource(image2);
// Draw decoded images
const sourceRect = CanvasKit.XYWHRect(0,0, 150, 150);
canvas.drawImageRect(skImage1, sourceRect, CanvasKit.XYWHRect(0,row * 100, 90, 90), null, false);
canvas.drawImageRect(skImage2, sourceRect, CanvasKit.XYWHRect(100,row * 100, 90, 90), null, false);
canvas.drawImageRect(skImage3, sourceRect, CanvasKit.XYWHRect(200,row * 100, 90, 90), null, false);
canvas.drawImageRect(skImage4, sourceRect, CanvasKit.XYWHRect(300,row * 100, 90, 90), null, false);
row++;
}
//Label images with the method used to decode them
const paint = new CanvasKit.SkPaint();
const textFont = new CanvasKit.SkFont(null, 7);
canvas.drawText('WASM Decoding', 0, 90, paint, textFont);
canvas.drawText('ImageBitmap Decoding', 100, 90, paint, textFont);
canvas.drawText('HTMLImageEl Decoding', 200, 90, paint, textFont);
canvas.drawText('Blob Decoding', 300, 90, paint, textFont);
});
gm('sweep_gradient', (canvas) => {
const paint = new CanvasKit.SkPaint();
const shader = CanvasKit.SkShader.MakeSweepGradient(
100, 100, // X, Y coordinates
[CanvasKit.GREEN, CanvasKit.BLUE],
[0.0, 1.0],
CanvasKit.TileMode.Clamp,
);
expect(shader).toBeTruthy('Could not make shader');
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
// TODO(kjlubick): There's a lot of shared code between the gradient gms
// It would be best to deduplicate that in a nice DAMP way.
// Inspired by https://fiddle.skia.org/c/b29ce50a341510784ac7d5281586d076
gm('linear_gradients', (canvas) => {
canvas.clear(CanvasKit.WHITE);
canvas.scale(2, 2);
const strokePaint = new CanvasKit.SkPaint();
strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
strokePaint.setColor(CanvasKit.BLACK);
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
const lgs = CanvasKit.SkShader.MakeLinearGradient(
[0, 0], [50, 100], // start and stop points
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror
);
paint.setShader(lgs);
let r = CanvasKit.LTRBRect(0, 0, 100, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const lgsPremul = CanvasKit.SkShader.MakeLinearGradient(
[100, 0], [150, 100], // start and stop points
Uint32Array.of(
CanvasKit.ColorAsInt(0, 255, 255, 0),
CanvasKit.ColorAsInt(0, 0, 255, 255),
CanvasKit.ColorAsInt(255, 0, 0, 255)),
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
null, // no local matrix
1 // interpolate colors in premul
);
paint.setShader(lgsPremul);
r = CanvasKit.LTRBRect(100, 0, 200, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const lgs45 = CanvasKit.SkShader.MakeLinearGradient(
[0, 100], [50, 200], // start and stop points
Float32Array.of(...transparentGreen, ...CanvasKit.BLUE, ...CanvasKit.RED),
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 0, 100),
);
paint.setShader(lgs45);
r = CanvasKit.LTRBRect(0, 100, 100, 200);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
// malloc'd color array
const colors = CanvasKit.Malloc(Float32Array, 12);
const typedColorsArray = colors.toTypedArray();
typedColorsArray.set(transparentGreen, 0);
typedColorsArray.set(CanvasKit.BLUE, 4);
typedColorsArray.set(CanvasKit.RED, 8);
const lgs45Premul = CanvasKit.SkShader.MakeLinearGradient(
[100, 100], [150, 200], // start and stop points
typedColorsArray,
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 100, 100),
1 // interpolate colors in premul
);
CanvasKit.Free(colors);
paint.setShader(lgs45Premul);
r = CanvasKit.LTRBRect(100, 100, 200, 200);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
lgs.delete();
lgs45.delete();
lgsPremul.delete();
lgs45Premul.delete();
strokePaint.delete();
paint.delete();
});
gm('radial_gradients', (canvas) => {
canvas.clear(CanvasKit.WHITE);
canvas.scale(2, 2);
const strokePaint = new CanvasKit.SkPaint();
strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
strokePaint.setColor(CanvasKit.BLACK);
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
const rgs = CanvasKit.SkShader.MakeRadialGradient(
[50, 50], 50, // center, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror
);
paint.setShader(rgs);
let r = CanvasKit.LTRBRect(0, 0, 100, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const rgsPremul = CanvasKit.SkShader.MakeRadialGradient(
[150, 50], 50, // center, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
null, // no local matrix
1 // interpolate colors in premul
);
paint.setShader(rgsPremul);
r = CanvasKit.LTRBRect(100, 0, 200, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const rgsSkew = CanvasKit.SkShader.MakeRadialGradient(
[50, 150], 50, // center, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.skewed(0.5, 0, 100, 100),
null, // color space
);
paint.setShader(rgsSkew);
r = CanvasKit.LTRBRect(0, 100, 100, 200);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const rgsSkewPremul = CanvasKit.SkShader.MakeRadialGradient(
[150, 150], 50, // center, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.skewed(0.5, 0, 100, 100),
1, // interpolate colors in premul
null, // color space
);
paint.setShader(rgsSkewPremul);
r = CanvasKit.LTRBRect(100, 100, 200, 200);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
rgs.delete();
rgsPremul.delete();
rgsSkew.delete();
rgsSkewPremul.delete();
strokePaint.delete();
paint.delete();
});
gm('conical_gradients', (canvas) => {
canvas.clear(CanvasKit.WHITE);
canvas.scale(2, 2);
const strokePaint = new CanvasKit.SkPaint();
strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
strokePaint.setColor(CanvasKit.BLACK);
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setAntiAlias(true);
const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
const cgs = CanvasKit.SkShader.MakeTwoPointConicalGradient(
[80, 10], 15, // start, radius
[10, 110], 60, // end, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
null, // color space
);
paint.setShader(cgs);
let r = CanvasKit.LTRBRect(0, 0, 100, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const cgsPremul = CanvasKit.SkShader.MakeTwoPointConicalGradient(
[180, 10], 15, // start, radius
[110, 110], 60, // end, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
null, // no local matrix
1, // interpolate colors in premul
null, // color space
);
paint.setShader(cgsPremul);
r = CanvasKit.LTRBRect(100, 0, 200, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const cgs45 = CanvasKit.SkShader.MakeTwoPointConicalGradient(
[80, 110], 15, // start, radius
[10, 210], 60, // end, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 0, 100),
null, // color space
);
paint.setShader(cgs45);
r = CanvasKit.LTRBRect(0, 100, 100, 200);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const cgs45Premul = CanvasKit.SkShader.MakeTwoPointConicalGradient(
[180, 110], 15, // start, radius
[110, 210], 60, // end, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 100, 100),
1, // interpolate colors in premul
null, // color space
);
paint.setShader(cgs45Premul);
r = CanvasKit.LTRBRect(100, 100, 200, 200);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
cgs.delete();
cgsPremul.delete();
cgs45.delete();
strokePaint.delete();
paint.delete();
});
gm('blur_filters', (canvas) => {
const pathUL = starPath(CanvasKit, 100, 100, 80);
const pathBR = starPath(CanvasKit, 400, 300, 80);
const paint = new CanvasKit.SkPaint();
const textFont = new CanvasKit.SkFont(null, 24);
canvas.drawText('Above: MaskFilter', 20, 220, paint, textFont);
canvas.drawText('Right: ImageFilter', 20, 260, paint, textFont);
paint.setColor(CanvasKit.BLUE);
const blurMask = CanvasKit.SkMaskFilter.MakeBlur(CanvasKit.BlurStyle.Normal, 5, true);
paint.setMaskFilter(blurMask);
canvas.drawPath(pathUL, paint);
const blurIF = CanvasKit.SkImageFilter.MakeBlur(8, 1, CanvasKit.TileMode.Decal, null);
paint.setImageFilter(blurIF);
canvas.drawPath(pathBR, paint);
pathUL.delete();
pathBR.delete();
paint.delete();
blurMask.delete();
blurIF.delete();
textFont.delete();
});
gm('combined_filters', (canvas, fetchedByteBuffers) => {
const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(img).toBeTruthy();
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
const redCF = CanvasKit.SkColorFilter.MakeBlend(
CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
const redIF = CanvasKit.SkImageFilter.MakeColorFilter(redCF, null);
const blurIF = CanvasKit.SkImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
const combined = CanvasKit.SkImageFilter.MakeCompose(redIF, blurIF);
// rotate 10 degrees centered on 200, 200
const m = CanvasKit.SkMatrix.rotated(Math.PI/18, 200, 200);
const rotated = CanvasKit.SkImageFilter.MakeMatrixTransform(m, CanvasKit.FilterQuality.Medium, combined);
paint.setImageFilter(rotated);
//canvas.rotate(10, 200, 200);
canvas.drawImage(img, 0, 0, paint);
canvas.drawRect(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
paint.delete();
redIF.delete();
redCF.delete();
blurIF.delete();
combined.delete();
rotated.delete();
img.delete();
}, '/assets/mandrill_512.png');
gm('animated_filters', (canvas, fetchedByteBuffers) => {
const img = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]);
expect(img).toBeTruthy();
img.decodeNextFrame();
img.decodeNextFrame();
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
const redCF = CanvasKit.SkColorFilter.MakeBlend(
CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
const redIF = CanvasKit.SkImageFilter.MakeColorFilter(redCF, null);
const blurIF = CanvasKit.SkImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
const combined = CanvasKit.SkImageFilter.MakeCompose(redIF, blurIF);
paint.setImageFilter(combined);
const frame = img.getCurrentFrame();
canvas.drawImage(frame, 100, 50, paint);
paint.delete();
redIF.delete();
redCF.delete();
blurIF.delete();
combined.delete();
frame.delete();
img.delete();
}, '/assets/flightAnim.gif');
gm('drawImage_skp', (canvas, fetchedByteBuffers) => {
const pic = CanvasKit.MakeSkPicture(fetchedByteBuffers[0]);
expect(pic).toBeTruthy();
canvas.clear(CanvasKit.TRANSPARENT);
canvas.drawPicture(pic);
}, '/assets/red_line.skp');
it('can draw once using drawOnce utility method', (done) => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface');
if (!surface) {
done();
return;
}
const drawFrame = (canvas) => {
const paint = new CanvasKit.SkPaint();
paint.setStrokeWidth(1.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const path = new CanvasKit.SkPath();
path.moveTo(20, 5);
path.lineTo(30, 20);
path.lineTo(40, 10);
canvas.drawPath(path, paint);
path.delete();
paint.delete();
// surface hasn't been flushed yet (nor should we call flush
// ourselves), so reportSurface would likely be blank if we
// were to call it.
done();
}
surface.drawOnce(drawFrame);
// Reminder: drawOnce is async. In this test, we are just making
// sure the drawOnce function is there and doesn't crash, so we can
// just call done() when the frame is rendered.
});
it('can draw client-supplied dirty rects', (done) => {
// dirty rects are only honored by software (CPU) canvases today.
const surface = CanvasKit.MakeSWCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface');
if (!surface) {
done();
return;
}
const drawFrame = (canvas) => {
const paint = new CanvasKit.SkPaint();
paint.setStrokeWidth(1.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const path = new CanvasKit.SkPath();
path.moveTo(20, 5);
path.lineTo(30, 20);
path.lineTo(40, 10);
canvas.drawPath(path, paint);
path.delete();
paint.delete();
done();
}
const dirtyRect = CanvasKit.XYWHRect(10, 10, 15, 15);
surface.drawOnce(drawFrame, dirtyRect);
// We simply ensure that passing a dirty rect doesn't crash.
});
it('can use DecodeCache APIs', () => {
const initialLimit = CanvasKit.getDecodeCacheLimitBytes();
expect(initialLimit).toBeGreaterThan(1024 * 1024);
const newLimit = 42 * 1024 * 1024;
CanvasKit.setDecodeCacheLimitBytes(newLimit);
expect(CanvasKit.getDecodeCacheLimitBytes()).toEqual(newLimit);
// We cannot make any assumptions about this value,
// so we just make sure it doesn't crash.
CanvasKit.getDecodeCacheUsedBytes();
});
gm('combined_shaders', (canvas) => {
const rShader = CanvasKit.SkShader.Color(CanvasKit.Color(255, 0, 0, 1.0));
const gShader = CanvasKit.SkShader.Color(CanvasKit.Color(0, 255, 0, 0.6));
const bShader = CanvasKit.SkShader.Color(CanvasKit.Color(0, 0, 255, 1.0));
const rgShader = CanvasKit.SkShader.Blend(CanvasKit.BlendMode.SrcOver, rShader, gShader);
const p = new CanvasKit.SkPaint();
p.setShader(rgShader);
canvas.drawPaint(p);
const gbShader = CanvasKit.SkShader.Lerp(0.5, gShader, bShader);
p.setShader(gbShader);
canvas.drawRect(CanvasKit.LTRBRect(5, 100, 300, 400), p);
rShader.delete();
gShader.delete();
bShader.delete();
rgShader.delete();
gbShader.delete();
p.delete();
});
it('exports consts correctly', () => {
expect(CanvasKit.TRANSPARENT).toEqual(Float32Array.of(0, 0, 0, 0));
expect(CanvasKit.RED).toEqual(Float32Array.of(1, 0, 0, 1));
expect(CanvasKit.QUAD_VERB).toEqual(2);
expect(CanvasKit.CONIC_VERB).toEqual(3);
expect(CanvasKit.SaveLayerInitWithPrevious).toEqual(4);
expect(CanvasKit.SaveLayerF16ColorType).toEqual(16);
});
it('can set color on a paint and get it as four floats', () => {
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color4f(3.3, 2.2, 1.1, 0.5));
expect(paint.getColor()).toEqual(Float32Array.of(3.3, 2.2, 1.1, 0.5));
paint.setColorComponents(0.5, 0.6, 0.7, 0.8);
expect(paint.getColor()).toEqual(Float32Array.of(0.5, 0.6, 0.7, 0.8));
paint.setColorInt(CanvasKit.ColorAsInt(50, 100, 150, 200));
let color = paint.getColor();
expect(color.length).toEqual(4);
expect(color[0]).toBeCloseTo(50/255, 5); // Red
expect(color[1]).toBeCloseTo(100/255, 5); // Green
expect(color[2]).toBeCloseTo(150/255, 5); // Blue
expect(color[3]).toBeCloseTo(200/255, 5); // Alpha
paint.setColorInt(0xFF000000);
expect(paint.getColor()).toEqual(Float32Array.of(0, 0, 0, 1.0));
});
gm('draw shadow', (canvas) => {
const lightRadius = 20;
const flags = 0;
const lightPos = [500,500,20];
const zPlaneParams = [0,0,1];
const path = starPath(CanvasKit);
canvas.drawShadow(path, zPlaneParams, lightPos, lightRadius,
CanvasKit.BLACK, CanvasKit.MAGENTA, flags);
})
describe('ColorSpace Support', () => {
it('Can create an SRGB 8888 surface', () => {
const colorSpace = CanvasKit.SkColorSpace.SRGB;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.SRGB);
expect(surface).toBeTruthy('Could not make surface');
let info = surface.imageInfo()
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888);
expect(CanvasKit.SkColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888, colorSpace);
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('Can create a Display P3 surface', () => {
const colorSpace = CanvasKit.SkColorSpace.DISPLAY_P3;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.DISPLAY_P3);
expect(surface).toBeTruthy('Could not make surface');
if (surface.reportBackendType() !== 'GPU') {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
let info = surface.imageInfo()
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
expect(CanvasKit.SkColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_F16, colorSpace);
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('Can create an Adobe RGB surface', () => {
const colorSpace = CanvasKit.SkColorSpace.ADOBE_RGB;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.ADOBE_RGB);
expect(surface).toBeTruthy('Could not make surface');
if (surface.reportBackendType() !== 'GPU') {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
let info = surface.imageInfo()
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
expect(CanvasKit.SkColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_F16, colorSpace);
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('combine draws from several color spaces', () => {
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.ADOBE_RGB);
expect(surface).toBeTruthy('Could not make surface');
if (surface.reportBackendType() !== 'GPU') {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.RED, CanvasKit.SkColorSpace.ADOBE_RGB);
canvas.drawPaint(paint);
paint.setColor(CanvasKit.RED, CanvasKit.SkColorSpace.DISPLAY_P3); // 93.7 in adobeRGB
canvas.drawRect(CanvasKit.LTRBRect(200, 0, 400, 600), paint);
paint.setColor(CanvasKit.RED, CanvasKit.SkColorSpace.SRGB); // 85.9 in adobeRGB
canvas.drawRect(CanvasKit.LTRBRect(400, 0, 600, 600), paint);
// this test paints three bands of red, each the maximum red that a color space supports.
// They are each represented by skia by some color in the Adobe RGB space of the surface,
// as floats between 0 and 1.
// TODO(nifong) readpixels and verify correctness after f32 readpixels bug is fixed
});
}); // end describe('ColorSpace Support')
describe('DOMMatrix support', () => {
gm('sweep_gradient_dommatrix', (canvas) => {
const paint = new CanvasKit.SkPaint();
const shader = CanvasKit.SkShader.MakeSweepGradient(
100, 100, // x y coordinates
[CanvasKit.GREEN, CanvasKit.BLUE],
[0.0, 1.0],
CanvasKit.TileMode.Clamp,
new DOMMatrix().translate(-10, 100),
);
expect(shader).toBeTruthy('Could not make shader');
if (!shader) {
return;
}
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
const radiansToDegrees = (rad) => {
return (rad / Math.PI) * 180;
}
// this should draw the same as concat_with4x4_canvas
gm('concat_dommatrix', (canvas) => {
const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
canvas.clear(CanvasKit.WHITE);
canvas.concat(new DOMMatrix().translate(CANVAS_WIDTH/2, 0, 0));
canvas.concat(new DOMMatrix().rotateAxisAngle(1, 0, 0, radiansToDegrees(Math.PI/3)));
canvas.concat(new DOMMatrix().rotateAxisAngle(0, 1, 0, radiansToDegrees(Math.PI/4)));
canvas.concat(new DOMMatrix().rotateAxisAngle(0, 0, 1, radiansToDegrees(Math.PI/16)));
canvas.concat(new DOMMatrix().translate(-CANVAS_WIDTH/2, 0, 0));
const localMatrix = canvas.getLocalToDevice();
expect4x4MatricesToMatch([
0.693519, -0.137949, 0.707106, 91.944030,
0.698150, 0.370924, -0.612372, -209.445297,
-0.177806, 0.918359, 0.353553, 53.342029,
0 , 0 , 0 , 1 ], localMatrix);
// Draw some stripes to help the eye detect the turn
const stripeWidth = 10;
paint.setColor(CanvasKit.BLACK);
for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
}
paint.setColor(CanvasKit.YELLOW);
canvas.drawPath(path, paint);
paint.delete();
path.delete();
});
}); // end describe('DOMMatrix support')
it('can call subarray on a Malloced object', () => {
const mThings = CanvasKit.Malloc(Float32Array, 6);
mThings.toTypedArray().set([4, 5, 6, 7, 8, 9]);
expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.toTypedArray());
expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.subarray(0));
expectTypedArraysToEqual(Float32Array.of(7, 8, 9), mThings.subarray(3));
expectTypedArraysToEqual(Float32Array.of(7), mThings.subarray(3, 4));
expectTypedArraysToEqual(Float32Array.of(7, 8), mThings.subarray(3, 5));
// mutations on the subarray affect the entire array (because they are backed by the
// same memory)
mThings.subarray(3)[0] = 100.5;
expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 100.5, 8, 9), mThings.toTypedArray());
CanvasKit.Free(mThings);
});
function expectTypedArraysToEqual(expected, actual) {
expect(expected.constructor.name).toEqual(actual.constructor.name);
expect(expected.length).toEqual(actual.length);
for (let i = 0; i < expected.length; i++) {
expect(expected[i]).toBeCloseTo(actual[i], 5, `element ${i}`);
}
}
});