Canvas Creation
Skia has multiple backends which receive Canvas
drawing
commands, including:
Raster - CPU-only.
GPU - Skia’s GPU-accelerated backend.
PDF - PDF document creation.
Picture - Skia’s display list format.
SVG - Experimental SVG backend.
Each backend has a unique way of creating a Canvas
. 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 Surface
, which is an object that manages the memory
into which the canvas commands are drawn.
import skia
width, height = 200, 200
surface = skia.Surface(width, height)
with surface as canvas:
canvas.drawCircle(100, 100, 40, skia.Paint(Color=skia.ColorGREEN))
image = surface.makeImageSnapshot()
image.save('output.png', skia.kPNG)
Alternatively, we could have specified the memory for the surface explicitly, instead of asking Skia to manage it. The following example directly draws into numpy array.
import skia
import numpy as np
import matplotlib.pyplot as plt
width, height = 200, 200
array = np.zeros((height, width, 4), dtype=np.uint8)
with skia.Surface(array) as canvas:
canvas.drawCircle(100, 100, 40, skia.Paint(Color=skia.ColorGREEN))
plt.imshow(array)
plt.show()
GPU
GPU Surfaces must have a GrDirectContext
object which manages the
GPU context, and related caches for textures and fonts. GrDirectContexts are matched
one to one with OpenGL contexts or Vulkan devices. That is, all
Surface
that will be rendered to using the same OpenGL context
or Vulkan device should share a GrDirectContext
. 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.
The following example uses glfw package to create an OpenGL context. Install
glfw
via pip to try.
pip install glfw
import skia
import glfw
import contextlib
@contextlib.contextmanager
def glfw_context():
if not glfw.init():
raise RuntimeError('glfw.init() failed')
glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
glfw.window_hint(glfw.STENCIL_BITS, 8)
# see https://www.glfw.org/faq#macos
if sys.platform.startswith("darwin"):
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(640, 480, '', None, None)
glfw.make_context_current(window)
yield window
glfw.terminate()
width, height = 200, 200
with glfw_context():
context = skia.GrDirectContext.MakeGL()
info = skia.ImageInfo.MakeN32Premul(width, height)
surface = skia.Surface.MakeRenderTarget(context, skia.Budgeted.kNo, info)
assert surface is not None
with surface as canvas:
canvas.drawCircle(100, 100, 40, skia.Paint(Color=skia.ColorGREEN))
image = surface.makeImageSnapshot()
assert image is not None
image.save('output.png', skia.kPNG)
OpenGL window
In the previous example, even though the rendering is executed on the GPU, nothing is actually shown on the screen. It is possible to use Skia to draw directly to a window on the screen. To do this:
You have to use GLFW or other similar library to create and manage the window.
GrBackendRenderTarget
has to be created with a surface attached to it.The rendered image has to be flushed to the screen by calling
Surface.flushAndSubmit()
.
Here’s a complete example:
import contextlib, glfw, skia
from OpenGL import GL
WIDTH, HEIGHT = 640, 480
@contextlib.contextmanager
def glfw_window():
if not glfw.init():
raise RuntimeError('glfw.init() failed')
glfw.window_hint(glfw.STENCIL_BITS, 8)
# see https://www.glfw.org/faq#macos
if sys.platform.startswith("darwin"):
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(WIDTH, HEIGHT, '', None, None)
glfw.make_context_current(window)
yield window
glfw.terminate()
@contextlib.contextmanager
def skia_surface(window):
context = skia.GrDirectContext.MakeGL()
(fb_width, fb_height) = glfw.get_framebuffer_size(window)
backend_render_target = skia.GrBackendRenderTarget(
fb_width,
fb_height,
0, # sampleCnt
0, # stencilBits
skia.GrGLFramebufferInfo(0, GL.GL_RGBA8))
surface = skia.Surface.MakeFromBackendRenderTarget(
context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin,
skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB())
assert surface is not None
yield surface
context.abandonContext()
with glfw_window() as window:
GL.glClear(GL.GL_COLOR_BUFFER_BIT)
with skia_surface(window) as surface:
with surface as canvas:
canvas.drawCircle(100, 100, 40, skia.Paint(Color=skia.ColorGREEN))
surface.flushAndSubmit()
glfw.swap_buffers(window)
while (glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
and not glfw.window_should_close(window)):
glfw.wait_events()
PDF
The PDF
backend uses Document
instead of
Surface
, since a document must include multiple pages. The
following demonstrates how to write a single-page PDF document to a file:
import skia
width, height = 200, 200
stream = skia.FILEWStream('output.pdf')
with skia.PDF.MakeDocument(stream) as document:
with document.page(width, height) as canvas:
canvas.drawCircle(100, 100, 40, skia.Paint(Color=skia.ColorGREEN))
Picture
The Picture
backend uses PictureRecorder
instead of Surface
.
import skia
width, height = 200, 200
recorder = skia.PictureRecorder()
canvas = recorder.beginRecording(width, height)
canvas.drawCircle(100, 100, 40, skia.Paint(Color=skia.ColorGREEN))
picture = recorder.finishRecordingAsPicture()
data = picture.serialize()
Recorded picture can later be drawn onto another canvas:
picture.playback(canvas)
SVG
The (still experimental) SVGCanvas
writes into an SVG document.
import skia
width, height = 200, 200
stream = skia.FILEWStream('output.svg')
canvas = skia.SVGCanvas.Make((width, height), stream)
draw(canvas)
del canvas
stream.flush()