skia2/site/user/modules/quickstart.md

365 lines
13 KiB
Markdown
Raw Normal View History

CanvasKit - Quickstart
==============================
CanvasKit is a wasm module that uses Skia to draw to canvas elements a more advance feature set than the canvas API.
Minimal application
-------------------
This example is a minimal Canvaskit application that draws a rounded rect for one frame.
It pulls the wasm binary from unpkg.com but you can also build and host it yourself.
<!--?prettify?-->
``` js
<canvas id=foo width=300 height=300></canvas>
<script type="text/javascript"
src="https://unpkg.com/canvaskit-wasm@0.17.2/bin/canvaskit.js"></script>
<script type="text/javascript">
const ckLoaded = CanvasKitInit({
locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.17.2/bin/'+file});
ckLoaded.then((CanvasKit) => {
const surface = CanvasKit.MakeCanvasSurface('foo');
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
function draw(canvas) {
canvas.clear(CanvasKit.WHITE);
canvas.drawRRect(rr, paint);
}
surface.drawOnce(draw);
});
</script>
```
<canvas id=foo width=300 height=300></canvas>
<script type="text/javascript"
src="https://unpkg.com/canvaskit-wasm@0.17.2/bin/canvaskit.js"></script>
<script type="text/javascript">
const ckLoaded = CanvasKitInit({
locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.17.2/bin/'+file});
ckLoaded.then((CanvasKit) => {
const surface = CanvasKit.MakeCanvasSurface('foo');
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
function draw(canvas) {
canvas.clear(CanvasKit.WHITE);
canvas.drawRRect(rr, paint);
}
surface.drawOnce(draw);
});
</script>
Let's break it down into parts and explain what they are doing:
`<canvas id=foo width=300 height=300></canvas>` Creates the canvas to which CanvasKit will draw.
This element is where we control the width and height of the drawing buffer, while it's css style
would control any scaling applied after drawing to those pixels. Despite using a canvas element,
CanvasKit isn't calling the HTML canvas's own draw methods. It is using this canvas element to
get a WebGL2 context and performing most of the drawing work in C++ code compiled to WebAssembly,
then sending commands to the GPU at the end of each frame.
<!--?prettify?-->
``` html
<script type="text/javascript"
src="https://unpkg.com/canvaskit-wasm@0.17.2/bin/canvaskit.js"></script>
```
and
<!--?prettify?-->
``` js
const ckLoaded = CanvasKitInit({
locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.17.2/bin/'+file});
ckLoaded.then((CanvasKit) => {
```
are loading the canvaskit helper js and wasm binary respectively. CanvasKitInit accepts a function
for allowing you to alter the path where it will try to find `canvaskit.wasm` and returns a promise
that resolves with the loaded module, which we typically name `CanvasKit`.
<!--?prettify?-->
``` js
const surface = CanvasKit.MakeCanvasSurface('foo');
```
Creates an SkSurface associated with the HTML canvas element above.
Hardware acceleration is the default behavior, but can be overridden by calling
`MakeSWCanvasSurface` instead. `MakeCanvasSurface` is also where alternative color spaces or gl
attrtributes can be specified.
<!--?prettify?-->
``` js
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
```
Creates a paint, a description of how to fill or stroke rects, paths, text and other geometry in
canvaskit. `rr` is a rounded rect, with corners having a radius of 25 in the x axis, and 15 pixels
in the y axis.
<!--?prettify?-->
``` js
function draw(canvas) {
canvas.clear(CanvasKit.WHITE);
canvas.drawRRect(rr, paint);
}
```
Defines a function that will draw our frame. The function is provided an SkCanvas object on which we
make draw calls. One to clear the entire canvas, and one to draw the rounded rect with the
paint from above.
We also delete the paint object. CanvasKit objects created with `new` or methods prefixed with
`make` must be deleted for the wasm memory to be released. Javascript's GC will not take care of
it automatically. `rr` is just an array, wasn't created with `new` and doesn't point to any WASM
memory, so we don't have to call delete on it.
<!--?prettify?-->
``` js
surface.drawOnce(draw);
paint.delete()
```
Hand the drawing function to `surface.drawOnce` which makes the calls and flushes the surface.
Upon flushing, Skia will batch and send WebGL commands, making visible changes appear onscreen.
This example draws once and disposes of the surface. As promised, it is is a minimal
application.
Basic Draw Loop
---------------
What if we need to redraw to our canvas every frame? This example
bounces a rounded rect around like a 90s screensaver.
<!--?prettify?-->
``` js
ckLoaded.then((CanvasKit) => {
const surface = CanvasKit.MakeCanvasSurface('foo2');
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
// const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
const w = 100; // size of rect
const h = 60;
let x = 10; // initial position of top left corner.
let y = 60;
let dirX = 1; // box is always moving at a constant speed in one of the four diagonal directions
let dirY = 1;
function drawFrame(canvas) {
// boundary check
if (x < 0 || x+w > 300) {
dirX *= -1; // reverse x direction when hitting side walls
}
if (y < 0 || y+h > 300) {
dirY *= -1; // reverse y direction when hitting top and bottom walls
}
// move
x += dirX;
y += dirY;
canvas.clear(CanvasKit.WHITE);
const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(x, y, x+w, y+h), 25, 15);
canvas.drawRRect(rr, paint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
});
```
<canvas id=foo2 width=300 height=300></canvas>
<script type="text/javascript">
ckLoaded.then((CanvasKit) => {
const surface = CanvasKit.MakeCanvasSurface('foo2');
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
// const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
const w = 100; // size of rect
const h = 60;
let x = 10; // initial position of top left corner.
let y = 60;
// The box is always moving at a constant speed in one of the four diagonal directions
let dirX = 1;
let dirY = 1;
function drawFrame(canvas) {
// boundary check
if (x < 0 || x+w > 300) {
dirX *= -1; // reverse x direction when hitting side walls
}
if (y < 0 || y+h > 300) {
dirY *= -1; // reverse y direction when hitting top and bottom walls
}
// move
x += dirX;
y += dirY;
canvas.clear(CanvasKit.WHITE);
const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(x, y, x+w, y+h), 25, 15);
canvas.drawRRect(rr, paint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
});
</script>
The main difference here is that we define a function to be called before each frame is drawn and
pass it to `surface.requestAnimationFrame(drawFrame);` That callback is handed a `canvas` and
flushing is taken care of.
<!--?prettify?-->
``` js
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
// code to update and draw the frame goes here
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
```
Creates a function to serve as our main drawing loop. Each time a frame is about to be rendered
(the browser will typically target 60fps), our function is called, we clear the canvas with white,
redraw the round rect, and call `surface.requestAnimationFrame(drawFrame)` registering the
function to be called again before the next frame.
`surface.requestAnimationFrame(drawFrame)` combines window.requestAnimationFrame with
`surface.flush()` and should be used in all the same ways. If your application would only make
visible changes as a result of mouse events,
don't call `surface.requestAnimationFrame` at the end of your drawFrame function. Call it only
after handling mouse input.
Text Shaping
------------
One of the biggest features that CanvasKit offers over the HTML Canvas API is paragraph shaping.
To use text your applicatoin, supply a font file and use Promise.all to run your code when both
CanvasKit and the font file are ready.
<!--?prettify?-->
``` js
const loadFont = fetch('https://storage.googleapis.com/skia-cdn/misc/Roboto-Regular.ttf')
.then((response) => response.arrayBuffer());
Promise.all([ckLoaded, loadFont]).then(([CanvasKit, robotoData]) => {
const surface = CanvasKit.MakeCanvasSurface('foo3');
const canvas = surface.getCanvas();
canvas.clear(CanvasKit.Color4f(0.9, 0.9, 0.9, 1.0));
const fontMgr = CanvasKit.SkFontMgr.FromData([robotoData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 28,
},
textAlign: CanvasKit.TextAlign.Left,
});
const text = 'Any sufficiently entrenched technology is indistinguishable from Javascript';
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text);
const paragraph = builder.build();
paragraph.layout(290); // width in pixels to use when wrapping text
canvas.drawParagraph(paragraph, 10, 10);
surface.flush();
});
```
<canvas id=foo3 width=300 height=300></canvas>
<script type="text/javascript">
const loadFont = fetch('https://storage.googleapis.com/skia-cdn/misc/Roboto-Regular.ttf')
.then((response) => response.arrayBuffer());
Promise.all([ckLoaded, loadFont]).then(([CanvasKit, robotoData]) => {
const surface = CanvasKit.MakeCanvasSurface('foo3');
const canvas = surface.getCanvas();
canvas.clear(CanvasKit.Color4f(0.9, 0.9, 0.9, 1.0));
const fontMgr = CanvasKit.SkFontMgr.FromData([robotoData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 28,
},
textAlign: CanvasKit.TextAlign.Left,
});
const text = 'Any sufficiently entrenched technology is indistinguishable from Javascript';
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text);
const paragraph = builder.build();
paragraph.layout(290); // width in pixels to use when wrapping text
canvas.drawParagraph(paragraph, 10, 10);
surface.flush();
});
</script>
<!--?prettify?-->
``` js
const fontMgr = CanvasKit.SkFontMgr.FromData([robotoData]);
```
Creates an object that provides fonts by name to various text facilities in CanvasKit. You could
load more than one font in this statement if needed.
<!--?prettify?-->
``` js
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 28,
},
textAlign: CanvasKit.TextAlign.Left,
});
```
Specifies the style of the text. The font's name, Roboto, will be used to fetch it from the font
manager. You can specify either (color) or (foregroundColor and backgroundColor) in order to have
a highlight. TODO(nifong): link to the full reference for ParagraphStyle / TextStyle
<!--?prettify?-->
``` js
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text);
const paragraph = builder.build();
```
Next, we create a `ParagraphBuilder` with a style, add some text, and finalize it with `build()`.
Alternatively, we could use multiple `TextStyle`s in one paragraph with
<!--?prettify?-->
``` js
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text1);
const boldTextStyle = CanvasKit.TextStyle({
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 28,
fontStyle: {'weight': CanvasKit.FontWeight.Bold},
})
builder.pushStyle(boldTextStyle);
builder.addText(text2);
builder.pop();
builder.addText(text3);
const paragraph = builder.build();
```
Finally, we *layout* the paragraph, meaning wrap the text to a particular width, and draw it to
the canvas with
<!--?prettify?-->
``` js
paragraph.layout(290); // width in pixels to use when wrapping text
canvas.drawParagraph(paragraph, 10, 10); // (x, y) position of left top corner of paragraph.
```