packages

Drawing canvas

DrawingCanvas is a view the user draws on with a finger, a pen or the mouse. It keeps an undo and redo history, can save and load strokes, and exports the drawing as a PNG or JPEG file, trimmed to the ink if you want. Every platform uses the same stroke format and the same curve, so strokes saved on one platform look the same on another.

  • pyxpo-drawing-canvas
  • v0.1.0a1
  • Native: Swift + Kotlin
  • Like react-native-skia and signature-canvas style drawing views
Ink drawn on the drawing canvas on Android and iOS.
Ink drawn on the drawing canvas on Android and iOS.

Install

Install the package into the same environment as pyxpo.

$pip install pyxpo-drawing-canvas

Add drawing-canvas to modules in your app config, then import it in your code.

pyproject.toml
[tool.pyxpo]
modules = ["drawing-canvas"]
python
import pyxpo_drawing_canvas as DrawingCanvas

Needs Python 3.9+ and pyxpo>=0.1.0a3.

This module has native code. After you add it, run pyxpo run or pyxpo build again so it is compiled into the app. Hot reload alone is not enough.

Platforms

iOSiOS 15+. Finger and Apple Pencil, drawn with Core Graphics.
AndroidAPI 24+. Finger and stylus, drawn with Canvas.
Desktop previewMouse and pen through pointer events, drawn on an HTML canvas.
WebThe same as the desktop window.

Quick start

app/index.py
import pyxpo_drawing_canvas as DrawingCanvas
from pyxpo import Button, View, component, use_ref

@component
def Sketch():
    canvas = use_ref(None)

    async def export():
        image = await canvas.current.to_image_async(trim=True, padding=16)
        if image:
            print(image["uri"], image["width"], image["height"])

    return View(
        DrawingCanvas.DrawingCanvas(ref=canvas, stroke_color="#222222", stroke_width=6,
                                    style={"flex": 1}),
        Button(title="Export", on_press=export),
        style={"flex": 1},
    )

Permissions

pyxpo adds these to Info.plist and AndroidManifest.xml for you when the module is listed.

iOSNone.
AndroidNone.

API reference

DrawingCanvas props

stroke_color="#000000"

Any pyxpo color. Applies to the next stroke.

stroke_width=4

In pt/dp, greater than 0 and at most 500. The eraser uses it too.

tool="pen"

"pen" or "eraser". The eraser removes ink and leaves the background.

background_color="#ffffff"

Shown under the ink and used by to_image_async(background=True).

enabled=True

False ignores touches.

on_change=None

Called with {"stroke_count", "can_undo", "can_redo"} after each stroke, undo, redo, clear and set_strokes_async.

style

Sizes the canvas, for example {"flex": 1}.

ref.current

camelCase names also work: getStrokesAsync, setStrokesAsync, toImageAsync.

undo(), redo()→ None

Take back or apply again the last stroke or clear().

clear()→ None

Removes every stroke. undo() brings them back.

await get_strokes_async()→ list[dict]

The visible strokes, oldest first, as {"tool", "color" (0xAARRGGBB), "width", "points"}. points is flat [x0, y0, x1, y1, ...] in pt/dp from the view's top left.

await set_strokes_async(strokes)→ None

Replaces the drawing and empties undo and redo. color takes any pyxpo color.

await to_image_async(format="png", trim=False, padding=0, scale=None, background=True, quality=1)→ {"uri", "width", "height", "x", "y"} or None

Writes the drawing to a file in the app cache. trim=True crops to the ink plus padding pt/dp and returns None for an empty canvas; x and y are the crop's top left in the view. scale is pixels per pt/dp (default: the screen's). background=False gives a transparent PNG. quality (0 to 1) is for jpg.

Examples

Save and restore a drawing

Strokes are plain JSON, so you can store them and load them later.

Ink drawn on the canvas on Android and iOS.
Ink drawn on the canvas on Android and iOS.
python
import json
from pyxpo_file_system import File, Paths

async def save(canvas):
    File(Paths.document, "sketch.json").write(json.dumps(await canvas.get_strokes_async()))

async def load(canvas):
    f = File(Paths.document, "sketch.json")
    if f.exists:
        await canvas.set_strokes_async(json.loads(f.text_sync()))

Good to know

  • On the web and the desktop window the page encodes the image and Python writes it to ~/.cache/pyxpo/drawing-canvas/ (or $XDG_CACHE_HOME), so uri is a file:// path on every platform.
  • Export renders the strokes again at the requested scale, so a small screen still gives a sharp large image.