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