2018-02-08 21:55:30 +00:00
|
|
|
SkCanvas Creation
|
2015-05-19 17:21:29 +00:00
|
|
|
=========================
|
|
|
|
|
2018-02-20 14:59:29 +00:00
|
|
|
First, read about [the SkCanvas API](skcanvas_overview).
|
2015-05-19 17:21:29 +00:00
|
|
|
|
|
|
|
Skia has multiple backends which receive SkCanvas drawing commands,
|
|
|
|
including:
|
|
|
|
|
|
|
|
- [Raster](#raster) - CPU-only.
|
2016-11-28 21:34:06 +00:00
|
|
|
- [GPU](#gpu) - Skia's GPU-accelerated backend.
|
2015-05-19 17:21:29 +00:00
|
|
|
- [SkPDF](#skpdf) - PDF document creation.
|
|
|
|
- [SkPicture](#skpicture) - Skia's display list format.
|
|
|
|
- [NullCanvas](#nullcanvas) - Useful for testing only.
|
|
|
|
- [SkXPS](#skxps) - Experimental XPS backend.
|
2017-08-02 20:15:00 +00:00
|
|
|
- [SkSVG](#sksvg) - Experimental SVG backend.
|
2015-05-19 17:21:29 +00:00
|
|
|
|
|
|
|
Each backend has a unique way of creating a SkCanvas. This page gives
|
|
|
|
an example for each:
|
|
|
|
|
|
|
|
<span id="raster"></span>
|
|
|
|
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.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
|
|
|
#include "SkData.h"
|
|
|
|
#include "SkImage.h"
|
|
|
|
#include "SkStream.h"
|
|
|
|
#include "SkSurface.h"
|
|
|
|
void raster(int width, int height,
|
2017-04-19 20:21:51 +00:00
|
|
|
void (*draw)(SkCanvas*),
|
2015-05-19 17:21:29 +00:00
|
|
|
const char* path) {
|
2017-04-19 20:21:51 +00:00
|
|
|
sk_sp<SkSurface> rasterSurface =
|
|
|
|
SkSurface::MakeRasterN32Premul(width, height);
|
2015-05-19 17:21:29 +00:00
|
|
|
SkCanvas* rasterCanvas = rasterSurface->getCanvas();
|
|
|
|
draw(rasterCanvas);
|
2017-04-19 20:21:51 +00:00
|
|
|
sk_sp<SkImage> img(rasterSurface->makeImageSnapshot());
|
2015-05-19 17:21:29 +00:00
|
|
|
if (!img) { return; }
|
2016-05-03 19:10:04 +00:00
|
|
|
sk_sp<SkData> png(img->encode());
|
2015-05-19 17:21:29 +00:00
|
|
|
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.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
2017-04-19 20:21:51 +00:00
|
|
|
#include <vector>
|
|
|
|
#include "SkSurface.h"
|
2015-05-19 17:21:29 +00:00
|
|
|
std::vector<char> raster_direct(int width, int height,
|
2017-04-19 20:21:51 +00:00
|
|
|
void (*draw)(SkCanvas*)) {
|
|
|
|
SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
|
2015-05-19 17:21:29 +00:00
|
|
|
size_t rowBytes = info.minRowBytes();
|
|
|
|
size_t size = info.getSafeSize(rowBytes);
|
|
|
|
std::vector<char> pixelMemory(size); // allocate memory
|
2017-04-19 20:21:51 +00:00
|
|
|
sk_sp<SkSurface> surface =
|
2016-05-03 19:10:04 +00:00
|
|
|
SkSurface::MakeRasterDirect(
|
2017-04-19 20:21:51 +00:00
|
|
|
info, &pixelMemory[0], rowBytes);
|
|
|
|
SkCanvas* canvas = surface->getCanvas();
|
2015-05-19 17:21:29 +00:00
|
|
|
draw(canvas);
|
2017-04-19 20:21:51 +00:00
|
|
|
return pixelMemory;
|
2015-05-19 17:21:29 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 21:34:06 +00:00
|
|
|
<span id="gpu"></span>
|
|
|
|
GPU
|
2015-05-19 17:21:29 +00:00
|
|
|
------
|
|
|
|
|
2016-11-28 21:34:06 +00:00
|
|
|
GPU Surfaces must have a `GrContext` object which manages the
|
|
|
|
GPU context, and related caches for textures and fonts. GrContexts
|
|
|
|
are matched one to one with OpenGL contexts or Vulkan devices. That is, all
|
|
|
|
SkSurfaces that will be rendered to using the same OpenGL context or Vulkan
|
|
|
|
device should share a GrContext. Skia does not create a OpenGL context or Vulkan
|
|
|
|
device for you. In OpenGL mode it also assumes that the correct OpenGL context
|
|
|
|
has been made current to the current thread when Skia calls are made.
|
2015-05-19 17:21:29 +00:00
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
2017-04-19 20:21:51 +00:00
|
|
|
|
Revert "Reland "Take GrContext private, GrDDLContext inherit from GrRecordingContext""
This reverts commit ff13ffbeacec5335cfdb84c8a9d20bb78e10da58.
Reason for revert: Blink in g3
Original change's description:
> Reland "Take GrContext private, GrDDLContext inherit from GrRecordingContext"
>
> This reverts commit 89bdc90ac877dc8cd49c86c35a20d9df0d3b9016.
>
> Reason for revert: Fix metal
>
> Original change's description:
> > Revert "Take GrContext private, GrDDLContext inherit from GrRecordingContext"
> >
> > This reverts commit d2daa94edeca88281796524fae45f38f11a7bef2.
> >
> > Reason for revert: Metaru
> >
> > Original change's description:
> > > Take GrContext private, GrDDLContext inherit from GrRecordingContext
> > >
> > > Woo!
> > >
> > > Change-Id: I8d201b709343dc18cad31ea740575285dd035f35
> > > Docs-Preview: https://skia.org/?cl=317436
> > > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/317436
> > > Reviewed-by: Robert Phillips <robertphillips@google.com>
> > > Reviewed-by: Brian Salomon <bsalomon@google.com>
> > > Commit-Queue: Adlai Holler <adlai@google.com>
> >
> > TBR=bsalomon@google.com,robertphillips@google.com,adlai@google.com
> >
> > Change-Id: I9b58dee285fbdc49ebc8e76df5da0fe224cf9787
> > No-Presubmit: true
> > No-Tree-Checks: true
> > No-Try: true
> > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/318758
> > Reviewed-by: Adlai Holler <adlai@google.com>
> > Commit-Queue: Adlai Holler <adlai@google.com>
>
> TBR=bsalomon@google.com,robertphillips@google.com,adlai@google.com
>
>
> Change-Id: Id4b10795193a904cd4ed8c36e60e74abe3b6702a
> Docs-Preview: https://skia.org/?cl=318759
> Cq-Include-Trybots: luci.skia.skia.primary:Build-Mac10.15.5-Clang-arm64-Debug-iOS_Metal,Build-Mac-Clang-x86_64-Debug-Metal
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/318759
> Commit-Queue: Adlai Holler <adlai@google.com>
> Reviewed-by: Adlai Holler <adlai@google.com>
TBR=bsalomon@google.com,robertphillips@google.com,adlai@google.com
Change-Id: Ib20fe933120d56b72efaec73a0bedec60bc28def
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/319184
Commit-Queue: Adlai Holler <adlai@google.com>
Reviewed-by: Adlai Holler <adlai@google.com>
2020-09-24 11:42:38 +00:00
|
|
|
#include "GrContext.h"
|
2016-11-28 21:34:06 +00:00
|
|
|
#include "gl/GrGLInterface.h"
|
2015-05-19 17:21:29 +00:00
|
|
|
#include "SkData.h"
|
|
|
|
#include "SkImage.h"
|
|
|
|
#include "SkStream.h"
|
|
|
|
#include "SkSurface.h"
|
2017-02-22 18:12:30 +00:00
|
|
|
|
2017-04-19 20:21:51 +00:00
|
|
|
void gl_example(int width, int height, void (*draw)(SkCanvas*), const char* path) {
|
2016-11-28 21:34:06 +00:00
|
|
|
// You've already created your OpenGL context and bound it.
|
|
|
|
const GrGLInterface* interface = nullptr;
|
|
|
|
// Leaving interface as null makes Skia extract pointers to OpenGL functions for the current
|
|
|
|
// context in a platform-specific way. Alternatively, you may create your own GrGLInterface and
|
|
|
|
// initialize it however you like to attach to an alternate OpenGL implementation or intercept
|
|
|
|
// Skia's OpenGL calls.
|
2017-07-25 14:05:01 +00:00
|
|
|
sk_sp<GrContext> context = GrContext::MakeGL(interface);
|
2015-05-19 17:21:29 +00:00
|
|
|
SkImageInfo info = SkImageInfo:: MakeN32Premul(width, height);
|
2016-05-03 19:10:04 +00:00
|
|
|
sk_sp<SkSurface> gpuSurface(
|
|
|
|
SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
|
2015-05-19 17:21:29 +00:00
|
|
|
if (!gpuSurface) {
|
2016-05-03 19:10:04 +00:00
|
|
|
SkDebugf("SkSurface::MakeRenderTarget returned null\n");
|
2015-05-19 17:21:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
SkCanvas* gpuCanvas = gpuSurface->getCanvas();
|
|
|
|
draw(gpuCanvas);
|
2016-11-28 21:34:06 +00:00
|
|
|
sk_sp<SkImage> img(gpuSurface->makeImageSnapshot());
|
2015-05-19 17:21:29 +00:00
|
|
|
if (!img) { return; }
|
2016-05-03 19:10:04 +00:00
|
|
|
sk_sp<SkData> png(img->encode());
|
2015-05-19 17:21:29 +00:00
|
|
|
if (!png) { return; }
|
|
|
|
SkFILEWStream out(path);
|
|
|
|
(void)out.write(png->data(), png->size());
|
|
|
|
}
|
|
|
|
|
|
|
|
<span id="skpdf"></span>
|
|
|
|
SkPDF
|
|
|
|
-----
|
|
|
|
|
|
|
|
The SkPDF backend uses `SkDocument` instead of `SkSurface`, since
|
|
|
|
a document must include multiple pages.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
2018-09-07 18:33:14 +00:00
|
|
|
#include "SkPDFDocument.h"
|
2015-05-19 17:21:29 +00:00
|
|
|
#include "SkStream.h"
|
|
|
|
void skpdf(int width, int height,
|
2017-04-19 20:21:51 +00:00
|
|
|
void (*draw)(SkCanvas*),
|
2015-05-19 17:21:29 +00:00
|
|
|
const char* path) {
|
|
|
|
SkFILEWStream pdfStream(path);
|
2019-01-07 15:00:48 +00:00
|
|
|
auto pdfDoc = SkPDF::MakeDocument(&pdfStream);
|
2015-05-19 17:21:29 +00:00
|
|
|
SkCanvas* pdfCanvas = pdfDoc->beginPage(SkIntToScalar(width),
|
|
|
|
SkIntToScalar(height));
|
|
|
|
draw(pdfCanvas);
|
|
|
|
pdfDoc->close();
|
|
|
|
}
|
|
|
|
|
|
|
|
<span id="skpicture"></span>
|
|
|
|
SkPicture
|
|
|
|
---------
|
|
|
|
|
|
|
|
The SkPicture backend uses SkPictureRecorder instead of SkSurface.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
2017-04-19 20:21:51 +00:00
|
|
|
#include "SkPictureRecorder.h"
|
|
|
|
#include "SkPicture.h"
|
2015-05-19 17:21:29 +00:00
|
|
|
#include "SkStream.h"
|
|
|
|
void picture(int width, int height,
|
2017-04-19 20:21:51 +00:00
|
|
|
void (*draw)(SkCanvas*),
|
2015-05-19 17:21:29 +00:00
|
|
|
const char* path) {
|
|
|
|
SkPictureRecorder recorder;
|
|
|
|
SkCanvas* recordingCanvas = recorder.beginRecording(SkIntToScalar(width),
|
|
|
|
SkIntToScalar(height));
|
|
|
|
draw(recordingCanvas);
|
2017-04-19 20:21:51 +00:00
|
|
|
sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
|
2015-05-19 17:21:29 +00:00
|
|
|
SkFILEWStream skpStream(path);
|
2018-06-18 21:09:01 +00:00
|
|
|
// Open SKP files with `viewer --skps PATH_TO_SKP --slide SKP_FILE`
|
2015-05-19 17:21:29 +00:00
|
|
|
picture->serialize(&skpStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
<span id="nullcanvas"></span>
|
|
|
|
NullCanvas
|
|
|
|
----------
|
|
|
|
|
|
|
|
The null canvas is a canvas that ignores all drawing commands and does
|
|
|
|
nothing.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
|
|
|
#include "SkNullCanvas.h"
|
2017-04-19 20:21:51 +00:00
|
|
|
void null_canvas_example(int, int, void (*draw)(SkCanvas*), const char*) {
|
|
|
|
std::unique_ptr<SkCanvas> nullCanvas = SkMakeNullCanvas();
|
|
|
|
draw(nullCanvas.get()); // NoOp
|
2015-05-19 17:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
<span id="skxps"></span>
|
|
|
|
SkXPS
|
|
|
|
-----
|
|
|
|
|
|
|
|
The (*still experimental*) SkXPS canvas writes into an XPS document.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
|
|
|
#include "SkDocument.h"
|
|
|
|
#include "SkStream.h"
|
2017-04-19 20:21:51 +00:00
|
|
|
#ifdef SK_BUILD_FOR_WIN
|
|
|
|
void skxps(IXpsOMObjectFactory* factory;
|
|
|
|
int width, int height,
|
|
|
|
void (*draw)(SkCanvas*),
|
2015-05-19 17:21:29 +00:00
|
|
|
const char* path) {
|
|
|
|
SkFILEWStream xpsStream(path);
|
2017-04-19 20:21:51 +00:00
|
|
|
sk_sp<SkDocument> xpsDoc = SkDocument::MakeXPS(&pdfStream, factory);
|
2015-05-19 17:21:29 +00:00
|
|
|
SkCanvas* xpsCanvas = xpsDoc->beginPage(SkIntToScalar(width),
|
|
|
|
SkIntToScalar(height));
|
|
|
|
draw(xpsCanvas);
|
|
|
|
xpsDoc->close();
|
|
|
|
}
|
2017-04-19 20:21:51 +00:00
|
|
|
#endif
|
2015-05-19 17:21:29 +00:00
|
|
|
|
|
|
|
<span id="sksvg"></span>
|
|
|
|
SkSVG
|
|
|
|
-----
|
|
|
|
|
|
|
|
The (*still experimental*) SkSVG canvas writes into an SVG document.
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
|
|
|
#include "SkStream.h"
|
|
|
|
#include "SkSVGCanvas.h"
|
|
|
|
#include "SkXMLWriter.h"
|
|
|
|
void sksvg(int width, int height,
|
2017-04-19 20:21:51 +00:00
|
|
|
void (*draw)(SkCanvas*),
|
2015-05-19 17:21:29 +00:00
|
|
|
const char* path) {
|
|
|
|
SkFILEWStream svgStream(path);
|
2016-05-03 19:10:04 +00:00
|
|
|
std::unique_ptr<SkXMLWriter> xmlWriter(
|
|
|
|
new SkXMLStreamWriter(&svgStream));
|
2017-04-19 20:21:51 +00:00
|
|
|
SkRect bounds = SkRect::MakeIWH(width, height);
|
|
|
|
std::unique_ptr<SkCanvas> svgCanvas =
|
|
|
|
SkSVGCanvas::Make(bounds, xmlWriter.get());
|
|
|
|
draw(svgCanvas.get());
|
2015-05-19 17:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
<span id="example"></span>
|
|
|
|
Example
|
|
|
|
-------
|
|
|
|
|
|
|
|
To try this code out, make a [new unit test using instructions
|
|
|
|
here](/dev/testing/tests) and wrap these funtions together:
|
|
|
|
|
|
|
|
<!--?prettify lang=cc?-->
|
|
|
|
|
|
|
|
#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) {
|
2017-04-19 20:21:51 +00:00
|
|
|
raster( 256, 256, example, "out_raster.png" );
|
|
|
|
gl_example( 256, 256, example, "out_gpu.png" );
|
|
|
|
skpdf( 256, 256, example, "out_skpdf.pdf" );
|
|
|
|
picture( 256, 256, example, "out_picture.skp");
|
2015-05-19 17:21:29 +00:00
|
|
|
}
|