Streamlit - Drawable Canvas
This project is best effort. Every now and then I'll add something I need myself and let a coding agent do most of the typing, but I don't have the time to go through bigger issues or pull requests. If there's a larger feature you want, fork away!
Please add a thumbs up HERE if you wish to see a native implementation maintained by the Streamlit team.
Streamlit component which provides a sketching canvas using Fabric.js.
Features
- Draw freely, lines, circles, boxes and polygons on the canvas, with options on stroke & fill
- Rotate, skew, scale, move any object of the canvas on demand
- Select a background color or image to draw on
- Get image data and every drawn object properties back to Streamlit !
- Choose to fetch back data in realtime or on demand with a button
- Undo, Redo or Delete canvas contents
- Save canvas data as JSON to reuse for another session
Installation
Requires Streamlit >= 1.53 and Python >= 3.10 (0.10.0 is built on Streamlit Components v2; see Upgrading from 0.9.x if you're on an older Streamlit).
pip install streamlit-drawable-canvas
Example Usage
Copy this code snippet:
import pandas as pd
from PIL import Image
import streamlit as st
from streamlit_drawable_canvas import st_canvas
# Specify canvas parameters in application
drawing_mode = st.sidebar.selectbox(
"Drawing tool:", ("point", "freedraw", "line", "rect", "circle", "polygon", "text")
)
stroke_width = st.sidebar.slider("Stroke width: ", 1, 25, 3)
if drawing_mode == "point":
point_display_radius = st.sidebar.slider("Point display radius: ", 1, 25, 3)
stroke_color = st.sidebar.color_picker("Stroke color hex: ")
bg_color = st.sidebar.color_picker("Background color hex: ", "#eee")
bg_image = st.sidebar.file_uploader("Background image:", type=["png", "jpg"])
realtime_update = st.sidebar.checkbox("Update in realtime", True)
# Create a canvas component
canvas_result = st_canvas(
fill_color="rgba(255, 165, 0, 0.3)", # Fixed fill color with some opacity
stroke_width=stroke_width,
stroke_color=stroke_color,
background_color=bg_color,
background_image=Image.open(bg_image) if bg_image else None,
update_streamlit=realtime_update,
height=150,
drawing_mode=drawing_mode,
point_display_radius=point_display_radius if drawing_mode == "point" else 0,
return_image_data=True,
key="canvas",
)
# Do something interesting with the image data and paths
if canvas_result.image_data is not None:
st.image(canvas_result.image_data)
if canvas_result.json_data is not None:
objects = pd.json_normalize(
canvas_result.json_data["objects"]
) # need to convert obj to str because PyArrow
for col in objects.select_dtypes(include=["object"]).columns:
objects[col] = objects[col].astype("str")
st.dataframe(objects)
You will find more detailed examples on the demo app.
For reading the returned drawing -- what's in json_data, why a resized shape keeps its
original width, how to map canvas coordinates back to your source image -- see
FAQ.md.
API
st_canvas(
fill_color: str
stroke_width: int
stroke_color: str
background_color: str
background_image: str | Path | bytes | Image
update_streamlit: bool
height: int
width: int
drawing_mode: str
initial_drawing: dict
point_display_radius: int
return_image_data: bool
key: str
on_change: callable
disabled: bool
background_image_fit: str
max_display_height: int
font_size: int
)
- fill_color : Color of fill for Rect/Circle/Polygon in CSS color property. Defaults
to "#eee". In
drawing_mode="text", defaults tostroke_colorinstead -- "#eee" text on a default canvas would be all but invisible. Passing a value explicitly behaves identically in every mode. - stroke_width : Width of drawing brush in CSS color property. Defaults to 20.
- stroke_color : Color of drawing brush in hex. Defaults to "black".
- background_color : Color of canvas background in CSS color property. Defaults to "" which is transparent. Overriden by background_image. Changing background_color will reset the drawing.
- background_image : Image to display behind canvas: an http(s) URL, a
data:URI, a local file path, raw image bytes, or a Pillow Image. Automatically resized to canvas dimensions. Being behind the canvas, it is not sent back to Streamlit on mouse event. Overrides background_color. Changes to this will reset canvas contents. - update_streamlit : Whenever True, send canvas data to Streamlit when object/selection is updated or mouse up. Forced off for
drawing_mode="polygon"-- an in-progress multi-click polygon isn't a meaningful intermediate value; the completed polygon still sends once closed. When nothing sends automatically, the toolbar stays pinned open instead of appearing on hover, because its send button is then the only discoverable way to commit a drawing. If what you want is "only give me the finished drawing", prefer anst.formoverupdate_streamlit=False-- see FAQ.md. - height : Height of canvas in pixels. Defaults to 400.
- width : Width of canvas in pixels. Defaults to 600.
- drawing_mode : One of
"freedraw","line","rect","circle","point","polygon","text". Text placement when "text", otherwise create new objects with the rest. Defaults to "freedraw". Any other value raisesValueError.- On "polygon" mode, click to add a vertex; every vertex shows a handle. Click the first vertex's handle to close the shape; click any other handle to remove that vertex.
- On "text" mode, clicking places an empty text object and starts editing it immediately; click elsewhere (or Escape/blur) to finish. Nothing is sent to Streamlit until editing ends.
- Editing (moving, scaling, rotating, click-to-edit existing text) isn't a drawing mode -- it's the toolbar's edit toggle, available regardless of
drawing_mode. It has two levels: selecting an object lets you move/scale/rotate it as a whole (level 1); clicking an already-selected polygon, line, rect or circle a second time descends into point editing (level 2), where dragging a handle moves an individual vertex/endpoint/rim point instead of the whole shape. See FAQ.md for the per-shape gesture table.
- initial_drawing : Initialize canvas with drawings from here. Should be the
json_dataoutput from another canvas. Beware: if you try to import a drawing from a bigger/smaller canvas, no rescaling is done in the canvas and the import could fail. - point_display_radius : To make points visible on the canvas, they are drawn as circles. This parameter modifies the radius of the displayed circle.
- return_image_data : If
True, populateimage_data(RGBA numpy array) andimage_bytes(raw PNG bytes, forst.download_button) on the result.Falseby default -- it PNG-encodes the whole canvas on every send. Accessing either without it raisesRuntimeError. - key : An optional string to use as the unique key for the widget. Assign a key so the component is not remounted on every rerun.
- on_change : Optional callback invoked when the component sends a new drawing.
- background_image_fit : One of
"stretch"(default) or"contain"."stretch"scales each axis independently to fill the canvas exactly, distorting the image when the aspect ratios differ -- this is the historical behaviour."contain"preserves the aspect ratio, fitting the image inside the canvas and centring it, so a canvas larger than its background image gets margins instead of a stretched image. Ignored when nobackground_imageis set. Any other value raisesValueError. - disabled : If
True, render the canvas read-only -- drawing, selection and transforms are all inert, nothing is sent back to Streamlit, and the toolbar is hidden.initial_drawingstill renders, so this is how you show a drawing back to someone without letting them change it. Defaults toFalse. - max_display_height : Caps the canvas's displayed height in pixels and makes it scroll vertically inside that box.
height, canvas pixel dimensions, andjson_datacoordinates are unaffected.None(the default) displays the canvas at its full height. Horizontal scrolling is always available, independent of this parameter. - font_size : Font size in pixels for text placed in
drawing_mode="text". Defaults to 20. Ignored in every other mode.
Example:
import streamlit as st
from streamlit_drawable_canvas import st_canvas
canvas_result = st_canvas()
st_canvas(initial_drawing=canvas_result.json_data)
Upgrading from 0.9.x
0.10.0 is a breaking release (Streamlit Components v2, Fabric.js 7). If you're upgrading:
image_dataraisesRuntimeError-- it's now opt-in. Passreturn_image_data=Truetost_canvas().- Old Streamlit or Python -- 0.10.0 needs Streamlit >= 1.53 and Python >= 3.10. If
you can't upgrade, pin
streamlit-drawable-canvas==0.9.3. - Saved drawings from 0.9.x with Circle or Point objects render as a thin sliver, not
the original shape, when fed back in via
initial_drawing. Fabric 4 wroteCircle.startAngle/endAnglein radians; Fabric 7 reinterprets those same JSON keys as degrees, andloadFromJSONdoesn't consult the JSON'sversionfield to tell the difference. This is declared breaking, with no migration shim. Line, Rect, freedraw, Polygon, and Edit-mode objects are unaffected -- only objects fromcircle/pointdrawing modes carrystartAngle/endAngle.
Development
Tasks are automated with just (see justfile) and uv. Run just (or just --list) to see every recipe.
Install
just setup # uv sync + npm ci (frontend) + pre-commit install
just reinstall # same, but wipes .venv / node_modules / build outputs first
Run the demo app
just demo # uv run streamlit run demo_app.py
For frontend changes, run the Vite watch-rebuild alongside it in another terminal --
it rebuilds frontend/build on every save, which just demo's Streamlit process picks
up on the next rerun:
just dev
Lint, format, test
just lint # ruff check + tsc --noEmit + prettier check
just format # ruff format + prettier write
just test # pytest + Vitest
End-to-end tests (Playwright)
just e2e-setup # one-time: install deps + browsers
just build # E2E needs the built frontend
just e2e # uv run pytest e2e_playwright -n auto
See the justfile (just --list) for the full recipe reference, including
version bumps and publishing.
References
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file streamlit_drawable_canvas-0.12.0.tar.gz.
File metadata
- Download URL: streamlit_drawable_canvas-0.12.0.tar.gz
- Upload date:
- Size: 126.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93738edbc03fbb6f4071723a4a71e43193abbc8465557e0a887f1a776fd17c08
|
|
| MD5 |
5ee397916e54604e36888f4bcc393bf7
|
|
| BLAKE2b-256 |
03c5ee2c40cc0a51c50b30b4b1ac439e89bb4826f3ccb0ecd0d366ad3d811519
|
File details
Details for the file streamlit_drawable_canvas-0.12.0-py3-none-any.whl.
File metadata
- Download URL: streamlit_drawable_canvas-0.12.0-py3-none-any.whl
- Upload date:
- Size: 122.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
daed7667c3783b7be2a45fa796ad6d8ffcd0e089aa063f313e1311197dfddb7d
|
|
| MD5 |
1731efaef04fae1f4623613765a5bb8f
|
|
| BLAKE2b-256 |
64130316045859092a44ea18ec2de78d42cd66007846dbf8e1e3116dd891f34a
|