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