skia2/site/user/api/canvas.md

7.8 KiB

Creating SkCanvas Objects

First, read about the SkCanvas API.

Skia has multiple backends which receive SkCanvas drawing commands, including:

  • Raster - CPU-only.
  • Ganesh - Skia's GPU-accelerated backend.
  • SkPDF - PDF document creation.
  • SkPicture - Skia's display list format.
  • NullCanvas - Useful for testing only.
  • SkXPS - Experimental XPS backend.
  • 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) {
    sk_sp<SkSurface> rasterSurface(
            SkSurface::MakeRasterN32Premul(width, height));
    SkCanvas* rasterCanvas = rasterSurface->getCanvas();
    draw(rasterCanvas);
    sk_sp<SkImage> img(s->newImageSnapshot());
    if (!img) { return; }
    sk_sp<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
    sk_sp<SkSurface> surface(
            SkSurface::MakeRasterDirect(
                    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);
    sk_sp<SkSurface> gpuSurface(
            SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
    if (!gpuSurface) {
        SkDebugf("SkSurface::MakeRenderTarget returned null\n");
        return;
    }
    SkCanvas* gpuCanvas = gpuSurface->getCanvas();
    draw(gpuCanvas);
    sk_sp<SkImage> img(s->newImageSnapshot());
    if (!img) { return; }
    sk_sp<SkData> 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);
    sk_sp<SkDocument> pdfDoc(SkDocument::MakePDF(&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);
    sk_sp<SkPicture> 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*) {
    sk_sp<SkCanvas> 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);
    sk_sp<SkDocument> xpsDoc(SkDocument::MakeXPS(&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);
    std::unique_ptr<SkXMLWriter> xmlWriter(
            new SkXMLStreamWriter(&svgStream));
    sk_sp<SkCanvas> 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 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");
}