# Canvas

> Draw text boxes, panels, progress bars, images, circles that hug the ground and routes with arrows on the game screen. The runtime draws them itself every frame without changing game state, so it's safe in multiplayer games; use it from Python, over HTTP, or by writing shared memory directly.

Source: https://war3ai.com/en/docs/canvas/

External programs can draw **text boxes, panels, progress bars, images, circles on the ground and routes on the ground (with arrows)** on the game screen, and the runtime draws them itself every frame. It's a good fit for your own HUD, guide lines, hints, teaching annotations and stream info boards.

## Canvas or JASS visual functions?

| | Canvas (this page) | [JASS visual functions](https://war3ai.com/en/docs/jass/) |
|---|---|---|
| Who draws it | The runtime draws it itself | The game itself (floating text, effects, boards, portrait dialogue…) |
| Multiplayer | **Safe**: drawn only on your local screen; creates no game objects and doesn't change game state | Single-player only |
| Style | Anything you like: Chinese fonts, rounded corners, translucency, borders, any color, local images | The game's native look |
| Following things | Follows units, world coordinates or screen positions; ground circles follow the terrain's rises and dips | Depends on the function |
| Cost | Measured at 0.2–0.35 ms per frame (9 elements) | About 13 ms per call |

You can use both together: JASS for effects in the game's native style, the canvas for custom panels, guide lines and hints.

## Python

```python
c = g.canvas                               # on first use the runtime installs its draw hook (~0.1 s)
c.text("title", "Hello, this is the canvas", screen=(40, 110), color=(255, 220, 80),
       bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True)
c.panel("status", "Companion · Sunny", ["Mood: happy", "Kills: 12"], screen=(16, 330))
c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%")   # follows the unit
c.text("tag", "Boss ultimate incoming!", unit=boss, lift=320, color=(255, 80, 80), size=22, bold=True)
c.circle("danger", (x, y), 300, color=(255, 60, 60), fill=(255, 60, 60, 60), width=3)          # danger zone on the ground
c.circle("aura", hero, 450, color=(80, 200, 255, 220))                                         # circle that follows a unit
c.path("route", [(x1, y1), (x2, y2), (x3, y3)], color=(255, 220, 0), width=5, arrow=True)
c.image("icon", "icon.png", screen=(40, 170), width=64, height=64)
c.remove("danger"); c.hide("tag"); c.clear()   # clear only removes what you drew
c.expire("tag", 5)                         # disappears by itself after 5 seconds
with c.batch(): ...                        # change many entries at once, with a single shared-memory write
c.stats()                                  # drawnFrames going up = it's really drawing
```

Each element is identified by a `key`: drawing the same key again updates it.

**Clickable**: add `clickable=True` to a text box or panel (set the hover color with `hover=`). When it's clicked, a `ui.click` arrives in the event stream with `ev.key` set to that key, and the game never receives that click. For ready-made buttons, choice cards, hotkeys and ground clicks, see [UI & input](https://war3ai.com/en/docs/ui-input/).

**Position** (give each element one of these):

- `screen=(x, y)`: screen pixels; negative values count back from the right / bottom edge; `center=True` aligns by the center;
- `frac=(0.5, 0.1)`: fraction of the screen;
- `world=(x, y)`: world coordinates;
- `unit=u`: follows a unit. Text and progress bars placed in the world or on a unit are anchored by the midpoint of their bottom edge; `lift` raises them.

By default, elements in the world and on units stay clear of the HUD at the bottom and the day/night clock at the top (`over_ui=True` draws over them). **Colors** can be written as `(r, g, b)`, `(r, g, b, a)`, `"#RRGGBB"` or `"#RRGGBBAA"`.

| Method | What it draws | Common parameters |
|---|---|---|
| `text(key, text, ...)` | Text box; use `\n` for multiple lines | `color`, `bg` background (transparent if omitted), `border`, `size`, `bold`, `shadow`, `width` (wraps at this width), `radius` for rounded corners |
| `panel(key, title, [lines...], ...)` | Panel (dark translucent background, gold border) | Same as `text` |
| `bar(key, 0..1, ...)` | Progress bar: health, cooldown, cast bar | `width`, `height`, `color`, `bg`, `border`, `text` |
| `image(key, path, ...)` | Local image (png / jpg / bmp / gif) | `width`, `height` (original size if omitted) |
| `circle(key, unit_or_point, radius, ...)` | Circle on the ground, hugging the terrain | `color` line color, `fill` fill (with alpha), `width` line width |
| `path(key, [points...], ...)` | Polyline on the ground | `color`, `width`, `arrow` for an arrowhead at the end; points can be coordinates or units |

## HTTP (any language)

Farsight backend (listens only on the local machine):

```http
POST /api/instances/20/canvas
{"set": [
   {"key": "banner", "kind": "text", "text": "Canvas from HTTP", "frac": [0.5, 0.12], "center": true,
    "color": "#FFDC50", "bg": [0, 0, 0, 180]},
   {"key": "hp", "kind": "bar", "value": 0.8, "unit": 596125988, "lift": 260, "text": "80%"},
   {"key": "zone", "kind": "circle", "center": 596125988, "radius": 600, "color": [255, 200, 0], "width": 4},
   {"key": "route", "kind": "path", "points": [[-4587, -9092], [-5387, -8792]], "color": "#50C8FF"}
 ],
 "remove": ["old"], "clear": false}

GET  /api/instances/20/canvas        which elements are drawn right now + how many frames have been drawn
```

`kind` is the Python method name, and the parameter names are the same too; for a unit, pass its `addr` from the snapshot.

## Writing shared memory directly

You can also skip Python and Farsight entirely: first send the semantic command `canvas_enable` once (W3P opcode 73), and the runtime creates the shared memory block `Local\War3Canvas_<pid>`: a 64-byte header + 256 entries × 112 bytes + a 64 KB text / point pool. Write it with a seqlock (sequence number goes odd → write the entries and the pool → sequence number goes even). The runtime reads it once per frame; if it reads a half-written update it keeps the previous frame, and it writes back the number of frames drawn, the element count and the fault count. The Python reference implementation is `sdk/python/w3canvas.py`, and the struct definitions are in the protocol header file; see [W3P protocol](https://war3ai.com/en/docs/protocol/).

## Several programs drawing at once

Mods, Farsight, MCP and the gateway may all draw into the same game at the same time, and there's only one canvas. The rule is: **each program touches only its own elements**.

- Before writing, take a named lock, read the existing elements, keep everyone else's, swap in your own, then write it back;
- Each element records who drew it (process ID + an in-process sequence number). When the program that drew it exits, the element is cleaned up the next time anyone writes, and its buttons stop intercepting clicks;
- Element IDs are allocated from a shared counter, so they never collide.

The Python SDK already does this, and `clear()` only clears its own elements. If you write the shared memory directly, follow the same rules, or you'll wipe out other programs' elements. For layout details, see [W3P protocol](https://war3ai.com/en/docs/protocol/).

## Measurements and caveats

- Measured on 2026-09-25 (1920×1080, 2× speed): 9 elements take 0.27–0.34 ms per frame at about 63 frames per second, with 0 faults; writing 9 entries takes 6 ms; while the hero moves, the circles, text and health bars that follow units keep up. Textures are redrawn only when the content changes; moving an element doesn't trigger a redraw.
- It's drawn after the game UI and before the mouse cursor: it covers the game's own health bars, units and UI, and the cursor is drawn over it. It stays clear of the HUD at the bottom and the day/night clock at the top, but **does not avoid the map's own panels** (the leaderboard and countdown in the top-right corner) — don't put your own panels in the top-right.
- Outside a game (main menu, score screen), elements placed at world coordinates or on units aren't drawn; elements at screen positions are drawn as usual.
- A ground circle is made by projecting each of 64 points on its circumference onto the ground, so on uneven terrain its shape rises and dips with the ground — that's correct: it's drawn on the real ground.
- The first time it's enabled, it installs the hook and warms up the fonts, which takes about 1 second; during that time text elements aren't drawn yet, but circles and lines are.
- If a single exception occurs while drawing, nothing more is drawn for the rest of the session (the same protection as speech bubbles), and `faults` in `stats()` becomes 1.
- Text, image paths and points share 64 KB in total, with at most 256 elements; image paths must be local paths the game process can read.

The [AI companion](https://war3ai.com/en/docs/companion/)'s status panel is drawn with the canvas: health bar, what it's doing, mood, and kill and heal counts.
