blob: 9b95b75c3e3c6c42181f37f9da0f1d54203cf793 [file] [log] [blame]
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 bounds = CanvasKit.LTRBRect(0, 0, 400, 120);
const rcanvas = spr.beginRecording(bounds);
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);
const paint2 = new CanvasKit.Paint();
paint2.setColor(CanvasKit.RED);
paint2.setStyle(CanvasKit.PaintStyle.Stroke);
canvas.drawRect(bounds, paint2);
const bytes = pic.serialize();
expect(bytes).toBeTruthy();
const matr = CanvasKit.Matrix.scaled(0.33, 0.33);
// Give a 5 pixel margin between the original content.
const tileRect = CanvasKit.LTRBRect(-5, -5, 405, 125);
const shader = pic.makeShader(CanvasKit.TileMode.Mirror, CanvasKit.TileMode.Mirror,
CanvasKit.FilterMode.Linear, matr, tileRect);
paint2.setStyle(CanvasKit.PaintStyle.Fill);
paint2.setShader(shader);
canvas.drawRect(CanvasKit.LTRBRect(0, 150, CANVAS_WIDTH, CANVAS_HEIGHT), paint2);
paint2.delete();
shader.delete();
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);
expect(aImg.currentFrameDuration()).toEqual(60);
const drawCurrentFrame = function(x, y) {
let img = aImg.makeImageAtCurrentFrame();
canvas.drawImage(img, x, y, null);
img.delete();
}
drawCurrentFrame(0, 0);
let c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
drawCurrentFrame(300, 0);
for(let i = 0; i < 10; i++) {
c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
}
drawCurrentFrame(0, 300);
for(let i = 0; i < 10; i++) {
c = aImg.decodeNextFrame();
expect(c).not.toEqual(-1);
}
drawCurrentFrame(300, 300);
aImg.delete();
}, '/assets/flightAnim.gif');
gm('exif_orientation', (canvas, fetchedByteBuffers) => {
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.Paint();
const font = new CanvasKit.Font(null, 14);
canvas.drawText('The following heart should be rotated 90 CCW due to exif.',
5, 25, paint, font);
// TODO(kjlubick) it would be nice to also to test MakeAnimatedImageFromEncoded but
// I could not create a sample animated image that worked.
const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(img).toBeTruthy();
canvas.drawImage(img, 5, 35, null);
img.delete();
paint.delete();
font.delete();
}, '/assets/exif_rotated_heart.jpg');
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({
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
'colorSpace': CanvasKit.ColorSpace.SRGB
}, pixels, 4);
canvas.drawImage(img, 1, 1, paint);
const info = img.getImageInfo();
expect(info).toEqual({
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
const cs = img.getColorSpace();
expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy();
cs.delete();
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));
// Allocate space for 4 rectangles.
const srcs = CanvasKit.Malloc(Float32Array, 16);
srcs.toTypedArray().set([
0, 0, 256, 256, // LTRB
256, 0, 512, 256,
0, 256, 256, 512,
256, 256, 512, 512
]);
// Allocate space for 4 RSXForms.
const dsts = CanvasKit.Malloc(Float32Array, 16);
dsts.toTypedArray().set([
0.5, 0, 20, 20, // scos, ssin, tx, ty
0.5, 0, 300, 20,
0.5, 0, 20, 300,
0.5, 0, 300, 300
]);
// Allocate space for 4 colors.
const colors = new CanvasKit.Malloc(Uint32Array, 4);
colors.toTypedArray().set([
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();
CanvasKit.Free(srcs);
CanvasKit.Free(dsts);
CanvasKit.Free(colors);
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, 8, 8,
8, 0, 16, 8,
0, 8, 8, 16,
8, 8, 16, 16,
];
const dsts = [
10, 0, 0, 0,
10, 0, 100, 0,
10, 0, 0, 100,
10, 0, 100, 100,
];
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(255, 255, 255, 128),
);
// sampling for each of the 4 instances
const sampling = [
null,
{B: 0, C: 0.5},
{filter: CanvasKit.FilterMode.Nearest, mipmap: CanvasKit.MipmapMode.None},
{filter: CanvasKit.FilterMode.Linear, mipmap: CanvasKit.MipmapMode.Nearest},
];
// positioning for each of the 4 instances
const offset = [
[0, 0], [256, 0], [0, 256], [256, 256]
];
canvas.translate(20, 20);
for (let i = 0; i < 4; ++i) {
canvas.save();
canvas.translate(offset[i][0], offset[i][1]);
canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.SrcOver, colors,
sampling[i]);
canvas.restore();
}
atlas.delete();
paint.delete();
}, '/assets/mandrill_16.png');
gm('draw_patch', (canvas, fetchedByteBuffers) => {
const image = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(image).toBeTruthy();
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.Paint();
const shader = image.makeShaderOptions(CanvasKit.TileMode.Clamp,
CanvasKit.TileMode.Clamp,
CanvasKit.FilterMode.Linear,
CanvasKit.MipmapMode.None);
const cubics = [0,0, 80,50, 160,50,
240,0, 200,80, 200,160,
240,240, 160,160, 80,240,
0,240, 50,160, 0,80];
const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN];
const texs = [0,0, 16,0, 16,16, 0,16];
const params = [
[ 0, 0, colors, null, null, CanvasKit.BlendMode.Dst],
[256, 0, null, texs, shader, null],
[ 0, 256, colors, texs, shader, null],
[256, 256, colors, texs, shader, CanvasKit.BlendMode.Screen],
];
for (const p of params) {
paint.setShader(p[4]);
canvas.save();
canvas.translate(p[0], p[1]);
canvas.drawPatch(cubics, p[2], p[3], p[5], paint);
canvas.restore();
}
paint.delete();
}, '/assets/mandrill_16.png');
gm('draw_glyphs', (canvas, fetchedByteBuffers) => {
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.Paint();
const font = new CanvasKit.Font(null, 24);
paint.setAntiAlias(true);
const DIM = 16; // row/col count for the grid
const GAP = 32; // spacing between each glyph
const glyphs = new Uint16Array(256);
const positions = new Float32Array(256*2);
for (let i = 0; i < 256; ++i) {
glyphs[i] = i;
positions[2*i+0] = (i%DIM) * GAP;
positions[2*i+1] = Math.round(i/DIM) * GAP;
}
canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint);
font.delete();
paint.delete();
});
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 filtering = { filter: CanvasKit.FilterMode.Linear };
const rotated = CanvasKit.ImageFilter.MakeMatrixTransform(m, filtering, 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.makeImageAtCurrentFrame();
canvas.drawImage(frame, 100, 50, paint);
paint.delete();
redIF.delete();
redCF.delete();
blurIF.delete();
combined.delete();
frame.delete();
img.delete();
}, '/assets/flightAnim.gif');
gm('drawImageVariants', (canvas, fetchedByteBuffers) => {
const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(img).toBeTruthy();
canvas.clear(CanvasKit.WHITE);
canvas.scale(2, 2);
const paint = new CanvasKit.Paint();
const clipTo = (x, y) => {
canvas.save();
canvas.clipRect(CanvasKit.XYWHRect(x, y, 128, 128), CanvasKit.ClipOp.Intersect);
};
clipTo(0, 0);
canvas.drawImage(img, 0, 0, paint);
canvas.restore();
clipTo(128, 0);
canvas.drawImageCubic(img, 128, 0, 1/3, 1/3, null);
canvas.restore();
clipTo(0, 128);
canvas.drawImageOptions(img, 0, 128, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null);
canvas.restore();
const mipImg = img.makeCopyWithDefaultMipmaps();
clipTo(128, 128);
canvas.drawImageOptions(mipImg, 128, 128,
CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest, null);
canvas.restore();
paint.delete();
mipImg.delete();
img.delete();
}, '/assets/mandrill_512.png');
gm('drawImageRectVariants', (canvas, fetchedByteBuffers) => {
const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
expect(img).toBeTruthy();
canvas.clear(CanvasKit.WHITE);
const paint = new CanvasKit.Paint();
const src = CanvasKit.XYWHRect(100, 100, 128, 128);
canvas.drawImageRect(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), paint);
canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
canvas.drawImageRectOptions(img, src, CanvasKit.XYWHRect(0, 256, 256, 256),
CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None);
const mipImg = img.makeCopyWithDefaultMipmaps();
canvas.drawImageRectOptions(mipImg, src, CanvasKit.XYWHRect(256, 256, 256, 256),
CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest);
paint.delete();
mipImg.delete();
img.delete();
}, '/assets/mandrill_512.png');
gm('drawImage_skp', (canvas, fetchedByteBuffers) => {
const pic = CanvasKit.MakePicture(fetchedByteBuffers[0]);
canvas.clear(CanvasKit.TRANSPARENT);
canvas.drawPicture(pic);
// The asset below can be re-downloaded from
// https://fiddle.skia.org/c/cbb8dee39e9f1576cd97c2d504db8eee
}, '/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 rgShader = CanvasKit.Shader.MakeBlend(CanvasKit.BlendMode.SrcOver, rShader, gShader);
const p = new CanvasKit.Paint();
p.setShader(rgShader);
canvas.drawPaint(p);
rShader.delete();
gShader.delete();
rgShader.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 lightPos = [500,500,20];
const zPlaneParams = [0,0,1];
const path = starPath(CanvasKit);
const textFont = new CanvasKit.Font(null, 24);
const textPaint = new CanvasKit.Paint();
canvas.drawShadow(path, zPlaneParams, lightPos, lightRadius,
CanvasKit.BLACK, CanvasKit.MAGENTA, 0);
canvas.drawText('Default Flags', 5, 250, textPaint, textFont);
let bounds = CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.identity(),
path, zPlaneParams, lightPos, lightRadius, 0);
expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164));
bounds = CanvasKit.getShadowLocalBounds(CanvasKit.M44.identity(),
path, zPlaneParams, lightPos, lightRadius, 0);
expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164));
// Test that the APIs accept Malloc objs and the Malloced typearray
const mZPlane = CanvasKit.Malloc(Float32Array, 3);
mZPlane.toTypedArray().set(zPlaneParams);
const mLight = CanvasKit.Malloc(Float32Array, 3);
const lightTA = mLight.toTypedArray();
lightTA.set(lightPos);
canvas.translate(250, 250);
canvas.drawShadow(path, mZPlane, lightTA, lightRadius,
CanvasKit.BLACK, CanvasKit.MAGENTA,
CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight);
canvas.drawText('All Flags', 5, 250, textPaint, textFont);
const outBounds = new Float32Array(4);
CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.rotated(Math.PI / 6),
path, mZPlane, mLight, lightRadius,
CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight,
outBounds);
expectTypedArraysToEqual(outBounds, Float32Array.of(-31.6630249, -15.24227, 245.5, 252.94101));
CanvasKit.Free(mZPlane);
CanvasKit.Free(mLight);
path.delete();
textFont.delete();
textPaint.delete();
});
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();
});
describe('ColorSpace Support', () => {
it('Creates an SRGB 8888 surface by default', () => {
const colorSpace = CanvasKit.ColorSpace.SRGB;
const surface = CanvasKit.MakeCanvasSurface('test');
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 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();
});
it('throws if an invalid matrix is passed in', () => {
let threw;
try {
CanvasKit.ImageFilter.MakeMatrixTransform(
'invalid matrix value',
{ filter: CanvasKit.FilterMode.Linear },
null
)
threw = false;
} catch (e) {
threw = true;
}
expect(threw).toBeTrue();
});
}); // 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);
});
gm('makeImageFromTextureSource_TypedArray', (canvas, _, surface) => {
if (!CanvasKit.gpu) {
return;
}
// This creates and draws an Unpremul 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 = surface.makeImageFromTextureSource(pixels, {
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
canvas.drawImage(img, 1, 1, null);
const info = img.getImageInfo();
expect(info).toEqual({
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
const cs = img.getColorSpace();
expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy();
cs.delete();
img.delete();
});
gm('makeImageFromTextureSource_PremulTypedArray', (canvas, _, surface) => {
if (!CanvasKit.gpu) {
return;
}
// This creates and draws an Unpremul 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
100, 0, 100, 100, // transparent purple
]);
const img = surface.makeImageFromTextureSource(pixels, {
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Premul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
canvas.drawImage(img, 1, 1, null);
const info = img.getImageInfo();
expect(info).toEqual({
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Premul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
img.delete();
});
gm('makeImageFromTextureSource_imgElement', (canvas, _, surface) => {
if (!CanvasKit.gpu) {
return;
}
// This makes an offscreen <img> with the provided source.
const imageEle = new Image();
imageEle.src = '/assets/mandrill_512.png';
// We need to wait until the image is loaded before the texture can use it. For good
// measure, we also wait for it to be decoded.
return imageEle.decode().then(() => {
const img = surface.makeImageFromTextureSource(imageEle);
// Make sure the texture is properly written to and Skia does not draw over it by
// by accident.
canvas.clear(CanvasKit.RED);
surface.updateTextureFromSource(img, imageEle);
canvas.drawImage(img, 0, 0, null);
const info = img.getImageInfo();
expect(info).toEqual({
'width': 512, // width and height should be derived from the image.
'height': 512,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
img.delete();
});
});
gm('MakeLazyImageFromTextureSource_imgElement', (canvas) => {
if (!CanvasKit.gpu) {
return;
}
// This makes an offscreen <img> with the provided source.
const imageEle = new Image();
imageEle.src = '/assets/mandrill_512.png';
// We need to wait until the image is loaded before the texture can use it. For good
// measure, we also wait for it to be decoded.
return imageEle.decode().then(() => {
const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
canvas.drawImage(img, 5, 5, null);
const info = img.getImageInfo();
expect(info).toEqual({
'width': 512, // width and height should be derived from the image.
'height': 512,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
img.delete();
});
});
gm('MakeLazyImageFromTextureSource_imageInfo', (canvas) => {
if (!CanvasKit.gpu) {
return;
}
// This makes an offscreen <img> with the provided source.
const imageEle = new Image();
imageEle.src = '/assets/mandrill_512.png';
// We need to wait until the image is loaded before the texture can use it. For good
// measure, we also wait for it to be decoded.
return imageEle.decode().then(() => {
const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle, {
'width': 400,
'height': 400,
'alphaType': CanvasKit.AlphaType.Premul,
'colorType': CanvasKit.ColorType.RGBA_8888,
});
canvas.drawImage(img, 20, 20, null);
img.delete();
});
});
it('encodes images in three different ways', () => {
// 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({
'width': 1,
'height': 4,
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorType': CanvasKit.ColorType.RGBA_8888,
'colorSpace': CanvasKit.ColorSpace.SRGB
}, pixels, 4);
let bytes = img.encodeToBytes(CanvasKit.ImageFormat.PNG, 100);
assertBytesDecodeToImage(bytes, 'png');
bytes = img.encodeToBytes(CanvasKit.ImageFormat.JPEG, 90);
assertBytesDecodeToImage(bytes, 'jpeg');
bytes = img.encodeToBytes(CanvasKit.ImageFormat.WEBP, 100);
assertBytesDecodeToImage(bytes, 'webp');
img.delete();
});
function assertBytesDecodeToImage(bytes, format) {
expect(bytes).toBeTruthy('null output for ' + format);
const img = CanvasKit.MakeImageFromEncoded(bytes);
expect(img).toBeTruthy('Could not decode result from '+ format);
img && img.delete();
}
it('can make a render target', () => {
if (!CanvasKit.gpu) {
return;
}
const canvas = document.getElementById('test');
const context = CanvasKit.GetWebGLContext(canvas);
const grContext = CanvasKit.MakeGrContext(context);
expect(grContext).toBeTruthy();
const target = CanvasKit.MakeRenderTarget(grContext, 100, 100);
expect(target).toBeTruthy();
target.delete();
grContext.delete();
});
gm('PathEffect_MakePath1D', (canvas) => {
// Based off //docs/examples/skpaint_path_1d_path_effect.cpp
canvas.clear(CanvasKit.WHITE);
const path = new CanvasKit.Path();
path.addOval(CanvasKit.XYWHRect(0, 0, 16, 6));
const paint = new CanvasKit.Paint();
const effect = CanvasKit.PathEffect.MakePath1D(
path, 32, 0, CanvasKit.Path1DEffect.Rotate,
);
paint.setColor(CanvasKit.Color(94, 53, 88, 1)); // deep purple
paint.setPathEffect(effect);
paint.setAntiAlias(true);
canvas.drawCircle(128, 128, 122, paint);
path.delete();
effect.delete();
paint.delete();
});
gm('PathEffect_MakePath2D', (canvas) => {
// Based off //docs/examples/skpaint_path_2d_path_effect.cpp
canvas.clear(CanvasKit.WHITE);
const path = new CanvasKit.Path();
path.moveTo(20, 30);
const points = [20, 20, 10, 30, 0, 30, 20, 10, 30, 10, 40, 0, 40, 10,
50, 10, 40, 20, 40, 30, 20, 50, 20, 40, 30, 30, 20, 30];
for (let i = 0; i < points.length; i += 2) {
path.lineTo(points[i], points[i+1]);
}
const paint = new CanvasKit.Paint();
const effect = CanvasKit.PathEffect.MakePath2D(
CanvasKit.Matrix.scaled(40, 40), path
);
paint.setColor(CanvasKit.Color(53, 94, 59, 1)); // hunter green
paint.setPathEffect(effect);
paint.setAntiAlias(true);
canvas.drawRect(CanvasKit.LTRBRect(-20, -20, 300, 300), paint);
path.delete();
effect.delete();
paint.delete();
});
gm('PathEffect_MakeLine2D', (canvas) => {
// Based off //docs/examples/skpaint_line_2d_path_effect.cpp
canvas.clear(CanvasKit.WHITE);
const lattice = CanvasKit.Matrix.multiply(
CanvasKit.Matrix.scaled(8, 8),
CanvasKit.Matrix.rotated(Math.PI / 6),
);
const paint = new CanvasKit.Paint();
const effect = CanvasKit.PathEffect.MakeLine2D(
2, lattice,
);
paint.setColor(CanvasKit.Color(59, 53, 94, 1)); // dark blue
paint.setPathEffect(effect);
paint.setAntiAlias(true);
canvas.drawRect(CanvasKit.LTRBRect(20, 20, 300, 300), paint);
effect.delete();
paint.delete();
});
});