skia2/modules/canvaskit/tests/core.spec.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

1094 lines
44 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.PictureRecorder();
const rcanvas = spr.beginRecording(
CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT));
const paint = new CanvasKit.Paint();
paint.setStrokeWidth(2.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
rcanvas.drawRRect(CanvasKit.RRectXY([5, 35, 45, 80], 15, 10), paint);
const font = new CanvasKit.Font(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.Paint();
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 Image', (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.ColorSpace.SRGB,
width: img.width(),
height: img.height(),
};
const rowBytes = 4 * img.width();
const pixels = img.readPixels(0, 0, imageInfo);
// 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);
// Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
const rdsData = CanvasKit.Malloc(Uint8Array, 512 * 5*512 * 4);
const pixels2 = rdsData.toTypedArray();
pixels2[0] = 127; // sentinel value, should be overwritten by readPixels.
img.readPixels(0, 0, imageInfo, rdsData, rowBytes);
expect(rdsData.toTypedArray()[0]).toEqual(pixels[0]);
img.delete();
CanvasKit.Free(rdsData);
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);
let img = aImg.makeImageAtCurrentFrame();
canvas.drawImage(img, 0, 0, null);
img.delete(); // This is annoying, so we added drawImageAtCurrentFrame
let c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
canvas.drawImageAtCurrentFrame(aImg, 300, 0, null);
for(let i = 0; i < 10; i++) {
c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
}
canvas.drawImageAtCurrentFrame(aImg, 0, 300, null);
for(let i = 0; i < 10; i++) {
c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
}
canvas.drawImageAtCurrentFrame(aImg, 300, 300, null);
aImg.delete();
}, '/assets/flightAnim.gif');
gm('1x4_from_scratch', (canvas) => {
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.Paint();
// This creates and draws an Image 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.ColorSpace.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.Paint();
paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
const srcs = new CanvasKit.RectBuilder();
// 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.ColorBuilder();
// note that the ColorBuilder 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.Paint();
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 Images 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.Paint();
const textFont = new CanvasKit.Font(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.Paint();
const shader = CanvasKit.Shader.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.Paint();
strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
strokePaint.setColor(CanvasKit.BLACK);
const paint = new CanvasKit.Paint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
const lgs = CanvasKit.Shader.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.Shader.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.Shader.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.Matrix.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.Shader.MakeLinearGradient(
[100, 100], [150, 200], // start and stop points
typedColorsArray,
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.Matrix.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.Paint();
strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
strokePaint.setColor(CanvasKit.BLACK);
const paint = new CanvasKit.Paint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
const rgs = CanvasKit.Shader.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.Shader.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.Shader.MakeRadialGradient(
[50, 150], 50, // center, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.Matrix.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.Shader.MakeRadialGradient(
[150, 150], 50, // center, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.Matrix.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.Paint();
strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
strokePaint.setColor(CanvasKit.BLACK);
const paint = new CanvasKit.Paint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setAntiAlias(true);
const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
const cgs = CanvasKit.Shader.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, // no local matrix
);
paint.setShader(cgs);
let r = CanvasKit.LTRBRect(0, 0, 100, 100);
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
const cgsPremul = CanvasKit.Shader.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.Shader.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.Matrix.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.Shader.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.Matrix.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.Paint();
const textFont = new CanvasKit.Font(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.MaskFilter.MakeBlur(CanvasKit.BlurStyle.Normal, 5, true);
paint.setMaskFilter(blurMask);
canvas.drawPath(pathUL, paint);
const blurIF = CanvasKit.ImageFilter.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.Paint();
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
const redCF = CanvasKit.ColorFilter.MakeBlend(
CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF);
// rotate 10 degrees centered on 200, 200
const m = CanvasKit.Matrix.rotated(Math.PI/18, 200, 200);
const rotated = CanvasKit.ImageFilter.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.Paint();
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
const redCF = CanvasKit.ColorFilter.MakeBlend(
CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
const combined = CanvasKit.ImageFilter.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.MakePicture(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.Paint();
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.Path();
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.Paint();
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.Path();
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.Shader.Color(CanvasKit.Color(255, 0, 0, 1.0)); // deprecated
const gShader = CanvasKit.Shader.MakeColor(CanvasKit.Color(0, 255, 0, 0.6));
const bShader = CanvasKit.Shader.MakeColor(CanvasKit.Color(0, 0, 255, 1.0));
const rgShader = CanvasKit.Shader.MakeBlend(CanvasKit.BlendMode.SrcOver, rShader, gShader);
const p = new CanvasKit.Paint();
p.setShader(rgShader);
canvas.drawPaint(p);
const gbShader = CanvasKit.Shader.MakeLerp(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.Paint();
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);
})
gm('fractal_noise_shader', (canvas) => {
const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 0, 0);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLACK);
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
gm('turbulance_shader', (canvas) => {
const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 0, 0);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLACK);
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
gm('fractal_noise_tiled_shader', (canvas) => {
const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 80, 80);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLACK);
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
gm('turbulance_tiled_shader', (canvas) => {
const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 80, 80);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLACK);
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
gm('improved_noise_shader', (canvas) => {
const shader = CanvasKit.Shader.MakeImprovedNoise(0.1, 0.05, 2, 10);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLACK);
paint.setShader(shader);
canvas.drawPaint(paint);
paint.delete();
shader.delete();
});
describe('ColorSpace Support', () => {
it('Can create an SRGB 8888 surface', () => {
const colorSpace = CanvasKit.ColorSpace.SRGB;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.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.ColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4);
mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels.
const canvas = surface.getCanvas();
canvas.clear(CanvasKit.TRANSPARENT);
const pixels = canvas.readPixels(0, 0, {
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
colorType: CanvasKit.ColorType.RGBA_8888,
alphaType: CanvasKit.AlphaType.Unpremul,
colorSpace: colorSpace
}, mObj, 4 * CANVAS_WIDTH);
expect(pixels).toBeTruthy('Could not read pixels from surface');
expect(pixels[0] !== 127).toBeTruthy();
expect(pixels[0]).toEqual(mObj.toTypedArray()[0]);
CanvasKit.Free(mObj);
surface.delete();
});
it('Can create a Display P3 surface', () => {
const colorSpace = CanvasKit.ColorSpace.DISPLAY_P3;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.DISPLAY_P3);
expect(surface).toBeTruthy('Could not make surface');
if (!surface.reportBackendTypeIsGPU()) {
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.ColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, {
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
colorType: CanvasKit.ColorType.RGBA_F16,
alphaType: CanvasKit.AlphaType.Unpremul,
colorSpace: colorSpace
});
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('Can create an Adobe RGB surface', () => {
const colorSpace = CanvasKit.ColorSpace.ADOBE_RGB;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB);
expect(surface).toBeTruthy('Could not make surface');
if (!surface.reportBackendTypeIsGPU()) {
console.log('Not expecting color space support in cpu backed surface.');
return;
}
let info = surface.imageInfo();
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, {
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
colorType: CanvasKit.ColorType.RGBA_F16,
alphaType: CanvasKit.AlphaType.Unpremul,
colorSpace: colorSpace
});
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('combine draws from several color spaces', () => {
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB);
expect(surface).toBeTruthy('Could not make surface');
if (!surface.reportBackendTypeIsGPU()) {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.ADOBE_RGB);
canvas.drawPaint(paint);
paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.DISPLAY_P3); // 93.7 in adobeRGB
canvas.drawRect(CanvasKit.LTRBRect(200, 0, 400, 600), paint);
paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.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.Paint();
const shader = CanvasKit.Shader.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.Paint();
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}`);
}
}
it('can create a RasterDirectSurface', () => {
// Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
const rdsData = CanvasKit.Malloc(Uint8Array, 5 * 5 * 4);
const surface = CanvasKit.MakeRasterDirectSurface({
'width': 5,
'height': 5,
'colorType': CanvasKit.ColorType.RGBA_8888,
'alphaType': CanvasKit.AlphaType.Premul,
'colorSpace': CanvasKit.ColorSpace.SRGB,
}, rdsData, 5 * 4);
surface.getCanvas().clear(CanvasKit.Color(200, 100, 0, 0.8));
const pixels = rdsData.toTypedArray();
// Check that the first pixels colors are right.
expect(pixels[0]).toEqual(160); // red (premul, 0.8 * 200)
expect(pixels[1]).toEqual(80); // green (premul, 0.8 * 100)
expect(pixels[2]).toEqual(0); // blue (premul, not that it matters)
expect(pixels[3]).toEqual(204); // alpha (0.8 * 255)
surface.delete();
CanvasKit.Free(rdsData);
});
});