1describe('Canvas Behavior', () => { 2 let container; 3 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); 11 }); 12 13 afterEach(() => { 14 document.body.removeChild(container); 15 }); 16 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); 23 paint.setDither(false); 24 25 canvas.drawLine(3, 10, 30, 15, paint); 26 const rrect = CanvasKit.RRectXY([5, 35, 45, 80], 15, 10); 27 canvas.drawRRect(rrect, paint); 28 29 canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint); 30 31 canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint); 32 33 const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20); 34 canvas.drawText('this is ascii text', 5, 100, paint, font); 35 36 const blob = CanvasKit.TextBlob.MakeFromText('Unicode chars é É ص', font); 37 canvas.drawTextBlob(blob, 5, 130, paint); 38 39 font.delete(); 40 blob.delete(); 41 paint.delete(); 42 // See canvas2d for more API tests 43 }); 44 45 gm('effect_and_text_example', (canvas) => { 46 const path = starPath(CanvasKit); 47 const paint = new CanvasKit.Paint(); 48 49 const textPaint = new CanvasKit.Paint(); 50 textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); 51 textPaint.setAntiAlias(true); 52 53 const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 30); 54 55 const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], 1); 56 57 paint.setPathEffect(dpe); 58 paint.setStyle(CanvasKit.PaintStyle.Stroke); 59 paint.setStrokeWidth(5.0); 60 paint.setAntiAlias(true); 61 paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); 62 63 canvas.drawPath(path, paint); 64 canvas.drawText('This is text', 10, 280, textPaint, textFont); 65 66 dpe.delete(); 67 path.delete(); 68 paint.delete(); 69 textFont.delete(); 70 textPaint.delete(); 71 }); 72 73 gm('patheffects_canvas', (canvas) => { 74 const path = starPath(CanvasKit, 100, 100, 100); 75 const paint = new CanvasKit.Paint(); 76 77 const cornerEffect = CanvasKit.PathEffect.MakeCorner(10); 78 const discreteEffect = CanvasKit.PathEffect.MakeDiscrete(5, 10, 0); 79 80 paint.setPathEffect(cornerEffect); 81 paint.setStyle(CanvasKit.PaintStyle.Stroke); 82 paint.setStrokeWidth(5.0); 83 paint.setAntiAlias(true); 84 paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); 85 canvas.drawPath(path, paint); 86 87 canvas.translate(200, 0); 88 89 paint.setPathEffect(discreteEffect); 90 canvas.drawPath(path, paint); 91 92 cornerEffect.delete(); 93 path.delete(); 94 paint.delete(); 95 }); 96 97 it('returns the depth of the save state stack', () => { 98 const canvas = new CanvasKit.Canvas(); 99 expect(canvas.getSaveCount()).toEqual(1); 100 canvas.save(); 101 canvas.save(); 102 canvas.restore(); 103 canvas.save(); 104 canvas.save(); 105 expect(canvas.getSaveCount()).toEqual(4); 106 // does nothing, by the SkCanvas API 107 canvas.restoreToCount(500); 108 expect(canvas.getSaveCount()).toEqual(4); 109 canvas.restore(); 110 expect(canvas.getSaveCount()).toEqual(3); 111 canvas.save(); 112 canvas.restoreToCount(2); 113 expect(canvas.getSaveCount()).toEqual(2); 114 }); 115 116 gm('circle_canvas', (canvas) => { 117 const path = starPath(CanvasKit); 118 119 const paint = new CanvasKit.Paint(); 120 121 paint.setStyle(CanvasKit.PaintStyle.Stroke); 122 paint.setStrokeWidth(5.0); 123 paint.setAntiAlias(true); 124 paint.setColor(CanvasKit.CYAN); 125 126 canvas.drawCircle(30, 50, 15, paint); 127 128 paint.setStyle(CanvasKit.PaintStyle.Fill); 129 paint.setColor(CanvasKit.RED); 130 canvas.drawCircle(130, 80, 60, paint); 131 canvas.drawCircle(20, 150, 60, paint); 132 133 path.delete(); 134 paint.delete(); 135 }); 136 137 gm('rrect_canvas', (canvas) => { 138 const path = starPath(CanvasKit); 139 140 const paint = new CanvasKit.Paint(); 141 142 paint.setStyle(CanvasKit.PaintStyle.Stroke); 143 paint.setStrokeWidth(3.0); 144 paint.setAntiAlias(true); 145 paint.setColor(CanvasKit.BLACK); 146 147 canvas.drawRRect(CanvasKit.RRectXY( 148 CanvasKit.LTRBRect(10, 10, 50, 50), 5, 10), paint); 149 150 canvas.drawRRect(CanvasKit.RRectXY( 151 CanvasKit.LTRBRect(60, 10, 110, 50), 10, 5), paint); 152 153 canvas.drawRRect(CanvasKit.RRectXY( 154 CanvasKit.LTRBRect(10, 60, 210, 260), 0, 30), paint); 155 156 canvas.drawRRect(CanvasKit.RRectXY( 157 CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30), paint); 158 159 path.delete(); 160 paint.delete(); 161 }); 162 163 gm('rrect_8corners_canvas', (canvas) => { 164 const path = starPath(CanvasKit); 165 166 const paint = new CanvasKit.Paint(); 167 168 paint.setStyle(CanvasKit.PaintStyle.Stroke); 169 paint.setStrokeWidth(3.0); 170 paint.setAntiAlias(true); 171 paint.setColor(CanvasKit.BLACK); 172 173 canvas.drawRRect([10, 10, 210, 210, 174 // top left corner, going clockwise 175 10, 30, 176 30, 10, 177 50, 75, 178 120, 120, 179 ], paint); 180 181 path.delete(); 182 paint.delete(); 183 }); 184 185 // As above, except with the array passed in via malloc'd memory. 186 gm('rrect_8corners_malloc_canvas', (canvas) => { 187 const path = starPath(CanvasKit); 188 189 const paint = new CanvasKit.Paint(); 190 191 paint.setStyle(CanvasKit.PaintStyle.Stroke); 192 paint.setStrokeWidth(3.0); 193 paint.setAntiAlias(true); 194 paint.setColor(CanvasKit.BLACK); 195 196 const rrect = CanvasKit.Malloc(Float32Array, 12); 197 rrect.toTypedArray().set([10, 10, 210, 210, 198 // top left corner, going clockwise 199 10, 30, 200 30, 10, 201 50, 75, 202 120, 120, 203 ]); 204 205 canvas.drawRRect(rrect, paint); 206 207 CanvasKit.Free(rrect); 208 path.delete(); 209 paint.delete(); 210 }); 211 212 gm('drawDRRect_canvas', (canvas) => { 213 const path = starPath(CanvasKit); 214 215 const paint = new CanvasKit.Paint(); 216 217 paint.setStyle(CanvasKit.PaintStyle.Fill); 218 paint.setStrokeWidth(3.0); 219 paint.setAntiAlias(true); 220 paint.setColor(CanvasKit.BLACK); 221 222 const outer = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 10, 5); 223 const inner = CanvasKit.RRectXY(CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30); 224 225 canvas.drawDRRect(outer, inner, paint); 226 227 path.delete(); 228 paint.delete(); 229 }); 230 231 gm('colorfilters_canvas', (canvas) => { 232 canvas.clear(CanvasKit.Color(230, 230, 230)); 233 234 const paint = new CanvasKit.Paint(); 235 236 const blue = CanvasKit.ColorFilter.MakeBlend( 237 CanvasKit.BLUE, CanvasKit.BlendMode.SrcIn); 238 const red = CanvasKit.ColorFilter.MakeBlend( 239 CanvasKit.Color(255, 0, 0, 0.8), CanvasKit.BlendMode.SrcOver); 240 const lerp = CanvasKit.ColorFilter.MakeLerp(0.6, red, blue); 241 242 paint.setStyle(CanvasKit.PaintStyle.Fill); 243 paint.setAntiAlias(true); 244 245 paint.setColorFilter(blue) 246 canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), paint); 247 paint.setColorFilter(lerp) 248 canvas.drawRect(CanvasKit.LTRBRect(50, 10, 100, 60), paint); 249 paint.setColorFilter(red) 250 canvas.drawRect4f(90, 10, 140, 60, paint); 251 252 const r = CanvasKit.ColorMatrix.rotated(0, .707, -.707); 253 const b = CanvasKit.ColorMatrix.rotated(2, .5, .866); 254 const s = CanvasKit.ColorMatrix.scaled(0.9, 1.5, 0.8, 0.8); 255 let cm = CanvasKit.ColorMatrix.concat(r, s); 256 cm = CanvasKit.ColorMatrix.concat(cm, b); 257 CanvasKit.ColorMatrix.postTranslate(cm, 20, 0, -10, 0); 258 259 const mat = CanvasKit.ColorFilter.MakeMatrix(cm); 260 const final = CanvasKit.ColorFilter.MakeCompose(mat, lerp); 261 262 paint.setColorFilter(final) 263 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint); 264 265 paint.delete(); 266 blue.delete(); 267 red.delete(); 268 lerp.delete(); 269 final.delete(); 270 }); 271 272 gm('blendmodes_canvas', (canvas) => { 273 274 const blendModeNames = Object.keys(CanvasKit.BlendMode).filter((key) => key !== 'values'); 275 276 const PASTEL_MUSTARD_YELLOW = CanvasKit.Color(248, 213, 85, 1.0); 277 const PASTEL_SKY_BLUE = CanvasKit.Color(74, 174, 245, 1.0); 278 279 const shapePaint = new CanvasKit.Paint(); 280 shapePaint.setColor(PASTEL_MUSTARD_YELLOW); 281 shapePaint.setAntiAlias(true); 282 283 const textPaint = new CanvasKit.Paint(); 284 textPaint.setAntiAlias(true); 285 286 const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 10); 287 288 let x = 10; 289 let y = 20; 290 for (const blendModeName of blendModeNames) { 291 // Draw a checkerboard for each blend mode. 292 // Each checkerboard is labelled with a blendmode's name. 293 canvas.drawText(blendModeName, x, y - 5, textPaint, textFont); 294 drawCheckerboard(canvas, x, y, x + 80, y + 80); 295 296 // A blue square is drawn on to each checkerboard with yellow circle. 297 // In each checkerboard the blue square is drawn using a different blendmode. 298 const blendMode = CanvasKit.BlendMode[blendModeName]; 299 canvas.drawOval(CanvasKit.LTRBRect(x + 5, y + 5, x + 55, y + 55), shapePaint); 300 drawRectangle(x + 30, y + 30, x + 70, y + 70, PASTEL_SKY_BLUE, blendMode); 301 302 x += 90; 303 if (x > 500) { 304 x = 10; 305 y += 110; 306 } 307 } 308 309 function drawCheckerboard(canvas, x1, y1, x2, y2) { 310 const CHECKERBOARD_SQUARE_SIZE = 5; 311 const GREY = CanvasKit.Color(220, 220, 220, 0.5); 312 // Draw black border and white background for checkerboard 313 drawRectangle(x1-1, y1-1, x2+1, y2+1, CanvasKit.BLACK); 314 drawRectangle(x1, y1, x2, y2, CanvasKit.WHITE); 315 316 // Draw checkerboard squares 317 const numberOfColumns = (x2 - x1) / CHECKERBOARD_SQUARE_SIZE; 318 const numberOfRows = (y2 - y1) / CHECKERBOARD_SQUARE_SIZE 319 320 for (let row = 0; row < numberOfRows; row++) { 321 for (let column = 0; column < numberOfColumns; column++) { 322 const rowIsEven = row % 2 === 0; 323 const columnIsEven = column % 2 === 0; 324 325 if ((rowIsEven && !columnIsEven) || (!rowIsEven && columnIsEven)) { 326 drawRectangle( 327 x1 + CHECKERBOARD_SQUARE_SIZE * row, 328 y1 + CHECKERBOARD_SQUARE_SIZE * column, 329 Math.min(x1 + CHECKERBOARD_SQUARE_SIZE * row + CHECKERBOARD_SQUARE_SIZE, x2), 330 Math.min(y1 + CHECKERBOARD_SQUARE_SIZE * column + CHECKERBOARD_SQUARE_SIZE, y2), 331 GREY 332 ); 333 } 334 } 335 } 336 } 337 338 function drawRectangle(x1, y1, x2, y2, color, blendMode=CanvasKit.BlendMode.srcOver) { 339 canvas.save(); 340 canvas.clipRect(CanvasKit.LTRBRect(x1, y1, x2, y2), CanvasKit.ClipOp.Intersect, true); 341 canvas.drawColor(color, blendMode); 342 canvas.restore(); 343 } 344 }); 345 346 gm('colorfilters_malloc_canvas', (canvas) => { 347 const paint = new CanvasKit.Paint(); 348 349 const src = [ 350 0.8, 0.45, 2, 0, 20, 351 0.53, -0.918, 0.566, 0, 0, 352 0.53, -0.918, -0.566, 0, -10, 353 0, 0, 0, 0.8, 0, 354 ] 355 const colorObj = new CanvasKit.Malloc(Float32Array, 20); 356 const cm = colorObj.toTypedArray(); 357 for (i in src) { 358 cm[i] = src[i]; 359 } 360 // MakeMatrix will free the malloc'd array when it is done with it. 361 const final = CanvasKit.ColorFilter.MakeMatrix(cm); 362 363 paint.setColorFilter(final) 364 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint); 365 366 CanvasKit.Free(colorObj); 367 paint.delete(); 368 final.delete(); 369 }); 370 371 gm('clips_canvas', (canvas) => { 372 const path = starPath(CanvasKit); 373 const paint = new CanvasKit.Paint(); 374 paint.setColor(CanvasKit.BLUE); 375 const rrect = CanvasKit.RRectXY(CanvasKit.LTRBRect(300, 300, 500, 500), 40, 40); 376 377 canvas.save(); 378 // draw magenta around the outside edge of an rrect. 379 canvas.clipRRect(rrect, CanvasKit.ClipOp.Difference, true); 380 canvas.drawColorComponents(250/255, 30/255, 240/255, 0.9, CanvasKit.BlendMode.SrcOver); 381 canvas.restore(); 382 383 // draw grey inside of a star pattern, then the blue star on top 384 canvas.clipPath(path, CanvasKit.ClipOp.Intersect, false); 385 canvas.drawColorInt(CanvasKit.ColorAsInt(200, 200, 200, 255), CanvasKit.BlendMode.SrcOver); 386 canvas.drawPath(path, paint); 387 388 path.delete(); 389 paint.delete(); 390 }); 391 392 // inspired by https://fiddle.skia.org/c/feb2a08bb09ede5309678d6a0ab3f981 393 gm('savelayer_rect_paint_canvas', (canvas) => { 394 const redPaint = new CanvasKit.Paint(); 395 redPaint.setColor(CanvasKit.RED); 396 const solidBluePaint = new CanvasKit.Paint(); 397 solidBluePaint.setColor(CanvasKit.BLUE); 398 399 const thirtyBluePaint = new CanvasKit.Paint(); 400 thirtyBluePaint.setColor(CanvasKit.BLUE); 401 thirtyBluePaint.setAlphaf(0.3); 402 403 const alpha = new CanvasKit.Paint(); 404 alpha.setAlphaf(0.3); 405 406 // Draw 4 solid red rectangles on the 0th layer. 407 canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), redPaint); 408 canvas.drawRect(CanvasKit.LTRBRect(150, 10, 200, 60), redPaint); 409 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 60, 120), redPaint); 410 canvas.drawRect(CanvasKit.LTRBRect(150, 70, 200, 120), redPaint); 411 412 // Draw 2 blue rectangles that overlap. One is solid, the other 413 // is 30% transparent. We should see purple from the right one, 414 // the left one overlaps the red because it is opaque. 415 canvas.drawRect(CanvasKit.LTRBRect(30, 10, 80, 60), solidBluePaint); 416 canvas.drawRect(CanvasKit.LTRBRect(170, 10, 220, 60), thirtyBluePaint); 417 418 // Save a new layer. When the 1st layer gets merged onto the 419 // 0th layer (i.e. when restore() is called), it will use the provided 420 // paint to do so. The provided paint is set to have 30% opacity, but 421 // it could also have things set like blend modes or image filters. 422 // The rectangle is just a hint, so I've set it to be the area that 423 // we actually draw in before restore is called. It could also be omitted, 424 // see the test below. 425 canvas.saveLayer(alpha, CanvasKit.LTRBRect(10, 10, 220, 180)); 426 427 // Draw the same blue overlapping rectangles as before. Notice in the 428 // final output, we have two different shades of purple instead of the 429 // solid blue overwriting the red. This proves the opacity was applied. 430 canvas.drawRect(CanvasKit.LTRBRect(30, 70, 80, 120), solidBluePaint); 431 canvas.drawRect(CanvasKit.LTRBRect(170, 70, 220, 120), thirtyBluePaint); 432 433 // We draw two more sets of overlapping red and blue rectangles. Notice 434 // the solid blue overwrites the red. This proves that the opacity from 435 // the alpha paint isn't available when the drawing happens - it only 436 // matters when restore() is called. 437 canvas.drawRect(CanvasKit.LTRBRect(10, 130, 60, 180), redPaint); 438 canvas.drawRect(CanvasKit.LTRBRect(30, 130, 80, 180), solidBluePaint); 439 440 canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint); 441 canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint); 442 443 canvas.restore(); 444 445 redPaint.delete(); 446 solidBluePaint.delete(); 447 thirtyBluePaint.delete(); 448 alpha.delete(); 449 }); 450 451 // identical to the test above, except the save layer only has the paint, not 452 // the rectangle. 453 gm('savelayer_paint_canvas', (canvas) => { 454 const redPaint = new CanvasKit.Paint(); 455 redPaint.setColor(CanvasKit.RED); 456 const solidBluePaint = new CanvasKit.Paint(); 457 solidBluePaint.setColor(CanvasKit.BLUE); 458 459 const thirtyBluePaint = new CanvasKit.Paint(); 460 thirtyBluePaint.setColor(CanvasKit.BLUE); 461 thirtyBluePaint.setAlphaf(0.3); 462 463 const alpha = new CanvasKit.Paint(); 464 alpha.setAlphaf(0.3); 465 466 // Draw 4 solid red rectangles on the 0th layer. 467 canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), redPaint); 468 canvas.drawRect(CanvasKit.LTRBRect(150, 10, 200, 60), redPaint); 469 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 60, 120), redPaint); 470 canvas.drawRect(CanvasKit.LTRBRect(150, 70, 200, 120), redPaint); 471 472 // Draw 2 blue rectangles that overlap. One is solid, the other 473 // is 30% transparent. We should see purple from the right one, 474 // the left one overlaps the red because it is opaque. 475 canvas.drawRect(CanvasKit.LTRBRect(30, 10, 80, 60), solidBluePaint); 476 canvas.drawRect(CanvasKit.LTRBRect(170, 10, 220, 60), thirtyBluePaint); 477 478 // Save a new layer. When the 1st layer gets merged onto the 479 // 0th layer (i.e. when restore() is called), it will use the provided 480 // paint to do so. The provided paint is set to have 30% opacity, but 481 // it could also have things set like blend modes or image filters. 482 canvas.saveLayerPaint(alpha); 483 484 // Draw the same blue overlapping rectangles as before. Notice in the 485 // final output, we have two different shades of purple instead of the 486 // solid blue overwriting the red. This proves the opacity was applied. 487 canvas.drawRect(CanvasKit.LTRBRect(30, 70, 80, 120), solidBluePaint); 488 canvas.drawRect(CanvasKit.LTRBRect(170, 70, 220, 120), thirtyBluePaint); 489 490 // We draw two more sets of overlapping red and blue rectangles. Notice 491 // the solid blue overwrites the red. This proves that the opacity from 492 // the alpha paint isn't available when the drawing happens - it only 493 // matters when restore() is called. 494 canvas.drawRect(CanvasKit.LTRBRect(10, 130, 60, 180), redPaint); 495 canvas.drawRect(CanvasKit.LTRBRect(30, 130, 80, 180), solidBluePaint); 496 497 canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint); 498 canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint); 499 500 canvas.restore(); 501 502 redPaint.delete(); 503 solidBluePaint.delete(); 504 thirtyBluePaint.delete(); 505 alpha.delete(); 506 }); 507 508 gm('savelayerrec_canvas', (canvas) => { 509 // Note: fiddle.skia.org quietly draws a white background before doing 510 // other things, which is noticed in cases like this where we use saveLayer 511 // with the rec struct. 512 canvas.scale(8, 8); 513 const redPaint = new CanvasKit.Paint(); 514 redPaint.setColor(CanvasKit.RED); 515 redPaint.setAntiAlias(true); 516 canvas.drawCircle(21, 21, 8, redPaint); 517 518 const bluePaint = new CanvasKit.Paint(); 519 bluePaint.setColor(CanvasKit.BLUE); 520 canvas.drawCircle(31, 21, 8, bluePaint); 521 522 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null); 523 524 const count = canvas.saveLayer(null, null, blurIF, 0); 525 expect(count).toEqual(1); 526 canvas.scale(1/4, 1/4); 527 canvas.drawCircle(125, 85, 8, redPaint); 528 canvas.restore(); 529 530 blurIF.delete(); 531 redPaint.delete(); 532 bluePaint.delete(); 533 }); 534 535 gm('savelayerrec_canvas_backdrop_tilemode', (canvas) => { 536 // Note: fiddle.skia.org quietly draws a white background before doing 537 // other things, which is noticed in cases like this where we use saveLayer 538 // with the rec struct. 539 canvas.scale(8, 8); 540 const redPaint = new CanvasKit.Paint(); 541 redPaint.setColor(CanvasKit.RED); 542 redPaint.setAntiAlias(true); 543 canvas.drawCircle(21, 21, 8, redPaint); 544 545 const bluePaint = new CanvasKit.Paint(); 546 bluePaint.setColor(CanvasKit.BLUE); 547 canvas.drawCircle(31, 21, 8, bluePaint); 548 549 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null); 550 551 const count = canvas.saveLayer(null, null, blurIF, 0, CanvasKit.TileMode.Decal); 552 expect(count).toEqual(1); 553 canvas.scale(1/4, 1/4); 554 canvas.drawCircle(125, 85, 8, redPaint); 555 canvas.restore(); 556 557 blurIF.delete(); 558 redPaint.delete(); 559 bluePaint.delete(); 560 }); 561 562 gm('drawpoints_canvas', (canvas) => { 563 const paint = new CanvasKit.Paint(); 564 paint.setAntiAlias(true); 565 paint.setStyle(CanvasKit.PaintStyle.Stroke); 566 paint.setStrokeWidth(10); 567 paint.setColor(CanvasKit.Color(153, 204, 162, 0.82)); 568 569 const points = [32, 16, 48, 48, 16, 32]; 570 571 const caps = [CanvasKit.StrokeCap.Round, CanvasKit.StrokeCap.Square, 572 CanvasKit.StrokeCap.Butt]; 573 const joins = [CanvasKit.StrokeJoin.Round, CanvasKit.StrokeJoin.Miter, 574 CanvasKit.StrokeJoin.Bevel]; 575 const modes = [CanvasKit.PointMode.Points, CanvasKit.PointMode.Lines, 576 CanvasKit.PointMode.Polygon]; 577 578 for (let i = 0; i < caps.length; i++) { 579 paint.setStrokeCap(caps[i]); 580 paint.setStrokeJoin(joins[i]); 581 582 for (const m of modes) { 583 canvas.drawPoints(m, points, paint); 584 canvas.translate(64, 0); 585 } 586 // Try with the malloc approach. Note that the drawPoints 587 // will free the pointer when done. 588 const mPointsObj = CanvasKit.Malloc(Float32Array, 3*2); 589 const mPoints = mPointsObj.toTypedArray(); 590 mPoints.set([32, 16, 48, 48, 16, 32]); 591 592 // The obj from Malloc can be passed in instead of the typed array. 593 canvas.drawPoints(CanvasKit.PointMode.Polygon, mPointsObj, paint); 594 canvas.translate(-192, 64); 595 CanvasKit.Free(mPointsObj); 596 } 597 598 paint.delete(); 599 }); 600 601 gm('drawPoints_in_different_modes', (canvas) => { 602 // From https://bugs.chromium.org/p/skia/issues/detail?id=11012 603 const boxPaint = new CanvasKit.Paint(); 604 boxPaint.setStyle(CanvasKit.PaintStyle.Stroke); 605 boxPaint.setStrokeWidth(1); 606 607 const paint = new CanvasKit.Paint(); 608 paint.setStyle(CanvasKit.PaintStyle.Stroke); 609 paint.setStrokeWidth(5); 610 paint.setStrokeCap(CanvasKit.StrokeCap.Round); 611 paint.setColorInt(0xFF0000FF); // Blue 612 paint.setAntiAlias(true); 613 614 const points = Float32Array.of(40, 40, 80, 40, 120, 80, 160, 80); 615 616 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 617 canvas.drawPoints(CanvasKit.PointMode.Points, points, paint); 618 619 canvas.translate(0, 50); 620 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 621 canvas.drawPoints(CanvasKit.PointMode.Lines, points, paint); 622 623 canvas.translate(0, 50); 624 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 625 canvas.drawPoints(CanvasKit.PointMode.Polygon, points, paint); 626 627 // The control version using drawPath 628 canvas.translate(0, 50); 629 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 630 const path = new CanvasKit.Path(); 631 path.moveTo(40, 40); 632 path.lineTo(80, 40); 633 path.lineTo(120, 80); 634 path.lineTo(160, 80); 635 paint.setColorInt(0xFFFF0000); // RED 636 canvas.drawPath(path, paint); 637 638 paint.delete(); 639 path.delete(); 640 boxPaint.delete(); 641 }); 642 643 gm('drawImageNine_canvas', (canvas, fetchedByteBuffers) => { 644 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 645 expect(img).toBeTruthy(); 646 const paint = new CanvasKit.Paint(); 647 648 canvas.drawImageNine(img, CanvasKit.LTRBiRect(40, 40, 400, 300), 649 CanvasKit.LTRBRect(5, 5, 300, 650), CanvasKit.FilterMode.Nearest, paint); 650 paint.delete(); 651 img.delete(); 652 }, '/assets/mandrill_512.png'); 653 654 // This should be a nice, clear image. 655 gm('makeImageShaderCubic_canvas', (canvas, fetchedByteBuffers) => { 656 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 657 expect(img).toBeTruthy(); 658 const paint = new CanvasKit.Paint(); 659 const shader = img.makeShaderCubic(CanvasKit.TileMode.Decal, CanvasKit.TileMode.Clamp, 660 1/3 /*B*/, 1/3 /*C*/, 661 CanvasKit.Matrix.rotated(0.1)); 662 paint.setShader(shader); 663 664 canvas.drawPaint(paint); 665 paint.delete(); 666 shader.delete(); 667 img.delete(); 668 }, '/assets/mandrill_512.png'); 669 670 // This will look more blocky than the version above. 671 gm('makeImageShaderOptions_canvas', (canvas, fetchedByteBuffers) => { 672 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 673 expect(img).toBeTruthy(); 674 const imgWithMipMap = img.makeCopyWithDefaultMipmaps(); 675 const paint = new CanvasKit.Paint(); 676 const shader = imgWithMipMap.makeShaderOptions(CanvasKit.TileMode.Decal, 677 CanvasKit.TileMode.Clamp, 678 CanvasKit.FilterMode.Nearest, 679 CanvasKit.MipmapMode.Linear, 680 CanvasKit.Matrix.rotated(0.1)); 681 paint.setShader(shader); 682 683 canvas.drawPaint(paint); 684 paint.delete(); 685 shader.delete(); 686 img.delete(); 687 imgWithMipMap.delete(); 688 }, '/assets/mandrill_512.png'); 689 690 gm('drawvertices_canvas', (canvas) => { 691 const paint = new CanvasKit.Paint(); 692 paint.setAntiAlias(true); 693 694 const points = [0, 0, 250, 0, 100, 100, 0, 250]; 695 // 2d float color array 696 const colors = [CanvasKit.RED, CanvasKit.BLUE, 697 CanvasKit.YELLOW, CanvasKit.CYAN]; 698 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan, 699 points, null /*textureCoordinates*/, colors, false /*isVolatile*/); 700 701 const bounds = vertices.bounds(); 702 expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250)); 703 704 canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint); 705 vertices.delete(); 706 paint.delete(); 707 }); 708 709 gm('drawvertices_canvas_flat_floats', (canvas) => { 710 const paint = new CanvasKit.Paint(); 711 paint.setAntiAlias(true); 712 713 const points = [0, 0, 250, 0, 100, 100, 0, 250]; 714 // 1d float color array 715 const colors = Float32Array.of(...CanvasKit.RED, ...CanvasKit.BLUE, 716 ...CanvasKit.YELLOW, ...CanvasKit.CYAN); 717 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan, 718 points, null /*textureCoordinates*/, colors, false /*isVolatile*/); 719 720 const bounds = vertices.bounds(); 721 expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250)); 722 723 canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint); 724 vertices.delete(); 725 paint.delete(); 726 }); 727 728 gm('drawvertices_texture_canvas', (canvas, fetchedByteBuffers) => { 729 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 730 731 const paint = new CanvasKit.Paint(); 732 paint.setAntiAlias(true); 733 734 const points = [ 735 70, 170, 40, 90, 130, 150, 100, 50, 736 225, 150, 225, 60, 310, 180, 330, 100, 737 ]; 738 const textureCoordinates = [ 739 0, 240, 0, 0, 80, 240, 80, 0, 740 160, 240, 160, 0, 240, 240, 240, 0, 741 ]; 742 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TrianglesStrip, 743 points, textureCoordinates, null /* colors */, false /*isVolatile*/); 744 745 const shader = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror, 746 1/3 /*B*/, 1/3 /*C*/,); 747 paint.setShader(shader); 748 canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint); 749 750 shader.delete(); 751 vertices.delete(); 752 paint.delete(); 753 img.delete(); 754 }, '/assets/brickwork-texture.jpg'); 755 756 it('can change the 3x3 matrix on the canvas and read it back', () => { 757 const canvas = new CanvasKit.Canvas(); 758 759 let matr = canvas.getTotalMatrix(); 760 expect(matr).toEqual(CanvasKit.Matrix.identity()); 761 762 // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to 763 // make sure the 3x3 matrix properly sets these to 0 when it uses the same buffer. 764 canvas.save(); 765 const garbageMatrix = new Float32Array(16); 766 garbageMatrix.fill(-3); 767 canvas.concat(garbageMatrix); 768 canvas.restore(); 769 770 canvas.concat(CanvasKit.Matrix.rotated(Math.PI/4)); 771 const d = new DOMMatrix().translate(20, 10); 772 canvas.concat(d); 773 774 matr = canvas.getTotalMatrix(); 775 const expected = CanvasKit.Matrix.multiply( 776 CanvasKit.Matrix.rotated(Math.PI/4), 777 CanvasKit.Matrix.translated(20, 10) 778 ); 779 expect3x3MatricesToMatch(expected, matr); 780 781 // The 3x3 should be expanded into a 4x4, with identity in the 3rd row and column. 782 matr = canvas.getLocalToDevice(); 783 expect4x4MatricesToMatch([ 784 0.707106, -0.707106, 0, 7.071067, 785 0.707106, 0.707106, 0, 21.213203, 786 0 , 0 , 1, 0 , 787 0 , 0 , 0, 1 ], matr); 788 }); 789 790 it('can quickly tell if a rect is in the current clip region', () => { 791 const canvas = new CanvasKit.Canvas(200, 200); 792 793 canvas.save(); 794 const rejectWithNoClip = canvas.quickReject(CanvasKit.LTRBRect(10, 10, 20, 20)); 795 expect(rejectWithNoClip).toBeFalse(); 796 canvas.restore(); 797 798 canvas.save(); 799 canvas.clipRect(CanvasKit.LTRBRect(10, 10, 20, 20), CanvasKit.ClipOp.Intersect, false); 800 const rejectPartiallyInsideClip = canvas.quickReject(CanvasKit.LTRBRect(15, 15, 25, 25)); 801 expect(rejectPartiallyInsideClip).toBeFalse(); 802 const rejectEntirelyOutsideClip = canvas.quickReject(CanvasKit.LTRBRect(30, 30, 50, 50)); 803 expect(rejectEntirelyOutsideClip).toBeTrue(); 804 canvas.restore(); 805 }); 806 807 it('can accept a 3x2 matrix', () => { 808 const canvas = new CanvasKit.Canvas(); 809 810 let matr = canvas.getTotalMatrix(); 811 expect(matr).toEqual(CanvasKit.Matrix.identity()); 812 813 // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to 814 // make sure the 3x2 matrix properly sets these to 0 when it uses the same buffer. 815 canvas.save(); 816 const garbageMatrix = new Float32Array(16); 817 garbageMatrix.fill(-3); 818 canvas.concat(garbageMatrix); 819 canvas.restore(); 820 821 canvas.concat([1.4, -0.2, 12, 822 0.2, 1.4, 24]); 823 824 matr = canvas.getTotalMatrix(); 825 const expected = [1.4, -0.2, 12, 826 0.2, 1.4, 24, 827 0, 0, 1]; 828 expect3x3MatricesToMatch(expected, matr); 829 830 // The 3x2 should be expanded into a 4x4, with identity in the 3rd row and column 831 // and the perspective filled in. 832 matr = canvas.getLocalToDevice(); 833 expect4x4MatricesToMatch([ 834 1.4, -0.2, 0, 12, 835 0.2, 1.4, 0, 24, 836 0 , 0 , 1, 0, 837 0 , 0 , 0, 1], matr); 838 }); 839 840 it('can change the 4x4 matrix on the canvas and read it back', () => { 841 const canvas = new CanvasKit.Canvas(); 842 843 let matr = canvas.getLocalToDevice(); 844 expect(matr).toEqual(CanvasKit.M44.identity()); 845 846 canvas.concat(CanvasKit.M44.rotated([0, 1, 0], Math.PI/4)); 847 canvas.concat(CanvasKit.M44.rotated([1, 0, 1], Math.PI/8)); 848 849 const expected = CanvasKit.M44.multiply( 850 CanvasKit.M44.rotated([0, 1, 0], Math.PI/4), 851 CanvasKit.M44.rotated([1, 0, 1], Math.PI/8), 852 ); 853 854 expect4x4MatricesToMatch(expected, canvas.getLocalToDevice()); 855 // TODO(kjlubick) add test for DOMMatrix 856 // TODO(nifong) add more involved test for camera-related math. 857 }); 858 859 it('can change the device clip bounds to the canvas and read it back', () => { 860 // We need to use the Canvas constructor with a width/height or there is no maximum 861 // clip area, and all clipping will result in a clip of [0, 0, 0, 0] 862 const canvas = new CanvasKit.Canvas(300, 400); 863 let clip = canvas.getDeviceClipBounds(); 864 expect(clip).toEqual(Int32Array.of(0, 0, 300, 400)); 865 866 canvas.clipRect(CanvasKit.LTRBRect(10, 20, 30, 45), CanvasKit.ClipOp.Intersect, false); 867 canvas.getDeviceClipBounds(clip); 868 expect(clip).toEqual(Int32Array.of(10, 20, 30, 45)); 869 }); 870 871 gm('concat_with4x4_canvas', (canvas) => { 872 const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2); 873 const paint = new CanvasKit.Paint(); 874 paint.setAntiAlias(true); 875 876 // Rotate it a bit on all 3 major axis, centered on the screen. 877 // To play with rotations, see https://jsfiddle.skia.org/canvaskit/0525300405796aa87c3b84cc0d5748516fca0045d7d6d9c7840710ab771edcd4 878 const turn = CanvasKit.M44.multiply( 879 CanvasKit.M44.translated([CANVAS_WIDTH/2, 0, 0]), 880 CanvasKit.M44.rotated([1, 0, 0], Math.PI/3), 881 CanvasKit.M44.rotated([0, 1, 0], Math.PI/4), 882 CanvasKit.M44.rotated([0, 0, 1], Math.PI/16), 883 CanvasKit.M44.translated([-CANVAS_WIDTH/2, 0, 0]), 884 ); 885 canvas.concat(turn); 886 887 // Draw some stripes to help the eye detect the turn 888 const stripeWidth = 10; 889 paint.setColor(CanvasKit.BLACK); 890 for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) { 891 canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint); 892 } 893 894 paint.setColor(CanvasKit.YELLOW); 895 canvas.drawPath(path, paint); 896 paint.delete(); 897 path.delete(); 898 }); 899}); 900 901const expect3x3MatricesToMatch = (expected, actual) => { 902 expect(expected.length).toEqual(9); 903 expect(actual.length).toEqual(9); 904 for (let i = 0; i < expected.length; i++) { 905 expect(expected[i]).toBeCloseTo(actual[i], 5); 906 } 907}; 908 909const expect4x4MatricesToMatch = (expected, actual) => { 910 expect(expected.length).toEqual(16); 911 expect(actual.length).toEqual(16); 912 for (let i = 0; i < expected.length; i++) { 913 expect(expected[i]).toBeCloseTo(actual[i], 5); 914 } 915}; 916