also, update some tools. DOCS_PREVIEW= https://skia.org/user/sample/pdf?cl=1936283003 DOCS_PREVIEW= https://skia.org/user/api/canvas?cl=1936283003 Review-Url: https://codereview.chromium.org/1936283003
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");
}