Creating SkCanvas Objects
=========================
First, read about [the SkCanvas API](skcanvas).
Skia has multiple backends which receive SkCanvas drawing commands,
including:
- [Raster](#raster) - CPU-only.
- [Ganesh](#ganesh) - Skia's GPU-accelerated backend.
- [SkPDF](#skpdf) - PDF document creation.
- [SkPicture](#skpicture) - Skia's display list format.
- [NullCanvas](#nullcanvas) - Useful for testing only.
- [SkXPS](#skxps) - Experimental XPS backend.
- [SkSVG](#sksvg) - Experimental XPS backend.
Each backend has a unique way of creating a SkCanvas. This page gives
an example for each:
Raster
------
The raster backend draws to a block of memory. This memory can be
managed by Skia or by the client.
The recommended way of creating a canvas for the Raster and Ganesh
backends is to use a `SkSurface`, which is an object that manages
the memory into which the canvas commands are drawn.
#include "SkData.h"
#include "SkImage.h"
#include "SkStream.h"
#include "SkSurface.h"
void raster(int width, int height,
void(*draw)(SkCanvas*),
const char* path) {
SkAutoTUnref rasterSurface(
SkSurface::NewRasterN32Premul(width, height));
SkCanvas* rasterCanvas = rasterSurface->getCanvas();
draw(rasterCanvas);
SkAutoTUnref img(s->newImageSnapshot());
if (!img) { return; }
SkAutoTUnref png(img->encode());
if (!png) { return; }
SkFILEWStream out(path);
(void)out.write(png->data(), png->size());
}
Alternatively, we could have specified the memory for the surface
explicitly, instead of asking Skia to manage it.
std::vector raster_direct(int width, int height,
void(*draw)(SkCanvas*)) {
SkImageInfo info = SkImageInfo::MakeN32(width, height);
size_t rowBytes = info.minRowBytes();
size_t size = info.getSafeSize(rowBytes);
std::vector pixelMemory(size); // allocate memory
SkAutoTUnref surface(
SkSurface::NewRasterDirect(
info, &pixelMemory[0], rowBytes));
SkCanvas* canvas = surface.getCanvas();
draw(canvas);
return std::move(pixelMemory);
}
Ganesh
------
Ganesh Surfaces must have a `GrContext` object which manages the
GPU context, and related caches for textures and fonts. In this
example, we use a `GrContextFactory` to create a context.
#include "GrContextFactory.h"
#include "SkData.h"
#include "SkImage.h"
#include "SkStream.h"
#include "SkSurface.h"
void ganesh(int width, int height,
void(*draw)(SkCanvas*),
const char* path) {
GrContextFactory grFactory;
GrContext* context = grFactory.get(GrContextFactory::kNative_GLContextType);
SkImageInfo info = SkImageInfo:: MakeN32Premul(width, height);
SkAutoTUnref gpuSurface(
SkSurface::NewRenderTarget(context, SkSurface::kNo_Budgeted, info));
if (!gpuSurface) {
SkDebugf("SkSurface::NewRenderTarget returned null\n");
return;
}
SkCanvas* gpuCanvas = gpuSurface->getCanvas();
draw(gpuCanvas);
SkAutoTUnref img(s->newImageSnapshot());
if (!img) { return; }
SkAutoTUnref png(img->encode());
if (!png) { return; }
SkFILEWStream out(path);
(void)out.write(png->data(), png->size());
}
SkPDF
-----
The SkPDF backend uses `SkDocument` instead of `SkSurface`, since
a document must include multiple pages.
#include "SkDocument.h"
#include "SkStream.h"
void skpdf(int width, int height,
void(*draw)(SkCanvas*),
const char* path) {
SkFILEWStream pdfStream(path);
SkAutoTUnref pdfDoc(SkDocument::CreatePDF(&pdfStream));
SkCanvas* pdfCanvas = pdfDoc->beginPage(SkIntToScalar(width),
SkIntToScalar(height));
draw(pdfCanvas);
pdfDoc->close();
}
SkPicture
---------
The SkPicture backend uses SkPictureRecorder instead of SkSurface.
#include "SkPictureRecorder"
#include "SkPicture"
#include "SkStream.h"
void picture(int width, int height,
void(*draw)(SkCanvas*),
const char* path) {
SkPictureRecorder recorder;
SkCanvas* recordingCanvas = recorder.beginRecording(SkIntToScalar(width),
SkIntToScalar(height));
draw(recordingCanvas);
SkAutoTUnref picture(recorder.endRecordingAsPicture());
SkFILEWStream skpStream(path);
// Open SKP files with `SampleApp --picture SKP_FILE`
picture->serialize(&skpStream);
}
NullCanvas
----------
The null canvas is a canvas that ignores all drawing commands and does
nothing.
#include "SkNullCanvas.h"
void picture(int, int, void(*draw)(SkCanvas*), const char*) {
SkAutoTDelete nullCanvas(SkCreateNullCanvas());
draw(nullCanvas); // NoOp
}
SkXPS
-----
The (*still experimental*) SkXPS canvas writes into an XPS document.
#include "SkDocument.h"
#include "SkStream.h"
void skxps(int width, int height,
void(*draw)(SkCanvas*),
const char* path) {
SkFILEWStream xpsStream(path);
SkAutoTUnref xpsDoc(SkDocument::CreateXPS(&pdfStream));
SkCanvas* xpsCanvas = xpsDoc->beginPage(SkIntToScalar(width),
SkIntToScalar(height));
draw(xpsCanvas);
xpsDoc->close();
}
SkSVG
-----
The (*still experimental*) SkSVG canvas writes into an SVG document.
#include "SkStream.h"
#include "SkSVGCanvas.h"
#include "SkXMLWriter.h"
void sksvg(int width, int height,
void(*draw)(SkCanvas*),
const char* path) {
SkFILEWStream svgStream(path);
SkAutoTDelete xmlWriter(SkNEW_ARGS(SkXMLStreamWriter, (&svgStream)));
SkAutoTUnref svgCanvas(SkSVGCanvas::Create(
SkRect::MakeWH(SkIntToScalar(src.size().width()),
SkIntToScalar(src.size().height())),
xmlWriter));
draw(svgCanvas);
}
Example
-------
To try this code out, make a [new unit test using instructions
here](/dev/testing/tests) and wrap these funtions together:
#include "SkCanvas.h"
#include "SkPath.h"
#include "Test.h"
void example(SkCanvas* canvas) {
const SkScalar scale = 256.0f;
const SkScalar R = 0.45f * scale;
const SkScalar TAU = 6.2831853f;
SkPath path;
for (int i = 0; i < 5; ++i) {
SkScalar theta = 2 * i * TAU / 5;
if (i == 0) {
path.moveTo(R * cos(theta), R * sin(theta));
} else {
path.lineTo(R * cos(theta), R * sin(theta));
}
}
path.close();
SkPaint p;
p.setAntiAlias(true);
canvas->clear(SK_ColorWHITE);
canvas->translate(0.5f * scale, 0.5f * scale);
canvas->drawPath(path, p);
}
DEF_TEST(FourBackends, r) {
raster( 256, 256, example, "out_raster.png" );
ganesh( 256, 256, example, "out_ganesh.png" );
skpdf( 256, 256, example, "out_skpdf.pdf" );
picture(256, 256, example, "out_picture.skp");
}