1describe('Canvas Behavior', () => { 2 let container; 3 4 beforeEach(async () => { 5 await LoadCanvasKit; 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 24 canvas.drawLine(3, 10, 30, 15, paint); 25 const rrect = CanvasKit.RRectXY([5, 35, 45, 80], 15, 10); 26 canvas.drawRRect(rrect, paint); 27 28 canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint); 29 30 canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint); 31 32 const font = new CanvasKit.Font(null, 20); 33 canvas.drawText('this is ascii text', 5, 100, paint, font); 34 35 const blob = CanvasKit.TextBlob.MakeFromText('Unicode chars é É ص', font); 36 canvas.drawTextBlob(blob, 5, 130, paint); 37 38 font.delete(); 39 blob.delete(); 40 paint.delete(); 41 // See canvas2d for more API tests 42 }); 43 44 gm('effect_and_text_example', (canvas) => { 45 const path = starPath(CanvasKit); 46 const paint = new CanvasKit.Paint(); 47 48 const textPaint = new CanvasKit.Paint(); 49 textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); 50 textPaint.setAntiAlias(true); 51 52 const textFont = new CanvasKit.Font(null, 30); 53 54 const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], 1); 55 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)); 61 62 canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); 63 64 canvas.drawPath(path, paint); 65 canvas.drawText('This is text', 10, 280, textPaint, textFont); 66 67 dpe.delete(); 68 path.delete(); 69 paint.delete(); 70 textFont.delete(); 71 textPaint.delete(); 72 }); 73 74 gm('patheffects_canvas', (canvas) => { 75 canvas.clear(CanvasKit.WHITE); 76 const path = starPath(CanvasKit, 100, 100, 100); 77 const paint = new CanvasKit.Paint(); 78 79 const cornerEffect = CanvasKit.PathEffect.MakeCorner(10); 80 const discreteEffect = CanvasKit.PathEffect.MakeDiscrete(5, 10, 0); 81 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); 88 89 canvas.translate(200, 0); 90 91 paint.setPathEffect(discreteEffect); 92 canvas.drawPath(path, paint); 93 94 cornerEffect.delete(); 95 path.delete(); 96 paint.delete(); 97 }); 98 99 it('returns the depth of the save state stack', () => { 100 const canvas = new CanvasKit.Canvas(); 101 expect(canvas.getSaveCount()).toEqual(1); 102 canvas.save(); 103 canvas.save(); 104 canvas.restore(); 105 canvas.save(); 106 canvas.save(); 107 expect(canvas.getSaveCount()).toEqual(4); 108 // does nothing, by the SkCanvas API 109 canvas.restoreToCount(500); 110 expect(canvas.getSaveCount()).toEqual(4); 111 canvas.restore(); 112 expect(canvas.getSaveCount()).toEqual(3); 113 canvas.save(); 114 canvas.restoreToCount(2); 115 expect(canvas.getSaveCount()).toEqual(2); 116 }); 117 118 gm('circle_canvas', (canvas) => { 119 const path = starPath(CanvasKit); 120 121 const paint = new CanvasKit.Paint(); 122 123 paint.setStyle(CanvasKit.PaintStyle.Stroke); 124 paint.setStrokeWidth(5.0); 125 paint.setAntiAlias(true); 126 paint.setColor(CanvasKit.CYAN); 127 128 canvas.clear(CanvasKit.WHITE); 129 130 canvas.drawCircle(30, 50, 15, paint); 131 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); 136 137 path.delete(); 138 paint.delete(); 139 }); 140 141 gm('rrect_canvas', (canvas) => { 142 const path = starPath(CanvasKit); 143 144 const paint = new CanvasKit.Paint(); 145 146 paint.setStyle(CanvasKit.PaintStyle.Stroke); 147 paint.setStrokeWidth(3.0); 148 paint.setAntiAlias(true); 149 paint.setColor(CanvasKit.BLACK); 150 151 canvas.clear(CanvasKit.WHITE); 152 153 canvas.drawRRect(CanvasKit.RRectXY( 154 CanvasKit.LTRBRect(10, 10, 50, 50), 5, 10), paint); 155 156 canvas.drawRRect(CanvasKit.RRectXY( 157 CanvasKit.LTRBRect(60, 10, 110, 50), 10, 5), paint); 158 159 canvas.drawRRect(CanvasKit.RRectXY( 160 CanvasKit.LTRBRect(10, 60, 210, 260), 0, 30), paint); 161 162 canvas.drawRRect(CanvasKit.RRectXY( 163 CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30), paint); 164 165 path.delete(); 166 paint.delete(); 167 }); 168 169 gm('rrect_8corners_canvas', (canvas) => { 170 const path = starPath(CanvasKit); 171 172 const paint = new CanvasKit.Paint(); 173 174 paint.setStyle(CanvasKit.PaintStyle.Stroke); 175 paint.setStrokeWidth(3.0); 176 paint.setAntiAlias(true); 177 paint.setColor(CanvasKit.BLACK); 178 179 canvas.clear(CanvasKit.WHITE); 180 181 canvas.drawRRect([10, 10, 210, 210, 182 // top left corner, going clockwise 183 10, 30, 184 30, 10, 185 50, 75, 186 120, 120, 187 ], paint); 188 189 path.delete(); 190 paint.delete(); 191 }); 192 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); 196 197 const paint = new CanvasKit.Paint(); 198 199 paint.setStyle(CanvasKit.PaintStyle.Stroke); 200 paint.setStrokeWidth(3.0); 201 paint.setAntiAlias(true); 202 paint.setColor(CanvasKit.BLACK); 203 204 canvas.clear(CanvasKit.WHITE); 205 206 const rrect = CanvasKit.Malloc(Float32Array, 12); 207 rrect.toTypedArray().set([10, 10, 210, 210, 208 // top left corner, going clockwise 209 10, 30, 210 30, 10, 211 50, 75, 212 120, 120, 213 ]); 214 215 canvas.drawRRect(rrect, paint); 216 217 CanvasKit.Free(rrect); 218 path.delete(); 219 paint.delete(); 220 }); 221 222 gm('drawDRRect_canvas', (canvas) => { 223 const path = starPath(CanvasKit); 224 225 const paint = new CanvasKit.Paint(); 226 227 paint.setStyle(CanvasKit.PaintStyle.Fill); 228 paint.setStrokeWidth(3.0); 229 paint.setAntiAlias(true); 230 paint.setColor(CanvasKit.BLACK); 231 232 canvas.clear(CanvasKit.WHITE); 233 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); 236 237 canvas.drawDRRect(outer, inner, paint); 238 239 path.delete(); 240 paint.delete(); 241 }); 242 243 gm('colorfilters_canvas', (canvas) => { 244 const paint = new CanvasKit.Paint(); 245 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); 251 252 paint.setStyle(CanvasKit.PaintStyle.Fill); 253 paint.setAntiAlias(true); 254 255 canvas.clear(CanvasKit.Color(230, 230, 230)); 256 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); 263 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); 270 271 const mat = CanvasKit.ColorFilter.MakeMatrix(cm); 272 const final = CanvasKit.ColorFilter.MakeCompose(mat, lerp); 273 274 paint.setColorFilter(final) 275 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint); 276 277 paint.delete(); 278 blue.delete(); 279 red.delete(); 280 lerp.delete(); 281 final.delete(); 282 }); 283 284 gm('blendmodes_canvas', (canvas) => { 285 canvas.clear(CanvasKit.WHITE); 286 287 const blendModeNames = Object.keys(CanvasKit.BlendMode).filter((key) => key !== 'values'); 288 289 const PASTEL_MUSTARD_YELLOW = CanvasKit.Color(248, 213, 85, 1.0); 290 const PASTEL_SKY_BLUE = CanvasKit.Color(74, 174, 245, 1.0); 291 292 const shapePaint = new CanvasKit.Paint(); 293 shapePaint.setColor(PASTEL_MUSTARD_YELLOW); 294 shapePaint.setAntiAlias(true); 295 296 const textPaint = new CanvasKit.Paint(); 297 textPaint.setAntiAlias(true); 298 299 const textFont = new CanvasKit.Font(null, 10); 300 301 let x = 10; 302 let y = 20; 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); 308 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); 314 315 x += 90; 316 if (x > 500) { 317 x = 10; 318 y += 110; 319 } 320 } 321 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); 328 329 // Draw checkerboard squares 330 const numberOfColumns = (x2 - x1) / CHECKERBOARD_SQUARE_SIZE; 331 const numberOfRows = (y2 - y1) / CHECKERBOARD_SQUARE_SIZE 332 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; 337 338 if ((rowIsEven && !columnIsEven) || (!rowIsEven && columnIsEven)) { 339 drawRectangle( 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), 344 GREY 345 ); 346 } 347 } 348 } 349 } 350 351 function drawRectangle(x1, y1, x2, y2, color, blendMode=CanvasKit.BlendMode.srcOver) { 352 canvas.save(); 353 canvas.clipRect(CanvasKit.LTRBRect(x1, y1, x2, y2), CanvasKit.ClipOp.Intersect, true); 354 canvas.drawColor(color, blendMode); 355 canvas.restore(); 356 } 357 }); 358 359 gm('colorfilters_malloc_canvas', (canvas) => { 360 const paint = new CanvasKit.Paint(); 361 362 const src = [ 363 0.8, 0.45, 2, 0, 20, 364 0.53, -0.918, 0.566, 0, 0, 365 0.53, -0.918, -0.566, 0, -10, 366 0, 0, 0, 0.8, 0, 367 ] 368 const colorObj = new CanvasKit.Malloc(Float32Array, 20); 369 const cm = colorObj.toTypedArray(); 370 for (i in src) { 371 cm[i] = src[i]; 372 } 373 // MakeMatrix will free the malloc'd array when it is done with it. 374 const final = CanvasKit.ColorFilter.MakeMatrix(cm); 375 376 paint.setColorFilter(final) 377 canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint); 378 379 CanvasKit.Free(colorObj); 380 paint.delete(); 381 final.delete(); 382 }); 383 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); 389 390 canvas.save(); 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); 394 canvas.restore(); 395 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); 400 401 path.delete(); 402 paint.delete(); 403 }); 404 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); 412 413 const thirtyBluePaint = new CanvasKit.Paint(); 414 thirtyBluePaint.setColor(CanvasKit.BLUE); 415 thirtyBluePaint.setAlphaf(0.3); 416 417 const alpha = new CanvasKit.Paint(); 418 alpha.setAlphaf(0.3); 419 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); 425 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); 431 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)); 440 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); 446 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); 453 454 canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint); 455 canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint); 456 457 canvas.restore(); 458 459 redPaint.delete(); 460 solidBluePaint.delete(); 461 thirtyBluePaint.delete(); 462 alpha.delete(); 463 }); 464 465 // identical to the test above, except the save layer only has the paint, not 466 // the rectangle. 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); 473 474 const thirtyBluePaint = new CanvasKit.Paint(); 475 thirtyBluePaint.setColor(CanvasKit.BLUE); 476 thirtyBluePaint.setAlphaf(0.3); 477 478 const alpha = new CanvasKit.Paint(); 479 alpha.setAlphaf(0.3); 480 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); 486 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); 492 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); 498 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); 504 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); 511 512 canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint); 513 canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint); 514 515 canvas.restore(); 516 517 redPaint.delete(); 518 solidBluePaint.delete(); 519 thirtyBluePaint.delete(); 520 alpha.delete(); 521 }); 522 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); 528 canvas.scale(8, 8); 529 const redPaint = new CanvasKit.Paint(); 530 redPaint.setColor(CanvasKit.RED); 531 redPaint.setAntiAlias(true); 532 canvas.drawCircle(21, 21, 8, redPaint); 533 534 const bluePaint = new CanvasKit.Paint(); 535 bluePaint.setColor(CanvasKit.BLUE); 536 canvas.drawCircle(31, 21, 8, bluePaint); 537 538 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null); 539 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); 544 canvas.restore(); 545 546 blurIF.delete(); 547 redPaint.delete(); 548 bluePaint.delete(); 549 }); 550 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)); 558 559 const points = [32, 16, 48, 48, 16, 32]; 560 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]; 567 568 for (let i = 0; i < caps.length; i++) { 569 paint.setStrokeCap(caps[i]); 570 paint.setStrokeJoin(joins[i]); 571 572 for (const m of modes) { 573 canvas.drawPoints(m, points, paint); 574 canvas.translate(64, 0); 575 } 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]); 581 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); 586 } 587 588 paint.delete(); 589 }); 590 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); 597 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); 604 605 const points = Float32Array.of(40, 40, 80, 40, 120, 80, 160, 80); 606 607 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 608 canvas.drawPoints(CanvasKit.PointMode.Points, points, paint); 609 610 canvas.translate(0, 50); 611 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 612 canvas.drawPoints(CanvasKit.PointMode.Lines, points, paint); 613 614 canvas.translate(0, 50); 615 canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint); 616 canvas.drawPoints(CanvasKit.PointMode.Polygon, points, paint); 617 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(); 622 path.moveTo(40, 40); 623 path.lineTo(80, 40); 624 path.lineTo(120, 80); 625 path.lineTo(160, 80); 626 paint.setColorInt(0xFFFF0000); // RED 627 canvas.drawPath(path, paint); 628 629 paint.delete(); 630 path.delete(); 631 boxPaint.delete(); 632 }); 633 634 gm('drawImageNine_canvas', (canvas, fetchedByteBuffers) => { 635 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 636 expect(img).toBeTruthy(); 637 638 canvas.clear(CanvasKit.WHITE); 639 const paint = new CanvasKit.Paint(); 640 641 canvas.drawImageNine(img, CanvasKit.LTRBiRect(40, 40, 400, 300), 642 CanvasKit.LTRBRect(5, 5, 300, 650), CanvasKit.FilterMode.Nearest, paint); 643 paint.delete(); 644 img.delete(); 645 }, '/assets/mandrill_512.png'); 646 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(); 651 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); 658 659 canvas.drawPaint(paint); 660 paint.delete(); 661 shader.delete(); 662 img.delete(); 663 }, '/assets/mandrill_512.png'); 664 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(); 670 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); 679 680 canvas.drawPaint(paint); 681 paint.delete(); 682 shader.delete(); 683 img.delete(); 684 imgWithMipMap.delete(); 685 }, '/assets/mandrill_512.png'); 686 687 gm('drawvertices_canvas', (canvas) => { 688 const paint = new CanvasKit.Paint(); 689 paint.setAntiAlias(true); 690 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*/); 697 698 const bounds = vertices.bounds(); 699 expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250)); 700 701 canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint); 702 vertices.delete(); 703 paint.delete(); 704 }); 705 706 gm('drawvertices_canvas_flat_floats', (canvas) => { 707 const paint = new CanvasKit.Paint(); 708 paint.setAntiAlias(true); 709 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*/); 716 717 const bounds = vertices.bounds(); 718 expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250)); 719 720 canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint); 721 vertices.delete(); 722 paint.delete(); 723 }); 724 725 gm('drawvertices_texture_canvas', (canvas, fetchedByteBuffers) => { 726 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 727 728 const paint = new CanvasKit.Paint(); 729 paint.setAntiAlias(true); 730 731 const points = [ 732 70, 170, 40, 90, 130, 150, 100, 50, 733 225, 150, 225, 60, 310, 180, 330, 100, 734 ]; 735 const textureCoordinates = [ 736 0, 240, 0, 0, 80, 240, 80, 0, 737 160, 240, 160, 0, 240, 240, 240, 0, 738 ]; 739 const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TrianglesStrip, 740 points, textureCoordinates, null /* colors */, false /*isVolatile*/); 741 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); 746 747 shader.delete(); 748 vertices.delete(); 749 paint.delete(); 750 img.delete(); 751 }, '/assets/brickwork-texture.jpg'); 752 753 it('can change the 3x3 matrix on the canvas and read it back', () => { 754 const canvas = new CanvasKit.Canvas(); 755 756 let matr = canvas.getTotalMatrix(); 757 expect(matr).toEqual(CanvasKit.Matrix.identity()); 758 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. 761 canvas.save(); 762 const garbageMatrix = new Float32Array(16); 763 garbageMatrix.fill(-3); 764 canvas.concat(garbageMatrix); 765 canvas.restore(); 766 767 canvas.concat(CanvasKit.Matrix.rotated(Math.PI/4)); 768 const d = new DOMMatrix().translate(20, 10); 769 canvas.concat(d); 770 771 matr = canvas.getTotalMatrix(); 772 const expected = CanvasKit.Matrix.multiply( 773 CanvasKit.Matrix.rotated(Math.PI/4), 774 CanvasKit.Matrix.translated(20, 10) 775 ); 776 expect3x3MatricesToMatch(expected, matr); 777 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, 783 0 , 0 , 0, 0 , 784 0 , 0 , 0, 1 ], matr); 785 }); 786 787 it('can accept a 3x2 matrix', () => { 788 const canvas = new CanvasKit.Canvas(); 789 790 let matr = canvas.getTotalMatrix(); 791 expect(matr).toEqual(CanvasKit.Matrix.identity()); 792 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. 795 canvas.save(); 796 const garbageMatrix = new Float32Array(16); 797 garbageMatrix.fill(-3); 798 canvas.concat(garbageMatrix); 799 canvas.restore(); 800 801 canvas.concat([1.4, -0.2, 12, 802 0.2, 1.4, 24]); 803 804 matr = canvas.getTotalMatrix(); 805 const expected = [1.4, -0.2, 12, 806 0.2, 1.4, 24, 807 0, 0, 1]; 808 expect3x3MatricesToMatch(expected, matr); 809 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([ 814 1.4, -0.2, 0, 12, 815 0.2, 1.4, 0, 24, 816 0 , 0 , 0, 0, 817 0 , 0 , 0, 1], matr); 818 }); 819 820 it('can mark a CTM and retrieve it', () => { 821 const canvas = new CanvasKit.Canvas(); 822 823 canvas.concat(CanvasKit.M44.rotated([0, 1, 0], Math.PI/4)); 824 canvas.concat(CanvasKit.M44.rotated([1, 0, 1], Math.PI/8)); 825 canvas.markCTM('krispykreme'); 826 827 const expected = CanvasKit.M44.multiply( 828 CanvasKit.M44.rotated([0, 1, 0], Math.PI/4), 829 CanvasKit.M44.rotated([1, 0, 1], Math.PI/8), 830 ); 831 832 expect4x4MatricesToMatch(expected, canvas.findMarkedCTM('krispykreme')); 833 }); 834 835 it('returns null for an invalid CTM marker', () => { 836 const canvas = new CanvasKit.Canvas(); 837 expect(canvas.findMarkedCTM('dunkindonuts')).toBeNull(); 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 gm('concat_with4x4_canvas', (canvas) => { 860 const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2); 861 const paint = new CanvasKit.Paint(); 862 paint.setAntiAlias(true); 863 canvas.clear(CanvasKit.WHITE); 864 865 // Rotate it a bit on all 3 major axis, centered on the screen. 866 // To play with rotations, see https://jsfiddle.skia.org/canvaskit/0525300405796aa87c3b84cc0d5748516fca0045d7d6d9c7840710ab771edcd4 867 const turn = CanvasKit.M44.multiply( 868 CanvasKit.M44.translated([CANVAS_WIDTH/2, 0, 0]), 869 CanvasKit.M44.rotated([1, 0, 0], Math.PI/3), 870 CanvasKit.M44.rotated([0, 1, 0], Math.PI/4), 871 CanvasKit.M44.rotated([0, 0, 1], Math.PI/16), 872 CanvasKit.M44.translated([-CANVAS_WIDTH/2, 0, 0]), 873 ); 874 canvas.concat(turn); 875 876 // Draw some stripes to help the eye detect the turn 877 const stripeWidth = 10; 878 paint.setColor(CanvasKit.BLACK); 879 for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) { 880 canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint); 881 } 882 883 paint.setColor(CanvasKit.YELLOW); 884 canvas.drawPath(path, paint); 885 paint.delete(); 886 path.delete(); 887 }); 888 889 gm('particles_canvas', (canvas) => { 890 const curveParticles = { 891 'MaxCount': 1000, 892 'Drawable': { 893 'Type': 'SkCircleDrawable', 894 'Radius': 2 895 }, 896 'Code': [ 897 `void effectSpawn(inout Effect effect) { 898 effect.rate = 200; 899 effect.color = float4(1, 0, 0, 1); 900 } 901 void spawn(inout Particle p) { 902 p.lifetime = 3 + rand(p.seed); 903 p.vel.y = -50; 904 } 905 906 void update(inout Particle p) { 907 float w = mix(15, 3, p.age); 908 p.pos.x = sin(radians(p.age * 320)) * mix(25, 10, p.age) + mix(-w, w, rand(p.seed)); 909 if (rand(p.seed) < 0.5) { p.pos.x = -p.pos.x; } 910 911 p.color.g = (mix(75, 220, p.age) + mix(-30, 30, rand(p.seed))) / 255; 912 }` 913 ], 914 'Bindings': [] 915 }; 916 917 const particles = CanvasKit.MakeParticles(JSON.stringify(curveParticles)); 918 particles.start(0, true); 919 particles.setPosition([0, 0]); 920 921 const paint = new CanvasKit.Paint(); 922 paint.setAntiAlias(true); 923 paint.setColor(CanvasKit.WHITE); 924 const font = new CanvasKit.Font(null, 12); 925 926 canvas.clear(CanvasKit.BLACK); 927 928 // Draw a 5x5 set of different times in the particle system 929 // like a filmstrip of motion of particles. 930 const LEFT_MARGIN = 90; 931 const TOP_MARGIN = 100; 932 for (let row = 0; row < 5; row++) { 933 for (let column = 0; column < 5; column++) { 934 canvas.save(); 935 canvas.translate(LEFT_MARGIN + column*100, TOP_MARGIN + row*100); 936 937 // Time moves in row-major order in increments of 0.02. 938 const particleTime = row/10 + column/50; 939 940 canvas.drawText('time ' + particleTime.toFixed(2), -30, 20, paint, font); 941 particles.update(particleTime); 942 943 particles.draw(canvas); 944 canvas.restore(); 945 } 946 } 947 }); 948}); 949 950const expect3x3MatricesToMatch = (expected, actual) => { 951 expect(expected.length).toEqual(9); 952 expect(actual.length).toEqual(9); 953 for (let i = 0; i < expected.length; i++) { 954 expect(expected[i]).toBeCloseTo(actual[i], 5); 955 } 956}; 957 958const expect4x4MatricesToMatch = (expected, actual) => { 959 expect(expected.length).toEqual(16); 960 expect(actual.length).toEqual(16); 961 for (let i = 0; i < expected.length; i++) { 962 expect(expected[i]).toBeCloseTo(actual[i], 5); 963 } 964}; 965