1 describe('Canvas Behavior', () => {
4 beforeEach(async () => {
5 await EverythingLoaded;
6 container = document.createElement('div');
7 container.innerHTML = `
8 <canvas width=600 height=600 id=test></canvas>
9 <canvas width=600 height=600 id=report></canvas>`;
10 document.body.appendChild(container);
14 document.body.removeChild(container);
17 gm('canvas_api_example', (canvas) => {
18 const paint = new CanvasKit.Paint();
19 paint.setStrokeWidth(2.0);
20 paint.setAntiAlias(true);
21 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
22 paint.setStyle(CanvasKit.PaintStyle.Stroke);
24 canvas.drawLine(3, 10, 30, 15, paint);
25 const rrect = CanvasKit.RRectXY([5, 35, 45, 80], 15, 10);
26 canvas.drawRRect(rrect, paint);
28 canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
30 canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
32 const font = new CanvasKit.Font(null, 20);
33 canvas.drawText('this is ascii text', 5, 100, paint, font);
35 const blob = CanvasKit.TextBlob.MakeFromText('Unicode chars 💩 é É ص', font);
36 canvas.drawTextBlob(blob, 5, 130, paint);
41 // See canvas2d for more API tests
44 gm('effect_and_text_example', (canvas) => {
45 const path = starPath(CanvasKit);
46 const paint = new CanvasKit.Paint();
48 const textPaint = new CanvasKit.Paint();
49 textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
50 textPaint.setAntiAlias(true);
52 const textFont = new CanvasKit.Font(null, 30);
54 const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], 1);
56 paint.setPathEffect(dpe);
57 paint.setStyle(CanvasKit.PaintStyle.Stroke);
58 paint.setStrokeWidth(5.0);
59 paint.setAntiAlias(true);
60 paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
62 canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
64 canvas.drawPath(path, paint);
65 canvas.drawText('This is text', 10, 280, textPaint, textFont);
74 gm('patheffects_canvas', (canvas) => {
75 canvas.clear(CanvasKit.WHITE);
76 const path = starPath(CanvasKit, 100, 100, 100);
77 const paint = new CanvasKit.Paint();
79 const cornerEffect = CanvasKit.PathEffect.MakeCorner(10);
80 const discreteEffect = CanvasKit.PathEffect.MakeDiscrete(5, 10, 0);
82 paint.setPathEffect(cornerEffect);
83 paint.setStyle(CanvasKit.PaintStyle.Stroke);
84 paint.setStrokeWidth(5.0);
85 paint.setAntiAlias(true);
86 paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
87 canvas.drawPath(path, paint);
89 canvas.translate(200, 0);
91 paint.setPathEffect(discreteEffect);
92 canvas.drawPath(path, paint);
94 cornerEffect.delete();
99 it('returns the depth of the save state stack', () => {
100 const canvas = new CanvasKit.Canvas();
101 expect(canvas.getSaveCount()).toEqual(1);
107 expect(canvas.getSaveCount()).toEqual(4);
108 // does nothing, by the SkCanvas API
109 canvas.restoreToCount(500);
110 expect(canvas.getSaveCount()).toEqual(4);
112 expect(canvas.getSaveCount()).toEqual(3);
114 canvas.restoreToCount(2);
115 expect(canvas.getSaveCount()).toEqual(2);
118 gm('circle_canvas', (canvas) => {
119 const path = starPath(CanvasKit);
121 const paint = new CanvasKit.Paint();
123 paint.setStyle(CanvasKit.PaintStyle.Stroke);
124 paint.setStrokeWidth(5.0);
125 paint.setAntiAlias(true);
126 paint.setColor(CanvasKit.CYAN);
128 canvas.clear(CanvasKit.WHITE);
130 canvas.drawCircle(30, 50, 15, paint);
132 paint.setStyle(CanvasKit.PaintStyle.Fill);
133 paint.setColor(CanvasKit.RED);
134 canvas.drawCircle(130, 80, 60, paint);
135 canvas.drawCircle(20, 150, 60, paint);
141 gm('rrect_canvas', (canvas) => {
142 const path = starPath(CanvasKit);
144 const paint = new CanvasKit.Paint();
146 paint.setStyle(CanvasKit.PaintStyle.Stroke);
147 paint.setStrokeWidth(3.0);
148 paint.setAntiAlias(true);
149 paint.setColor(CanvasKit.BLACK);
151 canvas.clear(CanvasKit.WHITE);
153 canvas.drawRRect(CanvasKit.RRectXY(
154 CanvasKit.LTRBRect(10, 10, 50, 50), 5, 10), paint);
156 canvas.drawRRect(CanvasKit.RRectXY(
157 CanvasKit.LTRBRect(60, 10, 110, 50), 10, 5), paint);
159 canvas.drawRRect(CanvasKit.RRectXY(
160 CanvasKit.LTRBRect(10, 60, 210, 260), 0, 30), paint);
162 canvas.drawRRect(CanvasKit.RRectXY(
163 CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30), paint);
169 gm('rrect_8corners_canvas', (canvas) => {
170 const path = starPath(CanvasKit);
172 const paint = new CanvasKit.Paint();
174 paint.setStyle(CanvasKit.PaintStyle.Stroke);
175 paint.setStrokeWidth(3.0);
176 paint.setAntiAlias(true);
177 paint.setColor(CanvasKit.BLACK);
179 canvas.clear(CanvasKit.WHITE);
181 canvas.drawRRect([10, 10, 210, 210,
182 // top left corner, going clockwise
193 // As above, except with the array passed in via malloc'd memory.
194 gm('rrect_8corners_malloc_canvas', (canvas) => {
195 const path = starPath(CanvasKit);
197 const paint = new CanvasKit.Paint();
199 paint.setStyle(CanvasKit.PaintStyle.Stroke);
200 paint.setStrokeWidth(3.0);
201 paint.setAntiAlias(true);
202 paint.setColor(CanvasKit.BLACK);
204 canvas.clear(CanvasKit.WHITE);
206 const rrect = CanvasKit.Malloc(Float32Array, 12);
207 rrect.toTypedArray().set([10, 10, 210, 210,
208 // top left corner, going clockwise
215 canvas.drawRRect(rrect, paint);
217 CanvasKit.Free(rrect);
222 gm('drawDRRect_canvas', (canvas) => {
223 const path = starPath(CanvasKit);
225 const paint = new CanvasKit.Paint();
227 paint.setStyle(CanvasKit.PaintStyle.Fill);
228 paint.setStrokeWidth(3.0);
229 paint.setAntiAlias(true);
230 paint.setColor(CanvasKit.BLACK);
232 canvas.clear(CanvasKit.WHITE);
234 const outer = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 10, 5);
235 const inner = CanvasKit.RRectXY(CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30);
237 canvas.drawDRRect(outer, inner, paint);
243 gm('colorfilters_canvas', (canvas) => {
244 const paint = new CanvasKit.Paint();
246 const blue = CanvasKit.ColorFilter.MakeBlend(
247 CanvasKit.BLUE, CanvasKit.BlendMode.SrcIn);
248 const red = CanvasKit.ColorFilter.MakeBlend(
249 CanvasKit.Color(255, 0, 0, 0.8), CanvasKit.BlendMode.SrcOver);
250 const lerp = CanvasKit.ColorFilter.MakeLerp(0.6, red, blue);
252 paint.setStyle(CanvasKit.PaintStyle.Fill);
253 paint.setAntiAlias(true);
255 canvas.clear(CanvasKit.Color(230, 230, 230));
257 paint.setColorFilter(blue)
258 canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), paint);
259 paint.setColorFilter(lerp)
260 canvas.drawRect(CanvasKit.LTRBRect(50, 10, 100, 60), paint);
261 paint.setColorFilter(red)
262 canvas.drawRect4f(90, 10, 140, 60, paint);
264 const r = CanvasKit.ColorMatrix.rotated(0, .707, -.707);
265 const b = CanvasKit.ColorMatrix.rotated(2, .5, .866);
266 const s = CanvasKit.ColorMatrix.scaled(0.9, 1.5, 0.8, 0.8);
267 let cm = CanvasKit.ColorMatrix.concat(r, s);
268 cm = CanvasKit.ColorMatrix.concat(cm, b);
269 CanvasKit.ColorMatrix.postTranslate(cm, 20, 0, -10, 0);
271 const mat = CanvasKit.ColorFilter.MakeMatrix(cm);
272 const final = CanvasKit.ColorFilter.MakeCompose(mat, lerp);
274 paint.setColorFilter(final)
275 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint);
284 gm('blendmodes_canvas', (canvas) => {
285 canvas.clear(CanvasKit.WHITE);
287 const blendModeNames = Object.keys(CanvasKit.BlendMode).filter((key) => key !== 'values');
289 const PASTEL_MUSTARD_YELLOW = CanvasKit.Color(248, 213, 85, 1.0);
290 const PASTEL_SKY_BLUE = CanvasKit.Color(74, 174, 245, 1.0);
292 const shapePaint = new CanvasKit.Paint();
293 shapePaint.setColor(PASTEL_MUSTARD_YELLOW);
294 shapePaint.setAntiAlias(true);
296 const textPaint = new CanvasKit.Paint();
297 textPaint.setAntiAlias(true);
299 const textFont = new CanvasKit.Font(null, 10);
303 for (const blendModeName of blendModeNames) {
304 // Draw a checkerboard for each blend mode.
305 // Each checkerboard is labelled with a blendmode's name.
306 canvas.drawText(blendModeName, x, y - 5, textPaint, textFont);
307 drawCheckerboard(canvas, x, y, x + 80, y + 80);
309 // A blue square is drawn on to each checkerboard with yellow circle.
310 // In each checkerboard the blue square is drawn using a different blendmode.
311 const blendMode = CanvasKit.BlendMode[blendModeName];
312 canvas.drawOval(CanvasKit.LTRBRect(x + 5, y + 5, x + 55, y + 55), shapePaint);
313 drawRectangle(x + 30, y + 30, x + 70, y + 70, PASTEL_SKY_BLUE, blendMode);
322 function drawCheckerboard(canvas, x1, y1, x2, y2) {
323 const CHECKERBOARD_SQUARE_SIZE = 5;
324 const GREY = CanvasKit.Color(220, 220, 220, 0.5);
325 // Draw black border and white background for checkerboard
326 drawRectangle(x1-1, y1-1, x2+1, y2+1, CanvasKit.BLACK);
327 drawRectangle(x1, y1, x2, y2, CanvasKit.WHITE);
329 // Draw checkerboard squares
330 const numberOfColumns = (x2 - x1) / CHECKERBOARD_SQUARE_SIZE;
331 const numberOfRows = (y2 - y1) / CHECKERBOARD_SQUARE_SIZE
333 for (let row = 0; row < numberOfRows; row++) {
334 for (let column = 0; column < numberOfColumns; column++) {
335 const rowIsEven = row % 2 === 0;
336 const columnIsEven = column % 2 === 0;
338 if ((rowIsEven && !columnIsEven) || (!rowIsEven && columnIsEven)) {
340 x1 + CHECKERBOARD_SQUARE_SIZE * row,
341 y1 + CHECKERBOARD_SQUARE_SIZE * column,
342 Math.min(x1 + CHECKERBOARD_SQUARE_SIZE * row + CHECKERBOARD_SQUARE_SIZE, x2),
343 Math.min(y1 + CHECKERBOARD_SQUARE_SIZE * column + CHECKERBOARD_SQUARE_SIZE, y2),
351 function drawRectangle(x1, y1, x2, y2, color, blendMode=CanvasKit.BlendMode.srcOver) {
353 canvas.clipRect(CanvasKit.LTRBRect(x1, y1, x2, y2), CanvasKit.ClipOp.Intersect, true);
354 canvas.drawColor(color, blendMode);
359 gm('colorfilters_malloc_canvas', (canvas) => {
360 const paint = new CanvasKit.Paint();
364 0.53, -0.918, 0.566, 0, 0,
365 0.53, -0.918, -0.566, 0, -10,
368 const colorObj = new CanvasKit.Malloc(Float32Array, 20);
369 const cm = colorObj.toTypedArray();
373 // MakeMatrix will free the malloc'd array when it is done with it.
374 const final = CanvasKit.ColorFilter.MakeMatrix(cm);
376 paint.setColorFilter(final)
377 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint);
379 CanvasKit.Free(colorObj);
384 gm('clips_canvas', (canvas) => {
385 const path = starPath(CanvasKit);
386 const paint = new CanvasKit.Paint();
387 paint.setColor(CanvasKit.BLUE);
388 const rrect = CanvasKit.RRectXY(CanvasKit.LTRBRect(300, 300, 500, 500), 40, 40);
391 // draw magenta around the outside edge of an rrect.
392 canvas.clipRRect(rrect, CanvasKit.ClipOp.Difference, true);
393 canvas.drawColorComponents(250/255, 30/255, 240/255, 0.9, CanvasKit.BlendMode.SrcOver);
396 // draw grey inside of a star pattern, then the blue star on top
397 canvas.clipPath(path, CanvasKit.ClipOp.Intersect, false);
398 canvas.drawColorInt(CanvasKit.ColorAsInt(200, 200, 200, 255), CanvasKit.BlendMode.SrcOver);
399 canvas.drawPath(path, paint);
405 // inspired by https://fiddle.skia.org/c/feb2a08bb09ede5309678d6a0ab3f981
406 gm('savelayer_rect_paint_canvas', (canvas) => {
407 canvas.clear(CanvasKit.WHITE);
408 const redPaint = new CanvasKit.Paint();
409 redPaint.setColor(CanvasKit.RED);
410 const solidBluePaint = new CanvasKit.Paint();
411 solidBluePaint.setColor(CanvasKit.BLUE);
413 const thirtyBluePaint = new CanvasKit.Paint();
414 thirtyBluePaint.setColor(CanvasKit.BLUE);
415 thirtyBluePaint.setAlphaf(0.3);
417 const alpha = new CanvasKit.Paint();
418 alpha.setAlphaf(0.3);
420 // Draw 4 solid red rectangles on the 0th layer.
421 canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), redPaint);
422 canvas.drawRect(CanvasKit.LTRBRect(150, 10, 200, 60), redPaint);
423 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 60, 120), redPaint);
424 canvas.drawRect(CanvasKit.LTRBRect(150, 70, 200, 120), redPaint);
426 // Draw 2 blue rectangles that overlap. One is solid, the other
427 // is 30% transparent. We should see purple from the right one,
428 // the left one overlaps the red because it is opaque.
429 canvas.drawRect(CanvasKit.LTRBRect(30, 10, 80, 60), solidBluePaint);
430 canvas.drawRect(CanvasKit.LTRBRect(170, 10, 220, 60), thirtyBluePaint);
432 // Save a new layer. When the 1st layer gets merged onto the
433 // 0th layer (i.e. when restore() is called), it will use the provided
434 // paint to do so. The provided paint is set to have 30% opacity, but
435 // it could also have things set like blend modes or image filters.
436 // The rectangle is just a hint, so I've set it to be the area that
437 // we actually draw in before restore is called. It could also be omitted,
438 // see the test below.
439 canvas.saveLayer(alpha, CanvasKit.LTRBRect(10, 10, 220, 180));
441 // Draw the same blue overlapping rectangles as before. Notice in the
442 // final output, we have two different shades of purple instead of the
443 // solid blue overwriting the red. This proves the opacity was applied.
444 canvas.drawRect(CanvasKit.LTRBRect(30, 70, 80, 120), solidBluePaint);
445 canvas.drawRect(CanvasKit.LTRBRect(170, 70, 220, 120), thirtyBluePaint);
447 // We draw two more sets of overlapping red and blue rectangles. Notice
448 // the solid blue overwrites the red. This proves that the opacity from
449 // the alpha paint isn't available when the drawing happens - it only
450 // matters when restore() is called.
451 canvas.drawRect(CanvasKit.LTRBRect(10, 130, 60, 180), redPaint);
452 canvas.drawRect(CanvasKit.LTRBRect(30, 130, 80, 180), solidBluePaint);
454 canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint);
455 canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint);
460 solidBluePaint.delete();
461 thirtyBluePaint.delete();
465 // identical to the test above, except the save layer only has the paint, not
467 gm('savelayer_paint_canvas', (canvas) => {
468 canvas.clear(CanvasKit.WHITE);
469 const redPaint = new CanvasKit.Paint();
470 redPaint.setColor(CanvasKit.RED);
471 const solidBluePaint = new CanvasKit.Paint();
472 solidBluePaint.setColor(CanvasKit.BLUE);
474 const thirtyBluePaint = new CanvasKit.Paint();
475 thirtyBluePaint.setColor(CanvasKit.BLUE);
476 thirtyBluePaint.setAlphaf(0.3);
478 const alpha = new CanvasKit.Paint();
479 alpha.setAlphaf(0.3);
481 // Draw 4 solid red rectangles on the 0th layer.
482 canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), redPaint);
483 canvas.drawRect(CanvasKit.LTRBRect(150, 10, 200, 60), redPaint);
484 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 60, 120), redPaint);
485 canvas.drawRect(CanvasKit.LTRBRect(150, 70, 200, 120), redPaint);
487 // Draw 2 blue rectangles that overlap. One is solid, the other
488 // is 30% transparent. We should see purple from the right one,
489 // the left one overlaps the red because it is opaque.
490 canvas.drawRect(CanvasKit.LTRBRect(30, 10, 80, 60), solidBluePaint);
491 canvas.drawRect(CanvasKit.LTRBRect(170, 10, 220, 60), thirtyBluePaint);
493 // Save a new layer. When the 1st layer gets merged onto the
494 // 0th layer (i.e. when restore() is called), it will use the provided
495 // paint to do so. The provided paint is set to have 30% opacity, but
496 // it could also have things set like blend modes or image filters.
497 canvas.saveLayerPaint(alpha);
499 // Draw the same blue overlapping rectangles as before. Notice in the
500 // final output, we have two different shades of purple instead of the
501 // solid blue overwriting the red. This proves the opacity was applied.
502 canvas.drawRect(CanvasKit.LTRBRect(30, 70, 80, 120), solidBluePaint);
503 canvas.drawRect(CanvasKit.LTRBRect(170, 70, 220, 120), thirtyBluePaint);
505 // We draw two more sets of overlapping red and blue rectangles. Notice
506 // the solid blue overwrites the red. This proves that the opacity from
507 // the alpha paint isn't available when the drawing happens - it only
508 // matters when restore() is called.
509 canvas.drawRect(CanvasKit.LTRBRect(10, 130, 60, 180), redPaint);
510 canvas.drawRect(CanvasKit.LTRBRect(30, 130, 80, 180), solidBluePaint);
512 canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint);
513 canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint);
518 solidBluePaint.delete();
519 thirtyBluePaint.delete();
523 gm('savelayerrec_canvas', (canvas) => {
524 // Note: fiddle.skia.org quietly draws a white background before doing
525 // other things, which is noticed in cases like this where we use saveLayer
526 // with the rec struct.
527 canvas.clear(CanvasKit.WHITE);
529 const redPaint = new CanvasKit.Paint();
530 redPaint.setColor(CanvasKit.RED);
531 redPaint.setAntiAlias(true);
532 canvas.drawCircle(21, 21, 8, redPaint);
534 const bluePaint = new CanvasKit.Paint();
535 bluePaint.setColor(CanvasKit.BLUE);
536 canvas.drawCircle(31, 21, 8, bluePaint);
538 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
540 const count = canvas.saveLayer(null, null, blurIF, 0);
541 expect(count).toEqual(1);
542 canvas.scale(1/4, 1/4);
543 canvas.drawCircle(125, 85, 8, redPaint);
551 gm('drawpoints_canvas', (canvas) => {
552 canvas.clear(CanvasKit.WHITE);
553 const paint = new CanvasKit.Paint();
554 paint.setAntiAlias(true);
555 paint.setStyle(CanvasKit.PaintStyle.Stroke);
556 paint.setStrokeWidth(10);
557 paint.setColor(CanvasKit.Color(153, 204, 162, 0.82));
559 const points = [32, 16, 48, 48, 16, 32];
561 const caps = [CanvasKit.StrokeCap.Round, CanvasKit.StrokeCap.Square,
562 CanvasKit.StrokeCap.Butt];
563 const joins = [CanvasKit.StrokeJoin.Round, CanvasKit.StrokeJoin.Miter,
564 CanvasKit.StrokeJoin.Bevel];
565 const modes = [CanvasKit.PointMode.Points, CanvasKit.PointMode.Lines,
566 CanvasKit.PointMode.Polygon];
568 for (let i = 0; i < caps.length; i++) {
569 paint.setStrokeCap(caps[i]);
570 paint.setStrokeJoin(joins[i]);
572 for (const m of modes) {
573 canvas.drawPoints(m, points, paint);
574 canvas.translate(64, 0);
576 // Try with the malloc approach. Note that the drawPoints
577 // will free the pointer when done.
578 const mPointsObj = CanvasKit.Malloc(Float32Array, 3*2);
579 const mPoints = mPointsObj.toTypedArray();
580 mPoints.set([32, 16, 48, 48, 16, 32]);
582 // The obj from Malloc can be passed in instead of the typed array.
583 canvas.drawPoints(CanvasKit.PointMode.Polygon, mPointsObj, paint);
584 canvas.translate(-192, 64);
585 CanvasKit.Free(mPointsObj);
591 gm('drawPoints in different modes', (canvas) => {
592 canvas.clear(CanvasKit.WHITE);
593 // From https://bugs.chromium.org/p/skia/issues/detail?id=11012
594 const boxPaint = new CanvasKit.Paint();
595 boxPaint.setStyle(CanvasKit.PaintStyle.Stroke);
596 boxPaint.setStrokeWidth(1);
598 const paint = new CanvasKit.Paint();
599 paint.setStyle(CanvasKit.PaintStyle.Stroke);
600 paint.setStrokeWidth(5);
601 paint.setStrokeCap(CanvasKit.StrokeCap.Round);
602 paint.setColorInt(0xFF0000FF); // Blue
603 paint.setAntiAlias(true);
605 const points = Float32Array.of(40, 40, 80, 40, 120, 80, 160, 80);
607 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
608 canvas.drawPoints(CanvasKit.PointMode.Points, points, paint);
610 canvas.translate(0, 50);
611 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
612 canvas.drawPoints(CanvasKit.PointMode.Lines, points, paint);
614 canvas.translate(0, 50);
615 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
616 canvas.drawPoints(CanvasKit.PointMode.Polygon, points, paint);
618 // The control version using drawPath
619 canvas.translate(0, 50);
620 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
621 const path = new CanvasKit.Path();
624 path.lineTo(120, 80);
625 path.lineTo(160, 80);
626 paint.setColorInt(0xFFFF0000); // RED
627 canvas.drawPath(path, paint);
634 gm('drawImageNine_canvas', (canvas, fetchedByteBuffers) => {
635 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
636 expect(img).toBeTruthy();
638 canvas.clear(CanvasKit.WHITE);
639 const paint = new CanvasKit.Paint();
641 canvas.drawImageNine(img, CanvasKit.LTRBiRect(40, 40, 400, 300),
642 CanvasKit.LTRBRect(5, 5, 300, 650), CanvasKit.FilterMode.Nearest, paint);
645 }, '/assets/mandrill_512.png');
647 // This should be a nice, clear image.
648 gm('makeImageShaderCubic_canvas', (canvas, fetchedByteBuffers) => {
649 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
650 expect(img).toBeTruthy();
652 canvas.clear(CanvasKit.WHITE);
653 const paint = new CanvasKit.Paint();
654 const shader = img.makeShaderCubic(CanvasKit.TileMode.Decal, CanvasKit.TileMode.Clamp,
655 1/3 /*B*/, 1/3 /*C*/,
656 CanvasKit.Matrix.rotated(0.1));
657 paint.setShader(shader);
659 canvas.drawPaint(paint);
663 }, '/assets/mandrill_512.png');
665 // This will look more blocky than the version above.
666 gm('makeImageShaderOptions_canvas', (canvas, fetchedByteBuffers) => {
667 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
668 expect(img).toBeTruthy();
669 const imgWithMipMap = img.makeCopyWithDefaultMipmaps();
671 canvas.clear(CanvasKit.WHITE);
672 const paint = new CanvasKit.Paint();
673 const shader = imgWithMipMap.makeShaderOptions(CanvasKit.TileMode.Decal,
674 CanvasKit.TileMode.Clamp,
675 CanvasKit.FilterMode.Nearest,
676 CanvasKit.MipmapMode.Linear,
677 CanvasKit.Matrix.rotated(0.1));
678 paint.setShader(shader);
680 canvas.drawPaint(paint);
684 imgWithMipMap.delete();
685 }, '/assets/mandrill_512.png');
687 gm('drawvertices_canvas', (canvas) => {
688 const paint = new CanvasKit.Paint();
689 paint.setAntiAlias(true);
691 const points = [0, 0, 250, 0, 100, 100, 0, 250];
692 // 2d float color array
693 const colors = [CanvasKit.RED, CanvasKit.BLUE,
694 CanvasKit.YELLOW, CanvasKit.CYAN];
695 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
696 points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
698 const bounds = vertices.bounds();
699 expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250));
701 canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
706 gm('drawvertices_canvas_flat_floats', (canvas) => {
707 const paint = new CanvasKit.Paint();
708 paint.setAntiAlias(true);
710 const points = [0, 0, 250, 0, 100, 100, 0, 250];
711 // 1d float color array
712 const colors = Float32Array.of(...CanvasKit.RED, ...CanvasKit.BLUE,
713 ...CanvasKit.YELLOW, ...CanvasKit.CYAN);
714 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
715 points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
717 const bounds = vertices.bounds();
718 expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250));
720 canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
725 gm('drawvertices_texture_canvas', (canvas, fetchedByteBuffers) => {
726 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
728 const paint = new CanvasKit.Paint();
729 paint.setAntiAlias(true);
732 70, 170, 40, 90, 130, 150, 100, 50,
733 225, 150, 225, 60, 310, 180, 330, 100,
735 const textureCoordinates = [
736 0, 240, 0, 0, 80, 240, 80, 0,
737 160, 240, 160, 0, 240, 240, 240, 0,
739 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TrianglesStrip,
740 points, textureCoordinates, null /* colors */, false /*isVolatile*/);
742 const shader = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror,
743 1/3 /*B*/, 1/3 /*C*/,);
744 paint.setShader(shader);
745 canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
751 }, '/assets/brickwork-texture.jpg');
753 it('can change the 3x3 matrix on the canvas and read it back', () => {
754 const canvas = new CanvasKit.Canvas();
756 let matr = canvas.getTotalMatrix();
757 expect(matr).toEqual(CanvasKit.Matrix.identity());
759 // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to
760 // make sure the 3x3 matrix properly sets these to 0 when it uses the same buffer.
762 const garbageMatrix = new Float32Array(16);
763 garbageMatrix.fill(-3);
764 canvas.concat(garbageMatrix);
767 canvas.concat(CanvasKit.Matrix.rotated(Math.PI/4));
768 const d = new DOMMatrix().translate(20, 10);
771 matr = canvas.getTotalMatrix();
772 const expected = CanvasKit.Matrix.multiply(
773 CanvasKit.Matrix.rotated(Math.PI/4),
774 CanvasKit.Matrix.translated(20, 10)
776 expect3x3MatricesToMatch(expected, matr);
778 // The 3x3 should be expanded into a 4x4, with 0s in the 3rd row and column.
779 matr = canvas.getLocalToDevice();
780 expect4x4MatricesToMatch([
781 0.707106, -0.707106, 0, 7.071067,
782 0.707106, 0.707106, 0, 21.213203,
784 0 , 0 , 0, 1 ], matr);
787 it('can accept a 3x2 matrix', () => {
788 const canvas = new CanvasKit.Canvas();
790 let matr = canvas.getTotalMatrix();
791 expect(matr).toEqual(CanvasKit.Matrix.identity());
793 // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to
794 // make sure the 3x2 matrix properly sets these to 0 when it uses the same buffer.
796 const garbageMatrix = new Float32Array(16);
797 garbageMatrix.fill(-3);
798 canvas.concat(garbageMatrix);
801 canvas.concat([1.4, -0.2, 12,
804 matr = canvas.getTotalMatrix();
805 const expected = [1.4, -0.2, 12,
808 expect3x3MatricesToMatch(expected, matr);
810 // The 3x2 should be expanded into a 4x4, with 0s in the 3rd row and column
811 // and the perspective filled in.
812 matr = canvas.getLocalToDevice();
813 expect4x4MatricesToMatch([
817 0 , 0 , 0, 1], matr);
820 it('can change the 4x4 matrix on the canvas and read it back', () => {
821 const canvas = new CanvasKit.Canvas();
823 let matr = canvas.getLocalToDevice();
824 expect(matr).toEqual(CanvasKit.M44.identity());
826 canvas.concat(CanvasKit.M44.rotated([0, 1, 0], Math.PI/4));
827 canvas.concat(CanvasKit.M44.rotated([1, 0, 1], Math.PI/8));
829 const expected = CanvasKit.M44.multiply(
830 CanvasKit.M44.rotated([0, 1, 0], Math.PI/4),
831 CanvasKit.M44.rotated([1, 0, 1], Math.PI/8),
834 expect4x4MatricesToMatch(expected, canvas.getLocalToDevice());
835 // TODO(kjlubick) add test for DOMMatrix
836 // TODO(nifong) add more involved test for camera-related math.
839 it('can change the device clip bounds to the canvas and read it back', () => {
840 // We need to use the Canvas constructor with a width/height or there is no maximum
841 // clip area, and all clipping will result in a clip of [0, 0, 0, 0]
842 const canvas = new CanvasKit.Canvas(300, 400);
843 let clip = canvas.getDeviceClipBounds();
844 expect(clip).toEqual(Int32Array.of(0, 0, 300, 400));
846 canvas.clipRect(CanvasKit.LTRBRect(10, 20, 30, 45), CanvasKit.ClipOp.Intersect, false);
847 canvas.getDeviceClipBounds(clip);
848 expect(clip).toEqual(Int32Array.of(10, 20, 30, 45));
851 gm('concat_with4x4_canvas', (canvas) => {
852 const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
853 const paint = new CanvasKit.Paint();
854 paint.setAntiAlias(true);
855 canvas.clear(CanvasKit.WHITE);
857 // Rotate it a bit on all 3 major axis, centered on the screen.
858 // To play with rotations, see https://jsfiddle.skia.org/canvaskit/0525300405796aa87c3b84cc0d5748516fca0045d7d6d9c7840710ab771edcd4
859 const turn = CanvasKit.M44.multiply(
860 CanvasKit.M44.translated([CANVAS_WIDTH/2, 0, 0]),
861 CanvasKit.M44.rotated([1, 0, 0], Math.PI/3),
862 CanvasKit.M44.rotated([0, 1, 0], Math.PI/4),
863 CanvasKit.M44.rotated([0, 0, 1], Math.PI/16),
864 CanvasKit.M44.translated([-CANVAS_WIDTH/2, 0, 0]),
868 // Draw some stripes to help the eye detect the turn
869 const stripeWidth = 10;
870 paint.setColor(CanvasKit.BLACK);
871 for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
872 canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
875 paint.setColor(CanvasKit.YELLOW);
876 canvas.drawPath(path, paint);
881 gm('particles_canvas', (canvas) => {
882 const curveParticles = {
885 'Type': 'SkCircleDrawable',
889 `void effectSpawn(inout Effect effect) {
891 effect.color = float4(1, 0, 0, 1);
893 void spawn(inout Particle p) {
894 p.lifetime = 3 + rand(p.seed);
898 void update(inout Particle p) {
899 float w = mix(15, 3, p.age);
900 p.pos.x = sin(radians(p.age * 320)) * mix(25, 10, p.age) + mix(-w, w, rand(p.seed));
901 if (rand(p.seed) < 0.5) { p.pos.x = -p.pos.x; }
903 p.color.g = (mix(75, 220, p.age) + mix(-30, 30, rand(p.seed))) / 255;
909 const particles = CanvasKit.MakeParticles(JSON.stringify(curveParticles));
910 particles.start(0, true);
911 particles.setPosition([0, 0]);
913 const paint = new CanvasKit.Paint();
914 paint.setAntiAlias(true);
915 paint.setColor(CanvasKit.WHITE);
916 const font = new CanvasKit.Font(null, 12);
918 canvas.clear(CanvasKit.BLACK);
920 // Draw a 5x5 set of different times in the particle system
921 // like a filmstrip of motion of particles.
922 const LEFT_MARGIN = 90;
923 const TOP_MARGIN = 100;
924 for (let row = 0; row < 5; row++) {
925 for (let column = 0; column < 5; column++) {
927 canvas.translate(LEFT_MARGIN + column*100, TOP_MARGIN + row*100);
929 // Time moves in row-major order in increments of 0.02.
930 const particleTime = row/10 + column/50;
932 canvas.drawText('time ' + particleTime.toFixed(2), -30, 20, paint, font);
933 particles.update(particleTime);
935 particles.draw(canvas);
942 const expect3x3MatricesToMatch = (expected, actual) => {
943 expect(expected.length).toEqual(9);
944 expect(actual.length).toEqual(9);
945 for (let i = 0; i < expected.length; i++) {
946 expect(expected[i]).toBeCloseTo(actual[i], 5);
950 const expect4x4MatricesToMatch = (expected, actual) => {
951 expect(expected.length).toEqual(16);
952 expect(actual.length).toEqual(16);
953 for (let i = 0; i < expected.length; i++) {
954 expect(expected[i]).toBeCloseTo(actual[i], 5);