First, read about the SkCanvas API.
Skia has multiple backends which receive SkCanvas drawing commands, including:
Each backend has a unique way of creating a SkCanvas. This page gives an example for each:
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<SkSurface> rasterSurface( SkSurface::NewRasterN32Premul(width, height)); SkCanvas* rasterCanvas = rasterSurface->getCanvas(); draw(rasterCanvas); SkAutoTUnref<SkImage> img(s->newImageSnapshot()); if (!img) { return; } SkAutoTUnref<SkData> 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<char> 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<char> pixelMemory(size); // allocate memory SkAutoTUnref<SkSurface> surface( SkSurface::NewRasterDirect( info, &pixelMemory[0], rowBytes)); SkCanvas* canvas = surface.getCanvas(); draw(canvas); return std::move(pixelMemory); }
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<SkSurface> gpuSurface( SkSurface::NewRenderTarget(context, SkSurface::kNo_Budgeted, info)); if (!gpuSurface) { SkDebugf("SkSurface::NewRenderTarget returned null\n"); return; } SkCanvas* gpuCanvas = gpuSurface->getCanvas(); draw(gpuCanvas); SkAutoTUnref<SkImage> img(s->newImageSnapshot()); if (!img) { return; } SkAutoTUnref<SkData> png(img->encode()); if (!png) { return; } SkFILEWStream out(path); (void)out.write(png->data(), png->size()); }
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<SkDocument> pdfDoc(SkDocument::CreatePDF(&pdfStream)); SkCanvas* pdfCanvas = pdfDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height)); draw(pdfCanvas); pdfDoc->close(); }
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<SkPicture> picture(recorder.endRecordingAsPicture()); SkFILEWStream skpStream(path); // Open SKP files with `SampleApp --picture SKP_FILE` picture->serialize(&skpStream); }
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<SkCanvas> nullCanvas(SkCreateNullCanvas()); draw(nullCanvas); // NoOp }
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<SkDocument> xpsDoc(SkDocument::CreateXPS(&pdfStream)); SkCanvas* xpsCanvas = xpsDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height)); draw(xpsCanvas); xpsDoc->close(); }
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<SkXMLWriter> xmlWriter(SkNEW_ARGS(SkXMLStreamWriter, (&svgStream))); SkAutoTUnref<SkCanvas> svgCanvas(SkSVGCanvas::Create( SkRect::MakeWH(SkIntToScalar(src.size().width()), SkIntToScalar(src.size().height())), xmlWriter)); draw(svgCanvas); }
To try this code out, make a new unit test using instructions here 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"); }