1describe('Core 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('picture_test', (canvas) => { 18 const spr = new CanvasKit.PictureRecorder(); 19 const bounds = CanvasKit.LTRBRect(0, 0, 400, 120); 20 const rcanvas = spr.beginRecording(bounds); 21 const paint = new CanvasKit.Paint(); 22 paint.setStrokeWidth(2.0); 23 paint.setAntiAlias(true); 24 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 25 paint.setStyle(CanvasKit.PaintStyle.Stroke); 26 27 rcanvas.drawRRect(CanvasKit.RRectXY([5, 35, 45, 80], 15, 10), paint); 28 29 const font = new CanvasKit.Font(null, 20); 30 rcanvas.drawText('this picture has a round rect', 5, 100, paint, font); 31 const pic = spr.finishRecordingAsPicture(); 32 spr.delete(); 33 paint.delete(); 34 35 canvas.drawPicture(pic); 36 const paint2 = new CanvasKit.Paint(); 37 paint2.setColor(CanvasKit.RED); 38 paint2.setStyle(CanvasKit.PaintStyle.Stroke); 39 canvas.drawRect(bounds, paint2); 40 41 const bytes = pic.serialize(); 42 expect(bytes).toBeTruthy(); 43 44 45 const matr = CanvasKit.Matrix.scaled(0.33, 0.33); 46 // Give a 5 pixel margin between the original content. 47 const tileRect = CanvasKit.LTRBRect(-5, -5, 405, 125); 48 const shader = pic.makeShader(CanvasKit.TileMode.Mirror, CanvasKit.TileMode.Mirror, 49 CanvasKit.FilterMode.Linear, matr, tileRect); 50 paint2.setStyle(CanvasKit.PaintStyle.Fill); 51 paint2.setShader(shader); 52 canvas.drawRect(CanvasKit.LTRBRect(0, 150, CANVAS_WIDTH, CANVAS_HEIGHT), paint2); 53 54 paint2.delete(); 55 shader.delete(); 56 pic.delete(); 57 }); 58 59 const uIntColorToCanvasKitColor = (c) => { 60 return CanvasKit.Color( 61 (c >> 16) & 0xFF, 62 (c >> 8) & 0xFF, 63 (c >> 0) & 0xFF, 64 ((c >> 24) & 0xFF) / 255 65 ); 66 }; 67 68 it('can compute tonal colors', () => { 69 const input = { 70 ambient: CanvasKit.BLUE, 71 spot: CanvasKit.RED, 72 }; 73 const out = CanvasKit.computeTonalColors(input); 74 expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK); 75 const expectedSpot = [0.173, 0, 0, 0.969]; 76 expect(out.spot.length).toEqual(4); 77 expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3); 78 expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3); 79 expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3); 80 expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3); 81 }); 82 83 it('can compute tonal colors with malloced values', () => { 84 const ambientColor = CanvasKit.Malloc(Float32Array, 4); 85 ambientColor.toTypedArray().set(CanvasKit.BLUE); 86 const spotColor = CanvasKit.Malloc(Float32Array, 4); 87 spotColor.toTypedArray().set(CanvasKit.RED); 88 const input = { 89 ambient: ambientColor, 90 spot: spotColor, 91 }; 92 const out = CanvasKit.computeTonalColors(input); 93 expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK); 94 const expectedSpot = [0.173, 0, 0, 0.969]; 95 expect(out.spot.length).toEqual(4); 96 expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3); 97 expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3); 98 expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3); 99 expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3); 100 }); 101 102 // This helper is used for all the MakeImageFromEncoded tests. 103 // TODO(kjlubick): rewrite this and callers to use gm 104 function decodeAndDrawSingleFrameImage(imgName, goldName, done) { 105 const imgPromise = fetch(imgName) 106 .then((response) => response.arrayBuffer()); 107 Promise.all([imgPromise, EverythingLoaded]).then((values) => { 108 const imgData = values[0]; 109 expect(imgData).toBeTruthy(); 110 catchException(done, () => { 111 let img = CanvasKit.MakeImageFromEncoded(imgData); 112 expect(img).toBeTruthy(); 113 const surface = CanvasKit.MakeCanvasSurface('test'); 114 expect(surface).toBeTruthy('Could not make surface'); 115 if (!surface) { 116 done(); 117 return; 118 } 119 const canvas = surface.getCanvas(); 120 let paint = new CanvasKit.Paint(); 121 canvas.drawImage(img, 0, 0, paint); 122 123 paint.delete(); 124 img.delete(); 125 126 reportSurface(surface, goldName, done); 127 })(); 128 }); 129 } 130 131 it('can decode and draw a png', (done) => { 132 decodeAndDrawSingleFrameImage('/assets/mandrill_512.png', 'drawImage_png', done); 133 }); 134 135 it('can decode and draw a jpg', (done) => { 136 decodeAndDrawSingleFrameImage('/assets/mandrill_h1v1.jpg', 'drawImage_jpg', done); 137 }); 138 139 it('can decode and draw a (still) gif', (done) => { 140 decodeAndDrawSingleFrameImage('/assets/flightAnim.gif', 'drawImage_gif', done); 141 }); 142 143 it('can decode and draw a still webp', (done) => { 144 decodeAndDrawSingleFrameImage('/assets/color_wheel.webp', 'drawImage_webp', done); 145 }); 146 147 it('can readPixels from an Image', (done) => { 148 const imgPromise = fetch('/assets/mandrill_512.png') 149 .then((response) => response.arrayBuffer()); 150 Promise.all([imgPromise, EverythingLoaded]).then((values) => { 151 const imgData = values[0]; 152 expect(imgData).toBeTruthy(); 153 catchException(done, () => { 154 let img = CanvasKit.MakeImageFromEncoded(imgData); 155 expect(img).toBeTruthy(); 156 const imageInfo = { 157 alphaType: CanvasKit.AlphaType.Unpremul, 158 colorType: CanvasKit.ColorType.RGBA_8888, 159 colorSpace: CanvasKit.ColorSpace.SRGB, 160 width: img.width(), 161 height: img.height(), 162 }; 163 const rowBytes = 4 * img.width(); 164 165 const pixels = img.readPixels(0, 0, imageInfo); 166 // We know the image is 512 by 512 pixels in size, each pixel 167 // requires 4 bytes (R, G, B, A). 168 expect(pixels.length).toEqual(512 * 512 * 4); 169 170 // Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A) 171 const rdsData = CanvasKit.Malloc(Uint8Array, 512 * 5*512 * 4); 172 const pixels2 = rdsData.toTypedArray(); 173 pixels2[0] = 127; // sentinel value, should be overwritten by readPixels. 174 img.readPixels(0, 0, imageInfo, rdsData, rowBytes); 175 expect(rdsData.toTypedArray()[0]).toEqual(pixels[0]); 176 177 img.delete(); 178 CanvasKit.Free(rdsData); 179 done(); 180 })(); 181 }); 182 }); 183 184 gm('drawDrawable_animated_gif', (canvas, fetchedByteBuffers) => { 185 let aImg = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]); 186 expect(aImg).toBeTruthy(); 187 expect(aImg.getRepetitionCount()).toEqual(-1); // infinite loop 188 expect(aImg.width()).toEqual(320); 189 expect(aImg.height()).toEqual(240); 190 expect(aImg.getFrameCount()).toEqual(60); 191 expect(aImg.currentFrameDuration()).toEqual(60); 192 193 const drawCurrentFrame = function(x, y) { 194 let img = aImg.makeImageAtCurrentFrame(); 195 canvas.drawImage(img, x, y, null); 196 img.delete(); 197 } 198 199 drawCurrentFrame(0, 0); 200 201 let c = aImg.decodeNextFrame(); 202 expect(c).not.toEqual(-1); 203 drawCurrentFrame(300, 0); 204 for(let i = 0; i < 10; i++) { 205 c = aImg.decodeNextFrame(); 206 expect(c).not.toEqual(-1); 207 } 208 drawCurrentFrame(0, 300); 209 for(let i = 0; i < 10; i++) { 210 c = aImg.decodeNextFrame(); 211 expect(c).not.toEqual(-1); 212 } 213 drawCurrentFrame(300, 300); 214 215 aImg.delete(); 216 }, '/assets/flightAnim.gif'); 217 218 gm('exif_orientation', (canvas, fetchedByteBuffers) => { 219 const paint = new CanvasKit.Paint(); 220 const font = new CanvasKit.Font(null, 14); 221 canvas.drawText('The following heart should be rotated 90 CCW due to exif.', 222 5, 25, paint, font); 223 224 // TODO(kjlubick) it would be nice to also to test MakeAnimatedImageFromEncoded but 225 // I could not create a sample animated image that worked. 226 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 227 expect(img).toBeTruthy(); 228 canvas.drawImage(img, 5, 35, null); 229 230 img.delete(); 231 paint.delete(); 232 font.delete(); 233 }, '/assets/exif_rotated_heart.jpg'); 234 235 gm('1x4_from_scratch', (canvas) => { 236 const paint = new CanvasKit.Paint(); 237 238 // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with 239 // the colors listed below. 240 const pixels = Uint8Array.from([ 241 255, 0, 0, 255, // opaque red 242 0, 255, 0, 255, // opaque green 243 0, 0, 255, 255, // opaque blue 244 255, 0, 255, 100, // transparent purple 245 ]); 246 const img = CanvasKit.MakeImage({ 247 'width': 1, 248 'height': 4, 249 'alphaType': CanvasKit.AlphaType.Unpremul, 250 'colorType': CanvasKit.ColorType.RGBA_8888, 251 'colorSpace': CanvasKit.ColorSpace.SRGB 252 }, pixels, 4); 253 canvas.drawImage(img, 1, 1, paint); 254 255 const info = img.getImageInfo(); 256 expect(info).toEqual({ 257 'width': 1, 258 'height': 4, 259 'alphaType': CanvasKit.AlphaType.Unpremul, 260 'colorType': CanvasKit.ColorType.RGBA_8888, 261 }); 262 const cs = img.getColorSpace(); 263 expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy(); 264 265 cs.delete(); 266 img.delete(); 267 paint.delete(); 268 }); 269 270 gm('draw_atlas_with_builders', (canvas, fetchedByteBuffers) => { 271 const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 272 expect(atlas).toBeTruthy(); 273 274 const paint = new CanvasKit.Paint(); 275 paint.setColor(CanvasKit.Color(0, 0, 0, 0.8)); 276 277 // Allocate space for 4 rectangles. 278 const srcs = CanvasKit.Malloc(Float32Array, 16); 279 srcs.toTypedArray().set([ 280 0, 0, 256, 256, // LTRB 281 256, 0, 512, 256, 282 0, 256, 256, 512, 283 256, 256, 512, 512 284 ]); 285 286 // Allocate space for 4 RSXForms. 287 const dsts = CanvasKit.Malloc(Float32Array, 16); 288 dsts.toTypedArray().set([ 289 0.5, 0, 20, 20, // scos, ssin, tx, ty 290 0.5, 0, 300, 20, 291 0.5, 0, 20, 300, 292 0.5, 0, 300, 300 293 ]); 294 295 // Allocate space for 4 colors. 296 const colors = new CanvasKit.Malloc(Uint32Array, 4); 297 colors.toTypedArray().set([ 298 CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green 299 CanvasKit.ColorAsInt( 51, 51, 191, 128), // light blue 300 CanvasKit.ColorAsInt( 0, 0, 0, 128), 301 CanvasKit.ColorAsInt(256, 256, 256, 128), 302 ]); 303 304 canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors); 305 306 atlas.delete(); 307 CanvasKit.Free(srcs); 308 CanvasKit.Free(dsts); 309 CanvasKit.Free(colors); 310 paint.delete(); 311 }, '/assets/mandrill_512.png'); 312 313 gm('draw_atlas_with_arrays', (canvas, fetchedByteBuffers) => { 314 const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 315 expect(atlas).toBeTruthy(); 316 317 const paint = new CanvasKit.Paint(); 318 paint.setColor(CanvasKit.Color(0, 0, 0, 0.8)); 319 320 const srcs = [ 321 0, 0, 8, 8, 322 8, 0, 16, 8, 323 0, 8, 8, 16, 324 8, 8, 16, 16, 325 ]; 326 327 const dsts = [ 328 10, 0, 0, 0, 329 10, 0, 100, 0, 330 10, 0, 0, 100, 331 10, 0, 100, 100, 332 ]; 333 334 const colors = Uint32Array.of( 335 CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green 336 CanvasKit.ColorAsInt( 51, 51, 191, 128), // light blue 337 CanvasKit.ColorAsInt( 0, 0, 0, 128), 338 CanvasKit.ColorAsInt(255, 255, 255, 128), 339 ); 340 341 // sampling for each of the 4 instances 342 const sampling = [ 343 null, 344 {B: 0, C: 0.5}, 345 {filter: CanvasKit.FilterMode.Nearest, mipmap: CanvasKit.MipmapMode.None}, 346 {filter: CanvasKit.FilterMode.Linear, mipmap: CanvasKit.MipmapMode.Nearest}, 347 ]; 348 349 // positioning for each of the 4 instances 350 const offset = [ 351 [0, 0], [256, 0], [0, 256], [256, 256] 352 ]; 353 354 canvas.translate(20, 20); 355 for (let i = 0; i < 4; ++i) { 356 canvas.save(); 357 canvas.translate(offset[i][0], offset[i][1]); 358 canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.SrcOver, colors, 359 sampling[i]); 360 canvas.restore(); 361 } 362 363 atlas.delete(); 364 paint.delete(); 365 }, '/assets/mandrill_16.png'); 366 367 gm('draw_patch', (canvas, fetchedByteBuffers) => { 368 const image = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 369 expect(image).toBeTruthy(); 370 371 const paint = new CanvasKit.Paint(); 372 const shader = image.makeShaderOptions(CanvasKit.TileMode.Clamp, 373 CanvasKit.TileMode.Clamp, 374 CanvasKit.FilterMode.Linear, 375 CanvasKit.MipmapMode.None); 376 const cubics = [0,0, 80,50, 160,50, 377 240,0, 200,80, 200,160, 378 240,240, 160,160, 80,240, 379 0,240, 50,160, 0,80]; 380 const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN]; 381 const texs = [0,0, 16,0, 16,16, 0,16]; 382 383 const params = [ 384 [ 0, 0, colors, null, null, CanvasKit.BlendMode.Dst], 385 [256, 0, null, texs, shader, null], 386 [ 0, 256, colors, texs, shader, null], 387 [256, 256, colors, texs, shader, CanvasKit.BlendMode.Screen], 388 ]; 389 for (const p of params) { 390 paint.setShader(p[4]); 391 canvas.save(); 392 canvas.translate(p[0], p[1]); 393 canvas.drawPatch(cubics, p[2], p[3], p[5], paint); 394 canvas.restore(); 395 } 396 paint.delete(); 397 }, '/assets/mandrill_16.png'); 398 399 gm('draw_glyphs', (canvas) => { 400 401 const paint = new CanvasKit.Paint(); 402 const font = new CanvasKit.Font(null, 24); 403 paint.setAntiAlias(true); 404 405 const DIM = 16; // row/col count for the grid 406 const GAP = 32; // spacing between each glyph 407 const glyphs = new Uint16Array(256); 408 const positions = new Float32Array(256*2); 409 for (let i = 0; i < 256; ++i) { 410 glyphs[i] = i; 411 positions[2*i+0] = (i%DIM) * GAP; 412 positions[2*i+1] = Math.round(i/DIM) * GAP; 413 } 414 canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint); 415 416 font.delete(); 417 paint.delete(); 418 }); 419 420 gm('image_decoding_methods', async (canvas) => { 421 422 const IMAGE_FILE_PATHS = [ 423 '/assets/brickwork-texture.jpg', 424 '/assets/mandrill_512.png', 425 '/assets/color_wheel.gif' 426 ]; 427 428 let row = 1; 429 // Test 4 different methods of decoding an image for each of the three images in 430 // IMAGE_FILE_PATHS. 431 // Resulting Images are drawn to visually show that all methods decode correctly. 432 for (const imageFilePath of IMAGE_FILE_PATHS) { 433 const response = await fetch(imageFilePath); 434 const arrayBuffer = await response.arrayBuffer(); 435 // response.blob() is preferable when you don't need both a Blob *and* an ArrayBuffer. 436 const blob = new Blob([ arrayBuffer ]); 437 438 // Method 1 - decode TypedArray using wasm codecs: 439 const skImage1 = CanvasKit.MakeImageFromEncoded(arrayBuffer); 440 441 // Method 2 (slower and does not work in Safari) decode using ImageBitmap: 442 const imageBitmap = await createImageBitmap(blob); 443 // Testing showed that transferring an ImageBitmap to a canvas using the 'bitmaprenderer' 444 // context and passing that canvas to CanvasKit.MakeImageFromCanvasImageSource() is 445 // marginally faster than passing ImageBitmap to 446 // CanvasKit.MakeImageFromCanvasImageSource() directly. 447 const canvasBitmapElement = document.createElement('canvas'); 448 canvasBitmapElement.width = imageBitmap.width; 449 canvasBitmapElement.height = imageBitmap.height; 450 const ctxBitmap = canvasBitmapElement.getContext('bitmaprenderer'); 451 ctxBitmap.transferFromImageBitmap(imageBitmap); 452 const skImage2 = CanvasKit.MakeImageFromCanvasImageSource(canvasBitmapElement); 453 454 // Method 3 (slowest) decode using HTMLImageElement directly: 455 const image = new Image(); 456 // Testing showed that waiting for a load event is faster than waiting on image.decode() 457 // HTMLImageElement.decode() reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode 458 const promise1 = new Promise((resolve) => image.addEventListener('load', resolve)); 459 image.src = imageFilePath; 460 await promise1; 461 const skImage3 = CanvasKit.MakeImageFromCanvasImageSource(image); 462 463 // Method 4 (roundabout, but works if all you have is a Blob) decode from Blob using 464 // HTMLImageElement: 465 const imageObjectUrl = URL.createObjectURL( blob ); 466 const image2 = new Image(); 467 const promise2 = new Promise((resolve) => image2.addEventListener('load', resolve)); 468 image2.src = imageObjectUrl; 469 await promise2; 470 const skImage4 = CanvasKit.MakeImageFromCanvasImageSource(image2); 471 472 // Draw decoded images 473 const sourceRect = CanvasKit.XYWHRect(0, 0, 150, 150); 474 canvas.drawImageRect(skImage1, sourceRect, CanvasKit.XYWHRect(0, row * 100, 90, 90), null, false); 475 canvas.drawImageRect(skImage2, sourceRect, CanvasKit.XYWHRect(100, row * 100, 90, 90), null, false); 476 canvas.drawImageRect(skImage3, sourceRect, CanvasKit.XYWHRect(200, row * 100, 90, 90), null, false); 477 canvas.drawImageRect(skImage4, sourceRect, CanvasKit.XYWHRect(300, row * 100, 90, 90), null, false); 478 479 row++; 480 } 481 // Label images with the method used to decode them 482 const paint = new CanvasKit.Paint(); 483 const textFont = new CanvasKit.Font(null, 7); 484 canvas.drawText('WASM Decoding', 0, 90, paint, textFont); 485 canvas.drawText('ImageBitmap Decoding', 100, 90, paint, textFont); 486 canvas.drawText('HTMLImageEl Decoding', 200, 90, paint, textFont); 487 canvas.drawText('Blob Decoding', 300, 90, paint, textFont); 488 }); 489 490 gm('sweep_gradient', (canvas) => { 491 const paint = new CanvasKit.Paint(); 492 const shader = CanvasKit.Shader.MakeSweepGradient( 493 100, 100, // X, Y coordinates 494 [CanvasKit.GREEN, CanvasKit.BLUE], 495 [0.0, 1.0], 496 CanvasKit.TileMode.Clamp, 497 ); 498 expect(shader).toBeTruthy('Could not make shader'); 499 500 paint.setShader(shader); 501 canvas.drawPaint(paint); 502 503 paint.delete(); 504 shader.delete(); 505 }); 506 507 // TODO(kjlubick): There's a lot of shared code between the gradient gms 508 // It would be best to deduplicate that in a nice DAMP way. 509 // Inspired by https://fiddle.skia.org/c/b29ce50a341510784ac7d5281586d076 510 gm('linear_gradients', (canvas) => { 511 canvas.scale(2, 2); 512 const strokePaint = new CanvasKit.Paint(); 513 strokePaint.setStyle(CanvasKit.PaintStyle.Stroke); 514 strokePaint.setColor(CanvasKit.BLACK); 515 516 const paint = new CanvasKit.Paint(); 517 paint.setStyle(CanvasKit.PaintStyle.Fill); 518 const transparentGreen = CanvasKit.Color(0, 255, 255, 0); 519 520 const lgs = CanvasKit.Shader.MakeLinearGradient( 521 [0, 0], [50, 100], // start and stop points 522 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 523 [0, 0.65, 1.0], 524 CanvasKit.TileMode.Mirror 525 ); 526 paint.setShader(lgs); 527 let r = CanvasKit.LTRBRect(0, 0, 100, 100); 528 canvas.drawRect(r, paint); 529 canvas.drawRect(r, strokePaint); 530 531 const lgsPremul = CanvasKit.Shader.MakeLinearGradient( 532 [100, 0], [150, 100], // start and stop points 533 Uint32Array.of( 534 CanvasKit.ColorAsInt(0, 255, 255, 0), 535 CanvasKit.ColorAsInt(0, 0, 255, 255), 536 CanvasKit.ColorAsInt(255, 0, 0, 255)), 537 [0, 0.65, 1.0], 538 CanvasKit.TileMode.Mirror, 539 null, // no local matrix 540 1 // interpolate colors in premul 541 ); 542 paint.setShader(lgsPremul); 543 r = CanvasKit.LTRBRect(100, 0, 200, 100); 544 canvas.drawRect(r, paint); 545 canvas.drawRect(r, strokePaint); 546 547 const lgs45 = CanvasKit.Shader.MakeLinearGradient( 548 [0, 100], [50, 200], // start and stop points 549 Float32Array.of(...transparentGreen, ...CanvasKit.BLUE, ...CanvasKit.RED), 550 [0, 0.65, 1.0], 551 CanvasKit.TileMode.Mirror, 552 CanvasKit.Matrix.rotated(Math.PI/4, 0, 100), 553 ); 554 paint.setShader(lgs45); 555 r = CanvasKit.LTRBRect(0, 100, 100, 200); 556 canvas.drawRect(r, paint); 557 canvas.drawRect(r, strokePaint); 558 559 // malloc'd color array 560 const colors = CanvasKit.Malloc(Float32Array, 12); 561 const typedColorsArray = colors.toTypedArray(); 562 typedColorsArray.set(transparentGreen, 0); 563 typedColorsArray.set(CanvasKit.BLUE, 4); 564 typedColorsArray.set(CanvasKit.RED, 8); 565 const lgs45Premul = CanvasKit.Shader.MakeLinearGradient( 566 [100, 100], [150, 200], // start and stop points 567 typedColorsArray, 568 [0, 0.65, 1.0], 569 CanvasKit.TileMode.Mirror, 570 CanvasKit.Matrix.rotated(Math.PI/4, 100, 100), 571 1 // interpolate colors in premul 572 ); 573 CanvasKit.Free(colors); 574 paint.setShader(lgs45Premul); 575 r = CanvasKit.LTRBRect(100, 100, 200, 200); 576 canvas.drawRect(r, paint); 577 canvas.drawRect(r, strokePaint); 578 579 lgs.delete(); 580 lgs45.delete(); 581 lgsPremul.delete(); 582 lgs45Premul.delete(); 583 strokePaint.delete(); 584 paint.delete(); 585 }); 586 587 gm('radial_gradients', (canvas) => { 588 canvas.scale(2, 2); 589 const strokePaint = new CanvasKit.Paint(); 590 strokePaint.setStyle(CanvasKit.PaintStyle.Stroke); 591 strokePaint.setColor(CanvasKit.BLACK); 592 593 const paint = new CanvasKit.Paint(); 594 paint.setStyle(CanvasKit.PaintStyle.Fill); 595 const transparentGreen = CanvasKit.Color(0, 255, 255, 0); 596 597 const rgs = CanvasKit.Shader.MakeRadialGradient( 598 [50, 50], 50, // center, radius 599 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 600 [0, 0.65, 1.0], 601 CanvasKit.TileMode.Mirror 602 ); 603 paint.setShader(rgs); 604 let r = CanvasKit.LTRBRect(0, 0, 100, 100); 605 canvas.drawRect(r, paint); 606 canvas.drawRect(r, strokePaint); 607 608 const rgsPremul = CanvasKit.Shader.MakeRadialGradient( 609 [150, 50], 50, // center, radius 610 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 611 [0, 0.65, 1.0], 612 CanvasKit.TileMode.Mirror, 613 null, // no local matrix 614 1 // interpolate colors in premul 615 ); 616 paint.setShader(rgsPremul); 617 r = CanvasKit.LTRBRect(100, 0, 200, 100); 618 canvas.drawRect(r, paint); 619 canvas.drawRect(r, strokePaint); 620 621 const rgsSkew = CanvasKit.Shader.MakeRadialGradient( 622 [50, 150], 50, // center, radius 623 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 624 [0, 0.65, 1.0], 625 CanvasKit.TileMode.Mirror, 626 CanvasKit.Matrix.skewed(0.5, 0, 100, 100), 627 null, // color space 628 ); 629 paint.setShader(rgsSkew); 630 r = CanvasKit.LTRBRect(0, 100, 100, 200); 631 canvas.drawRect(r, paint); 632 canvas.drawRect(r, strokePaint); 633 634 const rgsSkewPremul = CanvasKit.Shader.MakeRadialGradient( 635 [150, 150], 50, // center, radius 636 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 637 [0, 0.65, 1.0], 638 CanvasKit.TileMode.Mirror, 639 CanvasKit.Matrix.skewed(0.5, 0, 100, 100), 640 1, // interpolate colors in premul 641 null, // color space 642 ); 643 paint.setShader(rgsSkewPremul); 644 r = CanvasKit.LTRBRect(100, 100, 200, 200); 645 canvas.drawRect(r, paint); 646 canvas.drawRect(r, strokePaint); 647 648 rgs.delete(); 649 rgsPremul.delete(); 650 rgsSkew.delete(); 651 rgsSkewPremul.delete(); 652 strokePaint.delete(); 653 paint.delete(); 654 }); 655 656 gm('conical_gradients', (canvas) => { 657 canvas.scale(2, 2); 658 const strokePaint = new CanvasKit.Paint(); 659 strokePaint.setStyle(CanvasKit.PaintStyle.Stroke); 660 strokePaint.setColor(CanvasKit.BLACK); 661 662 const paint = new CanvasKit.Paint(); 663 paint.setStyle(CanvasKit.PaintStyle.Fill); 664 paint.setAntiAlias(true); 665 const transparentGreen = CanvasKit.Color(0, 255, 255, 0); 666 667 const cgs = CanvasKit.Shader.MakeTwoPointConicalGradient( 668 [80, 10], 15, // start, radius 669 [10, 110], 60, // end, radius 670 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 671 [0, 0.65, 1.0], 672 CanvasKit.TileMode.Mirror, 673 null, // no local matrix 674 ); 675 paint.setShader(cgs); 676 let r = CanvasKit.LTRBRect(0, 0, 100, 100); 677 canvas.drawRect(r, paint); 678 canvas.drawRect(r, strokePaint); 679 680 const cgsPremul = CanvasKit.Shader.MakeTwoPointConicalGradient( 681 [180, 10], 15, // start, radius 682 [110, 110], 60, // end, radius 683 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 684 [0, 0.65, 1.0], 685 CanvasKit.TileMode.Mirror, 686 null, // no local matrix 687 1, // interpolate colors in premul 688 null, // color space 689 ); 690 paint.setShader(cgsPremul); 691 r = CanvasKit.LTRBRect(100, 0, 200, 100); 692 canvas.drawRect(r, paint); 693 canvas.drawRect(r, strokePaint); 694 695 const cgs45 = CanvasKit.Shader.MakeTwoPointConicalGradient( 696 [80, 110], 15, // start, radius 697 [10, 210], 60, // end, radius 698 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 699 [0, 0.65, 1.0], 700 CanvasKit.TileMode.Mirror, 701 CanvasKit.Matrix.rotated(Math.PI/4, 0, 100), 702 null, // color space 703 ); 704 paint.setShader(cgs45); 705 r = CanvasKit.LTRBRect(0, 100, 100, 200); 706 canvas.drawRect(r, paint); 707 canvas.drawRect(r, strokePaint); 708 709 const cgs45Premul = CanvasKit.Shader.MakeTwoPointConicalGradient( 710 [180, 110], 15, // start, radius 711 [110, 210], 60, // end, radius 712 [transparentGreen, CanvasKit.BLUE, CanvasKit.RED], 713 [0, 0.65, 1.0], 714 CanvasKit.TileMode.Mirror, 715 CanvasKit.Matrix.rotated(Math.PI/4, 100, 100), 716 1, // interpolate colors in premul 717 null, // color space 718 ); 719 paint.setShader(cgs45Premul); 720 r = CanvasKit.LTRBRect(100, 100, 200, 200); 721 canvas.drawRect(r, paint); 722 canvas.drawRect(r, strokePaint); 723 724 cgs.delete(); 725 cgsPremul.delete(); 726 cgs45.delete(); 727 strokePaint.delete(); 728 paint.delete(); 729 }); 730 731 gm('blur_filters', (canvas) => { 732 const pathUL = starPath(CanvasKit, 100, 100, 80); 733 const pathBR = starPath(CanvasKit, 400, 300, 80); 734 const paint = new CanvasKit.Paint(); 735 const textFont = new CanvasKit.Font(null, 24); 736 737 canvas.drawText('Above: MaskFilter', 20, 220, paint, textFont); 738 canvas.drawText('Right: ImageFilter', 20, 260, paint, textFont); 739 740 paint.setColor(CanvasKit.BLUE); 741 742 const blurMask = CanvasKit.MaskFilter.MakeBlur(CanvasKit.BlurStyle.Normal, 5, true); 743 paint.setMaskFilter(blurMask); 744 canvas.drawPath(pathUL, paint); 745 746 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 1, CanvasKit.TileMode.Decal, null); 747 paint.setImageFilter(blurIF); 748 canvas.drawPath(pathBR, paint); 749 750 pathUL.delete(); 751 pathBR.delete(); 752 paint.delete(); 753 blurMask.delete(); 754 blurIF.delete(); 755 textFont.delete(); 756 }); 757 758 gm('luma_filter', (canvas) => { 759 const paint = new CanvasKit.Paint(); 760 paint.setAntiAlias(true); 761 const lumaCF = CanvasKit.ColorFilter.MakeLuma(); 762 paint.setColor(CanvasKit.BLUE); 763 paint.setColorFilter(lumaCF); 764 canvas.drawCircle(256, 256, 256, paint); 765 paint.delete(); 766 lumaCF.delete(); 767 }); 768 769 gm('combined_filters', (canvas, fetchedByteBuffers) => { 770 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 771 expect(img).toBeTruthy(); 772 const paint = new CanvasKit.Paint(); 773 paint.setAntiAlias(true); 774 paint.setColor(CanvasKit.Color(0, 255, 0, 1.0)); 775 const redCF = CanvasKit.ColorFilter.MakeBlend( 776 CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver); 777 const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null); 778 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null); 779 const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF); 780 781 // rotate 10 degrees centered on 200, 200 782 const m = CanvasKit.Matrix.rotated(Math.PI/18, 200, 200); 783 const filtering = { filter: CanvasKit.FilterMode.Linear }; 784 const rotated = CanvasKit.ImageFilter.MakeMatrixTransform(m, filtering, combined); 785 paint.setImageFilter(rotated); 786 787 //canvas.rotate(10, 200, 200); 788 canvas.drawImage(img, 0, 0, paint); 789 canvas.drawRect(CanvasKit.LTRBRect(5, 35, 45, 80), paint); 790 791 paint.delete(); 792 redIF.delete(); 793 redCF.delete(); 794 blurIF.delete(); 795 combined.delete(); 796 rotated.delete(); 797 img.delete(); 798 }, '/assets/mandrill_512.png'); 799 800 gm('animated_filters', (canvas, fetchedByteBuffers) => { 801 const img = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]); 802 expect(img).toBeTruthy(); 803 img.decodeNextFrame(); 804 img.decodeNextFrame(); 805 const paint = new CanvasKit.Paint(); 806 paint.setAntiAlias(true); 807 paint.setColor(CanvasKit.Color(0, 255, 0, 1.0)); 808 const redCF = CanvasKit.ColorFilter.MakeBlend( 809 CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver); 810 const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null); 811 const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null); 812 const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF); 813 paint.setImageFilter(combined); 814 815 const frame = img.makeImageAtCurrentFrame(); 816 canvas.drawImage(frame, 100, 50, paint); 817 818 paint.delete(); 819 redIF.delete(); 820 redCF.delete(); 821 blurIF.delete(); 822 combined.delete(); 823 frame.delete(); 824 img.delete(); 825 }, '/assets/flightAnim.gif'); 826 827 gm('drawImageVariants', (canvas, fetchedByteBuffers) => { 828 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 829 expect(img).toBeTruthy(); 830 canvas.scale(2, 2); 831 const paint = new CanvasKit.Paint(); 832 const clipTo = (x, y) => { 833 canvas.save(); 834 canvas.clipRect(CanvasKit.XYWHRect(x, y, 128, 128), CanvasKit.ClipOp.Intersect); 835 }; 836 837 clipTo(0, 0); 838 canvas.drawImage(img, 0, 0, paint); 839 canvas.restore(); 840 841 clipTo(128, 0); 842 canvas.drawImageCubic(img, 128, 0, 1/3, 1/3, null); 843 canvas.restore(); 844 845 clipTo(0, 128); 846 canvas.drawImageOptions(img, 0, 128, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null); 847 canvas.restore(); 848 849 const mipImg = img.makeCopyWithDefaultMipmaps(); 850 clipTo(128, 128); 851 canvas.drawImageOptions(mipImg, 128, 128, 852 CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest, null); 853 canvas.restore(); 854 855 paint.delete(); 856 mipImg.delete(); 857 img.delete(); 858 }, '/assets/mandrill_512.png'); 859 860 gm('drawImageRectVariants', (canvas, fetchedByteBuffers) => { 861 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 862 expect(img).toBeTruthy(); 863 const paint = new CanvasKit.Paint(); 864 const src = CanvasKit.XYWHRect(100, 100, 128, 128); 865 canvas.drawImageRect(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), paint); 866 canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3); 867 canvas.drawImageRectOptions(img, src, CanvasKit.XYWHRect(0, 256, 256, 256), 868 CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None); 869 const mipImg = img.makeCopyWithDefaultMipmaps(); 870 canvas.drawImageRectOptions(mipImg, src, CanvasKit.XYWHRect(256, 256, 256, 256), 871 CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest); 872 873 paint.delete(); 874 mipImg.delete(); 875 img.delete(); 876 }, '/assets/mandrill_512.png'); 877 878 gm('drawImage_skp', (canvas, fetchedByteBuffers) => { 879 canvas.clear(CanvasKit.TRANSPARENT); 880 const pic = CanvasKit.MakePicture(fetchedByteBuffers[0]); 881 canvas.drawPicture(pic); 882 // The asset below can be re-downloaded from 883 // https://fiddle.skia.org/c/cbb8dee39e9f1576cd97c2d504db8eee 884 }, '/assets/red_line.skp'); 885 886 it('can draw once using drawOnce utility method', (done) => { 887 const surface = CanvasKit.MakeCanvasSurface('test'); 888 expect(surface).toBeTruthy('Could not make surface'); 889 if (!surface) { 890 done(); 891 return; 892 } 893 894 const drawFrame = (canvas) => { 895 const paint = new CanvasKit.Paint(); 896 paint.setStrokeWidth(1.0); 897 paint.setAntiAlias(true); 898 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 899 paint.setStyle(CanvasKit.PaintStyle.Stroke); 900 const path = new CanvasKit.Path(); 901 path.moveTo(20, 5); 902 path.lineTo(30, 20); 903 path.lineTo(40, 10); 904 canvas.drawPath(path, paint); 905 path.delete(); 906 paint.delete(); 907 // surface hasn't been flushed yet (nor should we call flush 908 // ourselves), so reportSurface would likely be blank if we 909 // were to call it. 910 done(); 911 }; 912 surface.drawOnce(drawFrame); 913 // Reminder: drawOnce is async. In this test, we are just making 914 // sure the drawOnce function is there and doesn't crash, so we can 915 // just call done() when the frame is rendered. 916 }); 917 918 it('can draw client-supplied dirty rects', (done) => { 919 // dirty rects are only honored by software (CPU) canvases today. 920 const surface = CanvasKit.MakeSWCanvasSurface('test'); 921 expect(surface).toBeTruthy('Could not make surface'); 922 if (!surface) { 923 done(); 924 return; 925 } 926 927 const drawFrame = (canvas) => { 928 const paint = new CanvasKit.Paint(); 929 paint.setStrokeWidth(1.0); 930 paint.setAntiAlias(true); 931 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 932 paint.setStyle(CanvasKit.PaintStyle.Stroke); 933 const path = new CanvasKit.Path(); 934 path.moveTo(20, 5); 935 path.lineTo(30, 20); 936 path.lineTo(40, 10); 937 canvas.drawPath(path, paint); 938 path.delete(); 939 paint.delete(); 940 done(); 941 }; 942 const dirtyRect = CanvasKit.XYWHRect(10, 10, 15, 15); 943 surface.drawOnce(drawFrame, dirtyRect); 944 // We simply ensure that passing a dirty rect doesn't crash. 945 }); 946 947 it('can use DecodeCache APIs', () => { 948 const initialLimit = CanvasKit.getDecodeCacheLimitBytes(); 949 expect(initialLimit).toBeGreaterThan(1024 * 1024); 950 951 const newLimit = 42 * 1024 * 1024; 952 CanvasKit.setDecodeCacheLimitBytes(newLimit); 953 expect(CanvasKit.getDecodeCacheLimitBytes()).toEqual(newLimit); 954 955 // We cannot make any assumptions about this value, 956 // so we just make sure it doesn't crash. 957 CanvasKit.getDecodeCacheUsedBytes(); 958 }); 959 960 gm('combined_shaders', (canvas) => { 961 const rShader = CanvasKit.Shader.Color(CanvasKit.Color(255, 0, 0, 1.0)); // deprecated 962 const gShader = CanvasKit.Shader.MakeColor(CanvasKit.Color(0, 255, 0, 0.6)); 963 964 const rgShader = CanvasKit.Shader.MakeBlend(CanvasKit.BlendMode.SrcOver, rShader, gShader); 965 966 const p = new CanvasKit.Paint(); 967 p.setShader(rgShader); 968 canvas.drawPaint(p); 969 970 rShader.delete(); 971 gShader.delete(); 972 rgShader.delete(); 973 p.delete(); 974 }); 975 976 it('exports consts correctly', () => { 977 expect(CanvasKit.TRANSPARENT).toEqual(Float32Array.of(0, 0, 0, 0)); 978 expect(CanvasKit.RED).toEqual(Float32Array.of(1, 0, 0, 1)); 979 980 expect(CanvasKit.QUAD_VERB).toEqual(2); 981 expect(CanvasKit.CONIC_VERB).toEqual(3); 982 983 expect(CanvasKit.SaveLayerInitWithPrevious).toEqual(4); 984 expect(CanvasKit.SaveLayerF16ColorType).toEqual(16); 985 }); 986 987 it('can set color on a paint and get it as four floats', () => { 988 const paint = new CanvasKit.Paint(); 989 paint.setColor(CanvasKit.Color4f(3.3, 2.2, 1.1, 0.5)); 990 expect(paint.getColor()).toEqual(Float32Array.of(3.3, 2.2, 1.1, 0.5)); 991 992 paint.setColorComponents(0.5, 0.6, 0.7, 0.8); 993 expect(paint.getColor()).toEqual(Float32Array.of(0.5, 0.6, 0.7, 0.8)); 994 995 paint.setColorInt(CanvasKit.ColorAsInt(50, 100, 150, 200)); 996 let color = paint.getColor(); 997 expect(color.length).toEqual(4); 998 expect(color[0]).toBeCloseTo(50/255, 5); // Red 999 expect(color[1]).toBeCloseTo(100/255, 5); // Green 1000 expect(color[2]).toBeCloseTo(150/255, 5); // Blue 1001 expect(color[3]).toBeCloseTo(200/255, 5); // Alpha 1002 1003 paint.setColorInt(0xFF000000); 1004 expect(paint.getColor()).toEqual(Float32Array.of(0, 0, 0, 1.0)); 1005 }); 1006 1007 gm('draw_shadow', (canvas) => { 1008 const lightRadius = 20; 1009 const lightPos = [500,500,20]; 1010 const zPlaneParams = [0,0,1]; 1011 const path = starPath(CanvasKit); 1012 const textFont = new CanvasKit.Font(null, 24); 1013 const textPaint = new CanvasKit.Paint(); 1014 1015 canvas.drawShadow(path, zPlaneParams, lightPos, lightRadius, 1016 CanvasKit.BLACK, CanvasKit.MAGENTA, 0); 1017 canvas.drawText('Default Flags', 5, 250, textPaint, textFont); 1018 1019 let bounds = CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.identity(), 1020 path, zPlaneParams, lightPos, lightRadius, 0); 1021 expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164)); 1022 1023 bounds = CanvasKit.getShadowLocalBounds(CanvasKit.M44.identity(), 1024 path, zPlaneParams, lightPos, lightRadius, 0); 1025 expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164)); 1026 1027 // Test that the APIs accept Malloc objs and the Malloced typearray 1028 const mZPlane = CanvasKit.Malloc(Float32Array, 3); 1029 mZPlane.toTypedArray().set(zPlaneParams); 1030 const mLight = CanvasKit.Malloc(Float32Array, 3); 1031 const lightTA = mLight.toTypedArray(); 1032 lightTA.set(lightPos); 1033 1034 canvas.translate(250, 250); 1035 canvas.drawShadow(path, mZPlane, lightTA, lightRadius, 1036 CanvasKit.BLACK, CanvasKit.MAGENTA, 1037 CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight); 1038 canvas.drawText('All Flags', 5, 250, textPaint, textFont); 1039 1040 const outBounds = new Float32Array(4); 1041 CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.rotated(Math.PI / 6), 1042 path, mZPlane, mLight, lightRadius, 1043 CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight, 1044 outBounds); 1045 expectTypedArraysToEqual(outBounds, Float32Array.of(-31.6630249, -15.24227, 245.5, 252.94101)); 1046 1047 CanvasKit.Free(mZPlane); 1048 CanvasKit.Free(mLight); 1049 1050 path.delete(); 1051 textFont.delete(); 1052 textPaint.delete(); 1053 }); 1054 1055 gm('fractal_noise_shader', (canvas) => { 1056 const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 0, 0); 1057 const paint = new CanvasKit.Paint(); 1058 paint.setColor(CanvasKit.BLACK); 1059 paint.setShader(shader); 1060 canvas.drawPaint(paint); 1061 paint.delete(); 1062 shader.delete(); 1063 }); 1064 1065 gm('turbulance_shader', (canvas) => { 1066 const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 0, 0); 1067 const paint = new CanvasKit.Paint(); 1068 paint.setColor(CanvasKit.BLACK); 1069 paint.setShader(shader); 1070 canvas.drawPaint(paint); 1071 paint.delete(); 1072 shader.delete(); 1073 }); 1074 1075 gm('fractal_noise_tiled_shader', (canvas) => { 1076 const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 80, 80); 1077 const paint = new CanvasKit.Paint(); 1078 paint.setColor(CanvasKit.BLACK); 1079 paint.setShader(shader); 1080 canvas.drawPaint(paint); 1081 paint.delete(); 1082 shader.delete(); 1083 }); 1084 1085 gm('turbulance_tiled_shader', (canvas) => { 1086 const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 80, 80); 1087 const paint = new CanvasKit.Paint(); 1088 paint.setColor(CanvasKit.BLACK); 1089 paint.setShader(shader); 1090 canvas.drawPaint(paint); 1091 paint.delete(); 1092 shader.delete(); 1093 }); 1094 1095 describe('ColorSpace Support', () => { 1096 it('Creates an SRGB 8888 surface by default', () => { 1097 const colorSpace = CanvasKit.ColorSpace.SRGB; 1098 const surface = CanvasKit.MakeCanvasSurface('test'); 1099 expect(surface).toBeTruthy('Could not make surface'); 1100 let info = surface.imageInfo(); 1101 expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul); 1102 expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888); 1103 expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace)) 1104 .toBeTruthy("Surface not created with correct color space."); 1105 1106 const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4); 1107 mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels. 1108 const canvas = surface.getCanvas(); 1109 canvas.clear(CanvasKit.TRANSPARENT); 1110 const pixels = canvas.readPixels(0, 0, { 1111 width: CANVAS_WIDTH, 1112 height: CANVAS_HEIGHT, 1113 colorType: CanvasKit.ColorType.RGBA_8888, 1114 alphaType: CanvasKit.AlphaType.Unpremul, 1115 colorSpace: colorSpace 1116 }, mObj, 4 * CANVAS_WIDTH); 1117 expect(pixels).toBeTruthy('Could not read pixels from surface'); 1118 expect(pixels[0] !== 127).toBeTruthy(); 1119 expect(pixels[0]).toEqual(mObj.toTypedArray()[0]); 1120 CanvasKit.Free(mObj); 1121 surface.delete(); 1122 }); 1123 it('Can create an SRGB 8888 surface', () => { 1124 const colorSpace = CanvasKit.ColorSpace.SRGB; 1125 const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.SRGB); 1126 expect(surface).toBeTruthy('Could not make surface'); 1127 let info = surface.imageInfo(); 1128 expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul); 1129 expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888); 1130 expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace)) 1131 .toBeTruthy("Surface not created with correct color space."); 1132 1133 const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4); 1134 mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels. 1135 const canvas = surface.getCanvas(); 1136 canvas.clear(CanvasKit.TRANSPARENT); 1137 const pixels = canvas.readPixels(0, 0, { 1138 width: CANVAS_WIDTH, 1139 height: CANVAS_HEIGHT, 1140 colorType: CanvasKit.ColorType.RGBA_8888, 1141 alphaType: CanvasKit.AlphaType.Unpremul, 1142 colorSpace: colorSpace 1143 }, mObj, 4 * CANVAS_WIDTH); 1144 expect(pixels).toBeTruthy('Could not read pixels from surface'); 1145 expect(pixels[0] !== 127).toBeTruthy(); 1146 expect(pixels[0]).toEqual(mObj.toTypedArray()[0]); 1147 CanvasKit.Free(mObj); 1148 surface.delete(); 1149 }); 1150 it('Can create a Display P3 surface', () => { 1151 const colorSpace = CanvasKit.ColorSpace.DISPLAY_P3; 1152 const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.DISPLAY_P3); 1153 expect(surface).toBeTruthy('Could not make surface'); 1154 if (!surface.reportBackendTypeIsGPU()) { 1155 console.log('Not expecting color space support in cpu backed suface.'); 1156 return; 1157 } 1158 let info = surface.imageInfo(); 1159 expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul); 1160 expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16); 1161 expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace)) 1162 .toBeTruthy("Surface not created with correct color space."); 1163 1164 const pixels = surface.getCanvas().readPixels(0, 0, { 1165 width: CANVAS_WIDTH, 1166 height: CANVAS_HEIGHT, 1167 colorType: CanvasKit.ColorType.RGBA_F16, 1168 alphaType: CanvasKit.AlphaType.Unpremul, 1169 colorSpace: colorSpace 1170 }); 1171 expect(pixels).toBeTruthy('Could not read pixels from surface'); 1172 }); 1173 it('Can create an Adobe RGB surface', () => { 1174 const colorSpace = CanvasKit.ColorSpace.ADOBE_RGB; 1175 const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB); 1176 expect(surface).toBeTruthy('Could not make surface'); 1177 if (!surface.reportBackendTypeIsGPU()) { 1178 console.log('Not expecting color space support in cpu backed surface.'); 1179 return; 1180 } 1181 let info = surface.imageInfo(); 1182 expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul); 1183 expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16); 1184 expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace)) 1185 .toBeTruthy("Surface not created with correct color space."); 1186 1187 const pixels = surface.getCanvas().readPixels(0, 0, { 1188 width: CANVAS_WIDTH, 1189 height: CANVAS_HEIGHT, 1190 colorType: CanvasKit.ColorType.RGBA_F16, 1191 alphaType: CanvasKit.AlphaType.Unpremul, 1192 colorSpace: colorSpace 1193 }); 1194 expect(pixels).toBeTruthy('Could not read pixels from surface'); 1195 }); 1196 1197 it('combine draws from several color spaces', () => { 1198 const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB); 1199 expect(surface).toBeTruthy('Could not make surface'); 1200 if (!surface.reportBackendTypeIsGPU()) { 1201 console.log('Not expecting color space support in cpu backed suface.'); 1202 return; 1203 } 1204 const canvas = surface.getCanvas(); 1205 1206 let paint = new CanvasKit.Paint(); 1207 paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.ADOBE_RGB); 1208 canvas.drawPaint(paint); 1209 paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.DISPLAY_P3); // 93.7 in adobeRGB 1210 canvas.drawRect(CanvasKit.LTRBRect(200, 0, 400, 600), paint); 1211 paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.SRGB); // 85.9 in adobeRGB 1212 canvas.drawRect(CanvasKit.LTRBRect(400, 0, 600, 600), paint); 1213 1214 // this test paints three bands of red, each the maximum red that a color space supports. 1215 // They are each represented by skia by some color in the Adobe RGB space of the surface, 1216 // as floats between 0 and 1. 1217 1218 // TODO(nifong) readpixels and verify correctness after f32 readpixels bug is fixed 1219 }); 1220 }); // end describe('ColorSpace Support') 1221 1222 describe('DOMMatrix support', () => { 1223 gm('sweep_gradient_dommatrix', (canvas) => { 1224 const paint = new CanvasKit.Paint(); 1225 const shader = CanvasKit.Shader.MakeSweepGradient( 1226 100, 100, // x y coordinates 1227 [CanvasKit.GREEN, CanvasKit.BLUE], 1228 [0.0, 1.0], 1229 CanvasKit.TileMode.Clamp, 1230 new DOMMatrix().translate(-10, 100), 1231 ); 1232 expect(shader).toBeTruthy('Could not make shader'); 1233 1234 paint.setShader(shader); 1235 canvas.drawPaint(paint); 1236 1237 paint.delete(); 1238 shader.delete(); 1239 }); 1240 1241 const radiansToDegrees = (rad) => { 1242 return (rad / Math.PI) * 180; 1243 }; 1244 1245 // this should draw the same as concat_with4x4_canvas 1246 gm('concat_dommatrix', (canvas) => { 1247 const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2); 1248 const paint = new CanvasKit.Paint(); 1249 paint.setAntiAlias(true); 1250 canvas.concat(new DOMMatrix().translate(CANVAS_WIDTH/2, 0, 0)); 1251 canvas.concat(new DOMMatrix().rotateAxisAngle(1, 0, 0, radiansToDegrees(Math.PI/3))); 1252 canvas.concat(new DOMMatrix().rotateAxisAngle(0, 1, 0, radiansToDegrees(Math.PI/4))); 1253 canvas.concat(new DOMMatrix().rotateAxisAngle(0, 0, 1, radiansToDegrees(Math.PI/16))); 1254 canvas.concat(new DOMMatrix().translate(-CANVAS_WIDTH/2, 0, 0)); 1255 1256 const localMatrix = canvas.getLocalToDevice(); 1257 expect4x4MatricesToMatch([ 1258 0.693519, -0.137949, 0.707106, 91.944030, 1259 0.698150, 0.370924, -0.612372, -209.445297, 1260 -0.177806, 0.918359, 0.353553, 53.342029, 1261 0 , 0 , 0 , 1 ], localMatrix); 1262 1263 // Draw some stripes to help the eye detect the turn 1264 const stripeWidth = 10; 1265 paint.setColor(CanvasKit.BLACK); 1266 for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) { 1267 canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint); 1268 } 1269 1270 paint.setColor(CanvasKit.YELLOW); 1271 canvas.drawPath(path, paint); 1272 paint.delete(); 1273 path.delete(); 1274 }); 1275 1276 it('throws if an invalid matrix is passed in', () => { 1277 let threw; 1278 try { 1279 CanvasKit.ImageFilter.MakeMatrixTransform( 1280 'invalid matrix value', 1281 { filter: CanvasKit.FilterMode.Linear }, 1282 null 1283 ) 1284 threw = false; 1285 } catch (e) { 1286 threw = true; 1287 } 1288 expect(threw).toBeTrue(); 1289 }); 1290 }); // end describe('DOMMatrix support') 1291 1292 it('can call subarray on a Malloced object', () => { 1293 const mThings = CanvasKit.Malloc(Float32Array, 6); 1294 mThings.toTypedArray().set([4, 5, 6, 7, 8, 9]); 1295 expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.toTypedArray()); 1296 expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.subarray(0)); 1297 expectTypedArraysToEqual(Float32Array.of(7, 8, 9), mThings.subarray(3)); 1298 expectTypedArraysToEqual(Float32Array.of(7), mThings.subarray(3, 4)); 1299 expectTypedArraysToEqual(Float32Array.of(7, 8), mThings.subarray(3, 5)); 1300 1301 // mutations on the subarray affect the entire array (because they are backed by the 1302 // same memory) 1303 mThings.subarray(3)[0] = 100.5; 1304 expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 100.5, 8, 9), mThings.toTypedArray()); 1305 CanvasKit.Free(mThings); 1306 }); 1307 1308 function expectTypedArraysToEqual(expected, actual) { 1309 expect(expected.constructor.name).toEqual(actual.constructor.name); 1310 expect(expected.length).toEqual(actual.length); 1311 for (let i = 0; i < expected.length; i++) { 1312 expect(expected[i]).toBeCloseTo(actual[i], 5, `element ${i}`); 1313 } 1314 } 1315 1316 it('can create a RasterDirectSurface', () => { 1317 // Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A) 1318 const rdsData = CanvasKit.Malloc(Uint8Array, 5 * 5 * 4); 1319 const surface = CanvasKit.MakeRasterDirectSurface({ 1320 'width': 5, 1321 'height': 5, 1322 'colorType': CanvasKit.ColorType.RGBA_8888, 1323 'alphaType': CanvasKit.AlphaType.Premul, 1324 'colorSpace': CanvasKit.ColorSpace.SRGB, 1325 }, rdsData, 5 * 4); 1326 1327 surface.getCanvas().clear(CanvasKit.Color(200, 100, 0, 0.8)); 1328 const pixels = rdsData.toTypedArray(); 1329 // Check that the first pixels colors are right. 1330 expect(pixels[0]).toEqual(160); // red (premul, 0.8 * 200) 1331 expect(pixels[1]).toEqual(80); // green (premul, 0.8 * 100) 1332 expect(pixels[2]).toEqual(0); // blue (premul, not that it matters) 1333 expect(pixels[3]).toEqual(204); // alpha (0.8 * 255) 1334 surface.delete(); 1335 CanvasKit.Free(rdsData); 1336 }); 1337 1338 gm('makeImageFromTextureSource_TypedArray', (canvas, _, surface) => { 1339 if (!CanvasKit.gpu) { 1340 return SHOULD_SKIP; 1341 } 1342 // This creates and draws an Unpremul Image that is 1 pixel wide, 4 pixels tall with 1343 // the colors listed below. 1344 const pixels = Uint8Array.from([ 1345 255, 0, 0, 255, // opaque red 1346 0, 255, 0, 255, // opaque green 1347 0, 0, 255, 255, // opaque blue 1348 255, 0, 255, 100, // transparent purple 1349 ]); 1350 const img = surface.makeImageFromTextureSource(pixels, { 1351 'width': 1, 1352 'height': 4, 1353 'alphaType': CanvasKit.AlphaType.Unpremul, 1354 'colorType': CanvasKit.ColorType.RGBA_8888, 1355 }); 1356 canvas.drawImage(img, 1, 1, null); 1357 1358 const info = img.getImageInfo(); 1359 expect(info).toEqual({ 1360 'width': 1, 1361 'height': 4, 1362 'alphaType': CanvasKit.AlphaType.Unpremul, 1363 'colorType': CanvasKit.ColorType.RGBA_8888, 1364 }); 1365 const cs = img.getColorSpace(); 1366 expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy(); 1367 1368 cs.delete(); 1369 img.delete(); 1370 }); 1371 1372 gm('makeImageFromTextureSource_PremulTypedArray', (canvas, _, surface) => { 1373 if (!CanvasKit.gpu) { 1374 return SHOULD_SKIP; 1375 } 1376 // This creates and draws an Unpremul Image that is 1 pixel wide, 4 pixels tall with 1377 // the colors listed below. 1378 const pixels = Uint8Array.from([ 1379 255, 0, 0, 255, // opaque red 1380 0, 255, 0, 255, // opaque green 1381 0, 0, 255, 255, // opaque blue 1382 100, 0, 100, 100, // transparent purple 1383 ]); 1384 const img = surface.makeImageFromTextureSource(pixels, { 1385 'width': 1, 1386 'height': 4, 1387 'alphaType': CanvasKit.AlphaType.Premul, 1388 'colorType': CanvasKit.ColorType.RGBA_8888, 1389 }, /*srcIsPremul = */true); 1390 canvas.drawImage(img, 1, 1, null); 1391 1392 const info = img.getImageInfo(); 1393 expect(info).toEqual({ 1394 'width': 1, 1395 'height': 4, 1396 'alphaType': CanvasKit.AlphaType.Premul, 1397 'colorType': CanvasKit.ColorType.RGBA_8888, 1398 }); 1399 img.delete(); 1400 }); 1401 1402 gm('makeImageFromTextureSource_imgElement', (canvas, _, surface) => { 1403 if (!CanvasKit.gpu) { 1404 return SHOULD_SKIP; 1405 } 1406 // This makes an offscreen <img> with the provided source. 1407 const imageEle = new Image(); 1408 imageEle.src = '/assets/mandrill_512.png'; 1409 1410 // We need to wait until the image is loaded before the texture can use it. For good 1411 // measure, we also wait for it to be decoded. 1412 return imageEle.decode().then(() => { 1413 const img = surface.makeImageFromTextureSource(imageEle); 1414 // Make sure the texture is properly written to and Skia does not draw over it by 1415 // by accident. 1416 canvas.clear(CanvasKit.RED); 1417 surface.updateTextureFromSource(img, imageEle); 1418 canvas.drawImage(img, 0, 0, null); 1419 1420 const info = img.getImageInfo(); 1421 expect(info).toEqual({ 1422 'width': 512, // width and height should be derived from the image. 1423 'height': 512, 1424 'alphaType': CanvasKit.AlphaType.Unpremul, 1425 'colorType': CanvasKit.ColorType.RGBA_8888, 1426 }); 1427 img.delete(); 1428 }); 1429 }); 1430 1431 gm('MakeLazyImageFromTextureSource_imgElement', (canvas) => { 1432 if (!CanvasKit.gpu) { 1433 return SHOULD_SKIP; 1434 } 1435 // This makes an offscreen <img> with the provided source. 1436 const imageEle = new Image(); 1437 imageEle.src = '/assets/mandrill_512.png'; 1438 1439 // We need to wait until the image is loaded before the texture can use it. For good 1440 // measure, we also wait for it to be decoded. 1441 return imageEle.decode().then(() => { 1442 const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle); 1443 canvas.drawImage(img, 5, 5, null); 1444 1445 const info = img.getImageInfo(); 1446 expect(info).toEqual({ 1447 'width': 512, // width and height should be derived from the image. 1448 'height': 512, 1449 'alphaType': CanvasKit.AlphaType.Unpremul, 1450 'colorType': CanvasKit.ColorType.RGBA_8888, 1451 }); 1452 img.delete(); 1453 }); 1454 }); 1455 1456 gm('MakeLazyImageFromTextureSource_imageInfo', (canvas) => { 1457 if (!CanvasKit.gpu) { 1458 return SHOULD_SKIP; 1459 } 1460 // This makes an offscreen <img> with the provided source. 1461 // flutter_106433.png has transparent pixels, which is required to test the Premul 1462 // behavior. https://github.com/flutter/flutter/issues/106433 1463 const imageEle = new Image(); 1464 imageEle.src = '/assets/flutter_106433.png'; 1465 1466 // We need to wait until the image is loaded before the texture can use it. For good 1467 // measure, we also wait for it to be decoded. 1468 return imageEle.decode().then(() => { 1469 const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle, { 1470 'width': 183, 1471 'height': 180, 1472 'alphaType': CanvasKit.AlphaType.Premul, 1473 'colorType': CanvasKit.ColorType.RGBA_8888, 1474 }); 1475 canvas.clear(CanvasKit.RED); 1476 canvas.drawImage(img, 20, 20, null); 1477 1478 img.delete(); 1479 }); 1480 }); 1481 1482 gm('MakeLazyImageFromTextureSource_readPixels', (canvas) => { 1483 if (!CanvasKit.gpu) { 1484 return SHOULD_SKIP; 1485 } 1486 1487 // This makes an offscreen <img> with the provided source. 1488 const imageEle = new Image(); 1489 imageEle.src = '/assets/mandrill_512.png'; 1490 1491 // We need to wait until the image is loaded before the texture can use it. For good 1492 // measure, we also wait for it to be decoded. 1493 return imageEle.decode().then(() => { 1494 const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle); 1495 const imgInfo = { 1496 'width': 512, 1497 'height': 512, 1498 'alphaType': CanvasKit.AlphaType.Unpremul, 1499 'colorType': CanvasKit.ColorType.RGBA_8888, 1500 'colorSpace': CanvasKit.ColorSpace.SRGB 1501 }; 1502 const src = CanvasKit.XYWHRect(0, 0, 512, 512); 1503 const pixels = img.readPixels(0, 0, imgInfo); 1504 expect(pixels).toBeTruthy(); 1505 // Make a new image from reading the pixels of the texture-backed image, 1506 // then draw that new image to a canvas and verify it works. 1507 const newImg = CanvasKit.MakeImage(imgInfo, pixels, 512 * 4); 1508 canvas.drawImageRectCubic(newImg, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3); 1509 canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), 1/3, 1/3); 1510 1511 const font = new CanvasKit.Font(null, 20); 1512 const paint = new CanvasKit.Paint(); 1513 paint.setColor(CanvasKit.BLACK); 1514 canvas.drawText('original', 100, 280, paint, font); 1515 canvas.drawText('readPixels', 356, 280, paint, font); 1516 1517 img.delete(); 1518 newImg.delete(); 1519 font.delete(); 1520 paint.delete(); 1521 }); 1522 }); 1523 1524 // This tests the process of turning a SkPicture that contains texture-backed images into 1525 // an SkImage that can be drawn on a different surface. It does so by reading the pixels 1526 // back and creating a new SkImage from them. 1527 gm('MakeLazyImageFromTextureSource_makeImageSnapshot', (canvas) => { 1528 if (!CanvasKit.gpu) { 1529 return SHOULD_SKIP; 1530 } 1531 1532 // This makes an offscreen <img> with the provided source. 1533 const imageEle = new Image(); 1534 imageEle.src = '/assets/mandrill_512.png'; 1535 1536 // We need to wait until the image is loaded before the texture can use it. For good 1537 // measure, we also wait for it to be decoded. 1538 return imageEle.decode().then(() => { 1539 const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle); 1540 1541 const recorder = new CanvasKit.PictureRecorder(); 1542 const recorderCanvas = recorder.beginRecording(); 1543 const src = CanvasKit.XYWHRect(0, 0, 512, 512); 1544 recorderCanvas.drawImageRectCubic(img, src, src, 1/3, 1/3); 1545 const picture = recorder.finishRecordingAsPicture(); 1546 1547 // Draw the picture to an off-screen canvas 1548 const glCanvas = document.createElement("canvas"); 1549 glCanvas.width = 512; 1550 glCanvas.height = 512; 1551 const surface = CanvasKit.MakeWebGLCanvasSurface(glCanvas); 1552 const surfaceCanvas = surface.getCanvas(); 1553 surfaceCanvas.drawPicture(picture); 1554 const font = new CanvasKit.Font(null, 20); 1555 const paint = new CanvasKit.Paint(); 1556 paint.setColor(CanvasKit.WHITE); 1557 // Put some text onto this surface, just to verify the readback works. 1558 surfaceCanvas.drawText('This is on the picture', 10, 50, paint, font); 1559 // Then read the surface as an image and read the pixels from there. 1560 const imgFromPicture = surface.makeImageSnapshot(); 1561 const imgInfo = { 1562 'width': 512, 1563 'height': 512, 1564 'alphaType': CanvasKit.AlphaType.Unpremul, 1565 'colorType': CanvasKit.ColorType.RGBA_8888, 1566 'colorSpace': CanvasKit.ColorSpace.SRGB 1567 }; 1568 const pixels = imgFromPicture.readPixels(0, 0, imgInfo); 1569 expect(pixels).toBeTruthy(); 1570 // Create a new image with those pixels, which can be drawn on the test surface. 1571 const bitmapImg = CanvasKit.MakeImage(imgInfo, pixels, 512 * 4); 1572 1573 canvas.drawImageRectCubic(bitmapImg, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3); 1574 canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), 1/3, 1/3); 1575 1576 paint.setColor(CanvasKit.BLACK); 1577 canvas.drawText('original', 100, 280, paint, font); 1578 canvas.drawText('makeImageSnapshot', 290, 280, paint, font); 1579 1580 img.delete(); 1581 imgFromPicture.delete(); 1582 bitmapImg.delete(); 1583 picture.delete(); 1584 surface.delete(); 1585 font.delete(); 1586 paint.delete(); 1587 recorder.delete(); 1588 }); 1589 }); 1590 1591 1592 it('encodes images in three different ways', () => { 1593 // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with 1594 // the colors listed below. 1595 const pixels = Uint8Array.from([ 1596 255, 0, 0, 255, // opaque red 1597 0, 255, 0, 255, // opaque green 1598 0, 0, 255, 255, // opaque blue 1599 255, 0, 255, 100, // transparent purple 1600 ]); 1601 const img = CanvasKit.MakeImage({ 1602 'width': 1, 1603 'height': 4, 1604 'alphaType': CanvasKit.AlphaType.Unpremul, 1605 'colorType': CanvasKit.ColorType.RGBA_8888, 1606 'colorSpace': CanvasKit.ColorSpace.SRGB 1607 }, pixels, 4); 1608 1609 let bytes = img.encodeToBytes(CanvasKit.ImageFormat.PNG, 100); 1610 assertBytesDecodeToImage(bytes, 'png'); 1611 bytes = img.encodeToBytes(CanvasKit.ImageFormat.JPEG, 90); 1612 assertBytesDecodeToImage(bytes, 'jpeg'); 1613 bytes = img.encodeToBytes(CanvasKit.ImageFormat.WEBP, 100); 1614 assertBytesDecodeToImage(bytes, 'webp'); 1615 1616 img.delete(); 1617 }); 1618 1619 function assertBytesDecodeToImage(bytes, format) { 1620 expect(bytes).toBeTruthy('null output for ' + format); 1621 const img = CanvasKit.MakeImageFromEncoded(bytes); 1622 expect(img).toBeTruthy('Could not decode result from '+ format); 1623 img && img.delete(); 1624 } 1625 1626 it('can make a render target', () => { 1627 if (!CanvasKit.gpu) { 1628 return; 1629 } 1630 const canvas = document.getElementById('test'); 1631 const context = CanvasKit.GetWebGLContext(canvas); 1632 const grContext = CanvasKit.MakeGrContext(context); 1633 expect(grContext).toBeTruthy(); 1634 const target = CanvasKit.MakeRenderTarget(grContext, 100, 100); 1635 expect(target).toBeTruthy(); 1636 target.delete(); 1637 grContext.delete(); 1638 }); 1639 1640 gm('PathEffect_MakePath1D', (canvas) => { 1641 // Based off //docs/examples/skpaint_path_1d_path_effect.cpp 1642 1643 const path = new CanvasKit.Path(); 1644 path.addOval(CanvasKit.XYWHRect(0, 0, 16, 6)); 1645 1646 const paint = new CanvasKit.Paint(); 1647 const effect = CanvasKit.PathEffect.MakePath1D( 1648 path, 32, 0, CanvasKit.Path1DEffect.Rotate, 1649 ); 1650 paint.setColor(CanvasKit.Color(94, 53, 88, 1)); // deep purple 1651 paint.setPathEffect(effect); 1652 paint.setAntiAlias(true); 1653 canvas.drawCircle(128, 128, 122, paint); 1654 1655 path.delete(); 1656 effect.delete(); 1657 paint.delete(); 1658 }); 1659 1660 gm('Can_Interpolate_Path', (canvas) => { 1661 const paint = new CanvasKit.Paint(); 1662 paint.setAntiAlias(true); 1663 paint.setStyle(CanvasKit.PaintStyle.Stroke); 1664 paint.setStrokeWidth(2); 1665 const path = new CanvasKit.Path() 1666 const path2 = new CanvasKit.Path(); 1667 const path3 = new CanvasKit.Path(); 1668 path3.addCircle(30, 30, 10); 1669 path.moveTo(20, 20); 1670 path.lineTo(40, 40); 1671 path.lineTo(20, 40); 1672 path.lineTo(40, 20); 1673 path.close(); 1674 path2.addRect([20, 20, 40, 40]); 1675 path2.transform(CanvasKit.Matrix.translated(40, 0)); 1676 const canInterpolate1 = CanvasKit.Path.CanInterpolate(path, path2); 1677 expect(canInterpolate1).toBe(true); 1678 const canInterpolate2 = CanvasKit.Path.CanInterpolate(path, path3); 1679 expect(canInterpolate2).toBe(false); 1680 canvas.drawPath(path, paint); 1681 canvas.drawPath(path2, paint); 1682 path3.transform(CanvasKit.Matrix.translated(80, 0)); 1683 canvas.drawPath(path3, paint); 1684 path.delete(); 1685 path2.delete(); 1686 path3.delete(); 1687 paint.delete(); 1688 }); 1689 1690 gm('Interpolate_Paths', (canvas) => { 1691 const paint = new CanvasKit.Paint(); 1692 paint.setAntiAlias(true); 1693 paint.setStyle(CanvasKit.PaintStyle.Stroke); 1694 paint.setStrokeWidth(2); 1695 const path = new CanvasKit.Path() 1696 const path2 = new CanvasKit.Path(); 1697 path.moveTo(20, 20); 1698 path.lineTo(40, 40); 1699 path.lineTo(20, 40); 1700 path.lineTo(40, 20); 1701 path.close(); 1702 path2.addRect([20, 20, 40, 40]); 1703 for (let i = 0; i <= 1; i += 1.0 / 6) { 1704 const interp = CanvasKit.Path.MakeFromPathInterpolation(path, path2, i); 1705 canvas.drawPath(interp, paint); 1706 interp.delete(); 1707 canvas.translate(30, 0); 1708 } 1709 path.delete(); 1710 path2.delete(); 1711 paint.delete(); 1712 }); 1713 1714 gm('Draw_Circle', (canvas) => { 1715 const paint = new CanvasKit.Paint(); 1716 paint.setColor(CanvasKit.Color(59, 53, 94, 1)); 1717 const path = new CanvasKit.Path(); 1718 path.moveTo(256, 256); 1719 path.addCircle(256, 256, 256); 1720 canvas.drawPath(path, paint); 1721 path.delete(); 1722 paint.delete(); 1723 }); 1724 1725 gm('PathEffect_MakePath2D', (canvas) => { 1726 // Based off //docs/examples/skpaint_path_2d_path_effect.cpp 1727 1728 const path = new CanvasKit.Path(); 1729 path.moveTo(20, 30); 1730 const points = [20, 20, 10, 30, 0, 30, 20, 10, 30, 10, 40, 0, 40, 10, 1731 50, 10, 40, 20, 40, 30, 20, 50, 20, 40, 30, 30, 20, 30]; 1732 for (let i = 0; i < points.length; i += 2) { 1733 path.lineTo(points[i], points[i+1]); 1734 } 1735 1736 const paint = new CanvasKit.Paint(); 1737 const effect = CanvasKit.PathEffect.MakePath2D( 1738 CanvasKit.Matrix.scaled(40, 40), path 1739 ); 1740 paint.setColor(CanvasKit.Color(53, 94, 59, 1)); // hunter green 1741 paint.setPathEffect(effect); 1742 paint.setAntiAlias(true); 1743 canvas.drawRect(CanvasKit.LTRBRect(-20, -20, 300, 300), paint); 1744 1745 path.delete(); 1746 effect.delete(); 1747 paint.delete(); 1748 }); 1749 1750 gm('PathEffect_MakeLine2D', (canvas) => { 1751 // Based off //docs/examples/skpaint_line_2d_path_effect.cpp 1752 1753 const lattice = CanvasKit.Matrix.multiply( 1754 CanvasKit.Matrix.scaled(8, 8), 1755 CanvasKit.Matrix.rotated(Math.PI / 6), 1756 ); 1757 1758 const paint = new CanvasKit.Paint(); 1759 const effect = CanvasKit.PathEffect.MakeLine2D( 1760 2, lattice, 1761 ); 1762 paint.setColor(CanvasKit.Color(59, 53, 94, 1)); // dark blue 1763 paint.setPathEffect(effect); 1764 paint.setAntiAlias(true); 1765 canvas.drawRect(CanvasKit.LTRBRect(20, 20, 300, 300), paint); 1766 1767 effect.delete(); 1768 paint.delete(); 1769 }); 1770 1771 gm('ImageFilter_MakeBlend', (canvas) => { 1772 const redCF = CanvasKit.ColorFilter.MakeBlend( 1773 CanvasKit.Color(255, 0, 0, 0.4), CanvasKit.BlendMode.SrcOver); 1774 const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null); 1775 const blueCF = CanvasKit.ColorFilter.MakeBlend( 1776 CanvasKit.Color(0, 0, 255, 0.7), CanvasKit.BlendMode.SrcOver); 1777 const blueIF = CanvasKit.ImageFilter.MakeColorFilter(blueCF, null); 1778 1779 const BOX_SIZE = 100; 1780 const SWATCH_SIZE = 80; 1781 const MARGIN = (BOX_SIZE - SWATCH_SIZE) / 2; 1782 const COLS_PER_ROW = CANVAS_WIDTH / BOX_SIZE; 1783 const blends = ['Clear', 'Src', 'Dst', 'SrcOver', 'DstOver', 'SrcIn', 'DstIn', 'SrcOut', 1784 'DstOut', 'SrcATop', 'DstATop', 'Xor', 'Plus', 'Modulate', 'Screen', 1785 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 1786 'SoftLight', 'Difference', 'Exclusion', 'Multiply', 'Hue', 'Saturation', 1787 'Color', 'Luminosity']; 1788 const paint = new CanvasKit.Paint(); 1789 // Put a dark green on the paint itself. 1790 paint.setColor(CanvasKit.Color(0, 255, 0, 0.2)); 1791 1792 const font = new CanvasKit.Font(null, 10); 1793 const textPaint = new CanvasKit.Paint(); 1794 textPaint.setColor(CanvasKit.BLACK); 1795 1796 for (let i = 0; i < blends.length; i++) { 1797 const filter = CanvasKit.ImageFilter.MakeBlend(CanvasKit.BlendMode[blends[i]], 1798 redIF, blueIF); 1799 const col = i % COLS_PER_ROW, row = Math.floor(i / COLS_PER_ROW); 1800 1801 paint.setImageFilter(filter); 1802 canvas.save(); 1803 1804 canvas.clipRect(CanvasKit.XYWHRect(col * BOX_SIZE + MARGIN, row * BOX_SIZE + MARGIN, SWATCH_SIZE, SWATCH_SIZE), 1805 CanvasKit.ClipOp.Intersect); 1806 canvas.drawPaint(paint); 1807 canvas.restore(); 1808 1809 canvas.drawText(blends[i], col * BOX_SIZE + 30, row * BOX_SIZE + BOX_SIZE, textPaint, font); 1810 filter.delete(); 1811 } 1812 redCF.delete(); 1813 redIF.delete(); 1814 blueCF.delete(); 1815 blueIF.delete(); 1816 paint.delete(); 1817 }); 1818 1819 gm('ImageFilter_MakeDilate', (canvas, fetchedByteBuffers) => { 1820 1821 const paint = new CanvasKit.Paint(); 1822 const dilate = CanvasKit.ImageFilter.MakeDilate(2, 10, null); 1823 paint.setImageFilter(dilate); 1824 1825 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 1826 expect(img).toBeTruthy(); 1827 canvas.drawImage(img, 10, 20, paint); 1828 1829 img.delete(); 1830 paint.delete(); 1831 dilate.delete(); 1832 }, '/assets/mandrill_512.png'); 1833 1834 gm('ImageFilter_MakeErode', (canvas, fetchedByteBuffers) => { 1835 1836 const paint = new CanvasKit.Paint(); 1837 const erode = CanvasKit.ImageFilter.MakeErode(2, 10, null); 1838 paint.setImageFilter(erode); 1839 1840 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 1841 expect(img).toBeTruthy(); 1842 canvas.drawImage(img, 10, 20, paint); 1843 1844 img.delete(); 1845 paint.delete(); 1846 erode.delete(); 1847 }, '/assets/mandrill_512.png'); 1848 1849 gm('ImageFilter_MakeDisplacementMap', (canvas, fetchedByteBuffers) => { 1850 // See https://www.smashingmagazine.com/2021/09/deep-dive-wonderful-world-svg-displacement-filtering/ 1851 // for a good writeup of displacement filters. 1852 // https://jsfiddle.skia.org/canvaskit/27ba8450861fd4ec9632276dcdb2edd0d967070c2bb44e60f803597ff78ccda2 1853 // is a way to play with how the color and scale interact. 1854 1855 // As implemented, if the displacement map is smaller than the image * scale, things can 1856 // look strange, with a copy of the image in the background. Making it the size 1857 // of the canvas will at least mask the "ghost" image that shows up in the background. 1858 const DISPLACEMENT_SIZE = CANVAS_HEIGHT; 1859 const IMAGE_SIZE = 512; 1860 const SCALE = 30; 1861 const pixels = []; 1862 // Create a soft, oblong grid shape. This sort of makes it look like there is some warbly 1863 // glass in front of the mandrill image. 1864 for (let y = 0; y < DISPLACEMENT_SIZE; y++) { 1865 for (let x = 0; x < DISPLACEMENT_SIZE; x++) { 1866 if (x < SCALE/2 || y < SCALE/2 || x >= IMAGE_SIZE - SCALE/2 || y >= IMAGE_SIZE - SCALE/2) { 1867 // grey means no displacement. If we displace off the edge of the image, we'll 1868 // see strange transparent pixels showing up around the edges. 1869 pixels.push(127, 127, 127, 255); 1870 } else { 1871 // Scale our sine wave from [-1, 1] to [0, 255] (which will be scaled by the 1872 // DisplacementMap back to [-1, 1]. 1873 // Setting the alpha to be 255 doesn't impact the translation, but does 1874 // let us draw the image if we like. 1875 pixels.push(Math.sin(x/5)*255+127, Math.sin(y/3)*255+127, 0, 255); 1876 } 1877 } 1878 } 1879 const mapImg = CanvasKit.MakeImage({ 1880 width: DISPLACEMENT_SIZE, 1881 height: DISPLACEMENT_SIZE, 1882 // Premul is important - we do not want further division of our channels. 1883 alphaType: CanvasKit.AlphaType.Premul, 1884 colorType: CanvasKit.ColorType.RGBA_8888, 1885 colorSpace: CanvasKit.ColorSpace.SRGB, 1886 }, Uint8ClampedArray.from(pixels), 4 * DISPLACEMENT_SIZE); 1887 // To see just the displacement map, uncomment the lines below 1888 // canvas.drawImage(mapImg, 0, 0, null); 1889 // return; 1890 const map = CanvasKit.ImageFilter.MakeImage(mapImg, {C: 1/3, B:1/3}); 1891 1892 const displaced = CanvasKit.ImageFilter.MakeDisplacementMap(CanvasKit.ColorChannel.Red, 1893 CanvasKit.ColorChannel.Green, SCALE, map, null); 1894 const paint = new CanvasKit.Paint(); 1895 paint.setImageFilter(displaced); 1896 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 1897 expect(img).toBeTruthy(); 1898 canvas.drawImage(img, 0, 0, paint); 1899 1900 mapImg.delete(); 1901 img.delete(); 1902 map.delete(); 1903 paint.delete(); 1904 displaced.delete(); 1905 }, '/assets/mandrill_512.png'); 1906 1907 gm('ImageFilter_MakeDropShadow', (canvas, fetchedByteBuffers) => { 1908 1909 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 1910 expect(img).toBeTruthy(); 1911 1912 const drop = CanvasKit.ImageFilter.MakeDropShadow(10, -30, 4.0, 2.0, CanvasKit.MAGENTA, null); 1913 const paint = new CanvasKit.Paint(); 1914 paint.setImageFilter(drop) 1915 canvas.drawImage(img, 50, 50, paint); 1916 1917 img.delete(); 1918 paint.delete(); 1919 drop.delete(); 1920 }, '/assets/mandrill_512.png'); 1921 1922 gm('ImageFilter_MakeDropShadowOnly', (canvas, fetchedByteBuffers) => { 1923 1924 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 1925 expect(img).toBeTruthy(); 1926 1927 const drop = CanvasKit.ImageFilter.MakeDropShadowOnly(10, -30, 4.0, 2.0, CanvasKit.MAGENTA, null); 1928 const paint = new CanvasKit.Paint(); 1929 paint.setImageFilter(drop) 1930 canvas.drawImage(img, 50, 50, paint); 1931 img.delete(); 1932 paint.delete(); 1933 drop.delete(); 1934 }, '/assets/mandrill_512.png'); 1935 1936 gm('ImageFilter_MakeOffset', (canvas, fetchedByteBuffers) => { 1937 1938 const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]); 1939 expect(img).toBeTruthy(); 1940 1941 const offset = CanvasKit.ImageFilter.MakeOffset(30, -130, null); 1942 const paint = new CanvasKit.Paint(); 1943 paint.setImageFilter(offset); 1944 canvas.drawImage(img, 50, 50, paint); 1945 img.delete(); 1946 paint.delete(); 1947 offset.delete(); 1948 }, '/assets/mandrill_512.png'); 1949 1950 gm('ImageFilter_MakeShader', (canvas) => { 1951 1952 const rt = CanvasKit.RuntimeEffect.Make(` 1953uniform float4 color; 1954half4 main(vec2 fragcoord) { 1955 return vec4(color); 1956} 1957`); 1958 const shader = rt.makeShader([0.0, 0.0, 1.0, 0.5]); 1959 const filter = CanvasKit.ImageFilter.MakeShader(shader); 1960 const paint = new CanvasKit.Paint(); 1961 paint.setImageFilter(filter); 1962 canvas.drawPaint(paint); 1963 paint.delete(); 1964 filter.delete(); 1965 shader.delete(); 1966 rt.delete(); 1967 }); 1968 1969 it('can create, delete WebGL contexts', () => { 1970 if (!CanvasKit.webgl) { 1971 return SHOULD_SKIP; 1972 } 1973 1974 const newCanvas = document.createElement('canvas'); 1975 expect(newCanvas).toBeTruthy(); 1976 const ctx = CanvasKit.GetWebGLContext(newCanvas); 1977 expect(ctx).toBeGreaterThan(0); 1978 1979 const grContext = CanvasKit.MakeWebGLContext(ctx); 1980 expect(grContext).toBeTruthy(); 1981 1982 grContext.delete(); 1983 expect(grContext.isDeleted()).toBeTrue(); 1984 }); 1985 1986 it('can create, release, and delete WebGL contexts', () => { 1987 if (!CanvasKit.webgl) { 1988 return SHOULD_SKIP; 1989 } 1990 1991 const newCanvas = document.createElement('canvas'); 1992 expect(newCanvas).toBeTruthy(); 1993 const ctx = CanvasKit.GetWebGLContext(newCanvas); 1994 expect(ctx).toBeGreaterThan(0); 1995 1996 const grContext = CanvasKit.MakeWebGLContext(ctx); 1997 expect(grContext).toBeTruthy(); 1998 1999 grContext.releaseResourcesAndAbandonContext(); 2000 2001 grContext.delete(); 2002 expect(grContext.isDeleted()).toBeTrue(); 2003 }); 2004 2005 it('can provide sample count and stencil parameters to onscreen surface', () => { 2006 if (!CanvasKit.webgl) { 2007 return SHOULD_SKIP; 2008 } 2009 const paramCanvas = document.createElement('canvas'); 2010 const gl = paramCanvas.getContext('webgl'); 2011 var sample = gl.getParameter(gl.SAMPLES); 2012 var stencil = gl.getParameter(gl.STENCIL_BITS); 2013 2014 const newCanvas = document.createElement('canvas'); 2015 const ctx = CanvasKit.GetWebGLContext(newCanvas); 2016 const grContext = CanvasKit.MakeWebGLContext(ctx); 2017 expect(grContext).toBeTruthy(); 2018 2019 var surface = CanvasKit.MakeOnScreenGLSurface(grContext, 100, 100, CanvasKit.ColorSpace.SRGB, sample, stencil); 2020 expect(surface).toBeTruthy(); 2021 }); 2022}); 2023