# Canvas

> A drawing surface, and the verbs that paint on it. Put a `Canvas` in a render block, call `Draw.*` from an `on frame` body, and the picture is redrawn every frame — a game, a visualisation, a custom chart, anything the box-shaped render vocabulary cannot express. The drawing code is ordinary Osy#: ordinary loops, ordinary helper methods, ordinary state.

<!-- id: ui-canvas · area: ui · stability: preview · html: https://osysharp.com/reference/ui/canvas/ -->

## Summary        {#summary}
Everything else the render vocabulary draws is a **box**. That reaches further than it sounds — a Tetris well, a
bar chart and even a textured raycaster are all boxes — but a box cannot vary **within itself**, so lighting that
falls off down a wall, a sprite clipped by the thing in front of it, or a textured floor have no spelling.

`Canvas` is the surface for those. It is **immediate mode**: there is no scene, no retained objects and nothing to
keep in sync. Each frame you clear it and draw what should be there now.

The drawing happens in **Osy#**, in your `on frame` body — not in a callback handed to a JavaScript library. That is
the point of the design: a game's inner loop stays in the same language as the rest of the app.

## Signature      {#signature}
```osy syntax
Canvas(w: 640, h: 400)              // the surface. `w`/`h` are the drawing BUFFER, in canvas pixels

Draw.Clear(color)                   // fill the whole canvas
Draw.Rect(x, y, w, h, color)        // a filled rectangle, from its top-left corner
Draw.Line(x1, y1, x2, y2, color)    // a 1px stroked segment
Draw.Line(x1, y1, x2, y2, color, width)
Draw.Circle(x, y, radius, color)    // a filled disc, from its CENTRE
Draw.Text(text, x, y, color, size)  // a string, from its TOP-left; `size` in canvas pixels
Draw.Image(wall, dx, dy, dw, dh)    // a TEXTURE the app ships, into a destination rectangle
Draw.Image(wall, sx, sy, sw, sh, dx, dy, dw, dh)  // a SOURCE rectangle of it, into a destination one
Draw.Image(url, …)                  // the same two forms over a runtime url — see [textures](https://osysharp.com/reference/ui/textures/)
Draw.Pixels(buffer, w, h, dx, dy)   // a w-by-h buffer of packed 0xRRGGBB colours, one canvas pixel each
Draw.Pixels(buffer, w, h, dx, dy, dw, dh)   // …STRETCHED into a destination rectangle

Draw.Push()                         // save the current transform
Draw.Pop()                          // put back the one the matching Push saved
Draw.Translate(x, y)                // move the origin
Draw.Rotate(radians)                // turn everything drawn after it, about the origin
Draw.Scale(sx, sy)                  // resize it; a NEGATIVE factor mirrors
Draw.Reset()                        // back to plain canvas coordinates, stack and all

Gradient g = Draw.Gradient(x0, y0, x1, y1, colorA, colorB)   // a LINEAR fill, running between two points
Gradient r = Draw.Radial(x, y, radius, colorA, colorB)      // a RADIAL fill, running out from a centre
Draw.Rect(0, 0, 640, 420, g)        // a gradient goes wherever a colour goes

var sky = Draw.Surface(640, 420)    // an OFFSCREEN surface — hold it in a field, build it once
Draw.Into(sky)                      // …the verbs above now paint on it
Draw.Screen()                       // …and back to the component's Canvas
Draw.Image(sky, dx, dy, dw, dh)     // blit it, with the same two forms a texture takes
```

## Description    {#description}

### What goes in a frame body — clear, then draw   {#a-frame}
A canvas keeps what you drew last time, so a frame normally starts by clearing it and then draws everything that
should be visible now. State lives in the component, exactly as it does without a canvas.

```osy title="a disc that bounces" test app=arcade-paint
[Page("/bounce")]
[AllowAnonymous]
component Bounce() {
  double x = 60;
  double y = 60;
  double vx = 210;
  double vy = 160;

  on frame (double dt) {
    x = x + vx * dt;
    y = y + vy * dt;
    if (x < 24) { x = 24; vx = 0 - vx; }
    if (x > 296) { x = 296; vx = 0 - vx; }
    if (y < 24) { y = 24; vy = 0 - vy; }
    if (y > 176) { y = 176; vy = 0 - vy; }

    Draw.Clear("#0B0616");
    Draw.Circle(x, y, 22, "#22E5FF");
  }

  render { Canvas(w: 320, h: 200); }
}
```

Every position is scaled by `dt`, so the disc crosses the surface in the same wall-clock time whatever frame rate
the display runs at. See [on mount / on unmount](https://osysharp.com/reference/ui/lifecycle/) — that is a property of `on frame`, not of the canvas.

### Coordinates are canvas pixels    {#coordinates}
`x` grows right, `y` grows **down**, and the origin is the top-left corner — the convention every 2D drawing API
uses. The unit is the buffer declared by `Canvas(w:, h:)`, not CSS pixels and not a normalised space, so a loop
index is an `x` directly:

```osy title="one draw call per screen column" test app=arcade-paint
[Page("/columns")]
[AllowAnonymous]
component Columns() {
  double t = 0;

  on frame (double dt) {
    t = t + dt;
    Draw.Clear("#0B0616");
    for (var i = 0; i < 160; i = i + 1) {
      var h = 20 + i;
      Draw.Rect(i * 2, 200 - h, 2, h, i % 2 == 0 ? "#2A1B3D" : "#3A2551");
    }
  }

  render { Canvas(w: 320, h: 200); }
}
```

### Rotating, moving and scaling what you draw   {#transforms}
Every shape above is drawn **axis-aligned**: `Draw.Rect` takes a rectangle and `Draw.Image` takes a destination
rectangle, so neither can be turned. The transform verbs are how anything points somewhere.

The idea is that you draw a thing **once, in its own coordinates** — at the origin, facing along +x — and then say
where it is and which way it faces:

```osy syntax
Draw.Push();                 // remember where we were
Draw.Translate(ship.X, ship.Y);
Draw.Rotate(ship.Heading);   // RADIANS, like Math.Cos / Math.Sin
Draw.Line(14, 0, -10, -9, "#22E5FF", 2);     // the ship, at the origin, pointing along +x
Draw.Line(14, 0, -10, 9, "#22E5FF", 2);
Draw.Line(-10, -9, -10, 9, "#22E5FF", 2);
Draw.Pop();                  // and back
```

`Draw.Scale(sx, sy)` resizes the same way, which lets one unit-sized shape serve every size of a thing; a **negative**
factor mirrors, so `Draw.Scale(-1, 1)` is how a sprite faces left.

The verbs compose: a translate inside a translate is relative to the outer one, which is what lets a turret sit on a
tank and turn independently, with each drawn in its own frame.

⚠️ **Angles are radians.** Same as `Draw.Mesh`'s rotations, and same as `Math.Sin` / `Math.Cos` — which is what you
are usually holding anyway. From degrees: `deg * Math.Pi / 180.0`.

#### Every frame starts clean   {#transform-reset}
The transform is **reset before each frame body runs**. That is worth knowing for two reasons:

- A `Draw.Push()` you forget to `Draw.Pop()` is wrong for **that frame only**. Without the reset the world would
  drift a little further every frame, and the symptom — a slow rotation over half a minute — looks like a bug in your
  own maths rather than a missing line. (You are also told, once, when a frame body leaves one unpopped.)
- A camera can be a single line. `Draw.Translate(-camX, -camY)` at the top of the body, with no push and no pop, is
  the whole of it — because the next frame does not inherit it.

`Draw.Reset()` does the same thing **mid-frame**: it drops every transform and every push, which is how a HUD gets
nailed to the screen over a world that is moving.

#### Two verbs that do not follow the transform   {#transform-exceptions}
- **`Draw.Clear`** always clears the whole canvas, whatever the transform is. Clearing "the visible rectangle, moved"
  is never what anyone means, and the leftovers would read as smearing.
- **`Draw.Pixels`**, in its unscaled form, lands in plain canvas coordinates — the browser's pixel blit ignores
  transforms by specification. The **stretched** form (`dw`, `dh`) goes through the image path and does follow them.
  You are told once if you blit under a transform.

```osy title="a ship that points where it is flying" sample=arcade/model/pages/rocks.osy#PaintShip
  void PaintShip() {
    Draw.Push();
    Draw.Translate(sx, sy);
    Draw.Rotate(heading);
    Draw.Line(14, 0, -10, -9, "#22E5FF", 2);
    Draw.Line(14, 0, -10, 9, "#22E5FF", 2);
    Draw.Line(-10, -9, -10, 9, "#22E5FF", 2);
    // The exhaust flickers, and it is drawn in the SAME frame as the hull — so it stays glued to the tail however
    // the ship is turning, with no second angle to keep in step.
    if (thrusting) {
      var flame = 8.0 + Math.Sin(spin * 40.0) * 4.0;
      Draw.Line(-10, -4, -10 - flame, 0, "#FF2D95", 2);
      Draw.Line(-10, 4, -10 - flame, 0, "#FF2D95", 2);
    }
    Draw.Pop();
  }
```

### Gradients are values, not verbs   {#gradients}
`Draw.Gradient` and `Draw.Radial` do not draw anything. They answer a **`Gradient`** — a fill you then pass
wherever a colour string goes:

```osy syntax
Gradient sky;

on frame (double dt) {
  if (sky == null) {
    sky = Draw.Gradient(0, 0, 0, 430, "#4d8fd6", "#a9d2f2", "#ffeccd");
  }
  Draw.Rect(0, 0, 1280, 430, sky);
}
```

**That is why there is no `Draw.GradientRect`.** A gradient is accepted by `Draw.Clear`, `Draw.Rect`,
`Draw.Circle`, `Draw.Line` and `Draw.Text` alike, and by anything added later — because the fill is a value rather
than a second version of every verb.

**Two or more colours, spaced evenly** along the run. `Draw.Gradient` runs between the two points you give, so
`(0, 0, 0, 430)` is vertical and `(0, 0, 640, 0)` is horizontal; `Draw.Radial` runs from a centre outward, which is
what a sun, a glow or a vignette wants. A colour with alpha (`"rgba(255,238,190,0)"`) is how a glow fades into what
is behind it.

**Build it once.** A gradient is fixed in canvas pixels, so hold it in a `Gradient` field — as above, or in
`on mount` — rather than rebuilding it every frame.

### Painting something once — offscreen surfaces   {#surfaces}
A canvas keeps nothing between frames, so everything on screen is redrawn every frame — including the parts that
never change. A starfield of 160 stars is 160 draw commands, which is 9,600 a second for a picture that is identical
each time.

A **surface** is an offscreen canvas you paint once and then blit:

```osy syntax
var sky = Draw.Surface(640, 420);     // a field: built once, used every frame
bool painted = false;

on frame (double dt) {
  if (!painted) {
    Draw.Into(sky);                   // every verb from here paints on `sky`…
    Draw.Clear("#0B0616");            // …including Clear, which clears the SURFACE
    PaintStars();
    Draw.Screen();                    // …and back to the canvas
    painted = true;
  }
  Draw.Image(sky, 0, 0, 640, 420);    // one command instead of a hundred and sixty
  …the moving things…
}
```

`Draw.Image` takes a surface exactly as it takes a texture, in both the whole-image and source-rectangle forms — so
switching a background from shipped art to something you rendered is a change of value, not of call site.

The other thing surfaces buy is a **pre-rendered sprite**: rotating a shape costs the same every frame, and rotating
it once into a surface costs one blit thereafter.

⚠️ **Build a surface once.** It is a real canvas — allocating one per frame is the same mistake as loading an image
per frame. A field, or the first frame, is where one belongs. The largest edge is 4096 pixels.

#### Every frame starts on the screen   {#surface-reset}
As with the transform, the target is **reset before each frame body runs**. A `Draw.Into` you forget to close would
otherwise send every following frame into a buffer, and the screen would simply stop changing — no error, no blank
canvas, a frozen picture that reads as a hung game. Instead it costs you the rest of *one* frame, and you are told
once.

#### What a surface cannot do   {#surface-limits}
The **3D verbs** (`Draw.Camera` / `Light` / `Fog` / `Mesh`) always render on the component's own canvas and refuse to
paint into a surface — call `Draw.Screen()` first. Each surface keeps its **own** transform and its own `Draw.Push`
stack, so a `Draw.Pop` inside one can never restore the screen's; entering a surface gives you a clean coordinate
system.

```osy title="a starfield painted once and blitted every frame" sample=arcade/model/pages/rocks.osy#Starfield
    if (!starsPainted) {
      Draw.Into(stars);
      Draw.Clear("#0B0616");
      // ⚠ A JITTERED LATTICE, NOT `Roll()` FOR THE POSITIONS. `Roll()` is the small LCG `/blockfall` uses
      //    (`* 75 % 65537`, chosen because Osy# integer arithmetic is checked and a textbook multiplier overflows),
      //    and its period is short enough that 160 draws landed on 17 distinct pixels — measured. Coprime strides
      //    give 160 distinct positions by construction; `Roll()` still picks the brightness, where a short cycle is
      //    invisible.
      for (var i = 0; i < 160; i = i + 1) {
        var sxp = (i * 271) % 640;
        var syp = (i * 157) % 420;
        var mag = Roll();
        Draw.Rect(sxp, syp, mag > 0.86 ? 2 : 1, mag > 0.86 ? 2 : 1,
                  mag > 0.86 ? "#FFFFFF" : (mag > 0.5 ? "#8B79A8" : "#3A2A55"));
      }
      Draw.Screen();
      starsPainted = true;
    }

    Draw.Image(stars, 0, 0, W, H);
```

### `w:` and `h:` are the resolution, and they are required    {#size}
They set the drawing **buffer** — how many pixels there are to draw on — as well as the element's CSS size, so one
canvas pixel is one CSS pixel by default. To show a fixed resolution larger or smaller, style the element:
`Canvas(w: 320, h: 200, maxW: "100%")`.

Both are **required**, and both must be written as **whole-number literals**. A canvas with no declared size
silently gets 300×150 for its buffer while the element stretches to whatever CSS says, which renders everything
blurred and in the wrong place with nothing to indicate why — so it is refused instead. And because assigning a
canvas's size **clears it**, a size that varied with state would blank the surface at a moment nothing in the code
names; a fixed resolution scaled by CSS is what you want anyway.

### Text draws in the canvas's own font    {#text}
`Draw.Text` uses the font-family the `Canvas` element itself has, so `Canvas(w: 640, h: 400, fontFamily: Font.Display)`
makes drawn text match the `Text(…)` beside it. `y` is the text's **top**, like every other verb's `y` — not its
baseline.

### Images load on first use    {#images}
`Draw.Image` starts loading a url the first time it sees one and draws nothing until it has arrived, so the first
frame that names a new image skips it and every later frame has it. Cache-warm it by drawing it off-screen if the
first frame matters.

The nine-argument form takes a **source** rectangle and a **destination** rectangle, which is how a sprite sheet is
cut up — and how a textured wall is drawn, by stretching a one-pixel-wide slice of a texture to the wall's height.

### Drawing needs a canvas in the same component    {#same-component}
`Draw.*` paints the `Canvas` declared by the component whose body is running. A canvas inside a nested component
belongs to that component, and drawing on it from the outside is refused with a message saying so — it would
otherwise appear to work until the child re-rendered.

### Can a screen reader read a canvas? No   {#accessibility}
Nothing drawn on a canvas is text, an element, or reachable by a screen reader or a keyboard. Use it for what is
genuinely a picture, and put anything a reader must be able to read or press in ordinary atoms around it — a score,
a legend, the controls. A canvas that is decorative needs no description; one that carries information needs the
same information available another way.

### How much can a frame draw?    {#budget}
Re-measured in a real browser, 2026-09-05: roughly **75,000 draw commands** per frame while holding 60fps (90,000
drops to 30), and **more than 300,000** arithmetic steps — the top of the rig, so that ceiling was not found. That is
far past a per-column raycaster and well into per-tile and per-sprite work. If a frame body gets slower than the
display, the frame rate drops rather than frames queueing up.

⚠️ **The draw budget is the browser's, not the language's.** The same measurement with the JS compilation switched
off is identical — 75,000 either way — because what a `Draw.*` call costs is the canvas, and Osy#'s own overhead
around it is already small beside that. The arithmetic budget is where compilation shows, and there the ceiling is
past anything a 2D game needs.

⚑ These are one machine on one day, as the previous figures were; treat them as an order of magnitude rather than a
specification, and measure your own frame if you are close to the edge.

## See also {#see-also}
- [Canvas 3D](https://osysharp.com/reference/ui/canvas-3d/) — the lit, shadowed 3D scene the same canvas can carry: `Draw.Camera`, `Draw.Light`, `Draw.Mesh`
- [on mount / on unmount](https://osysharp.com/reference/ui/lifecycle/) — `on frame (double dt)`, the clock that drives a canvas
- [component](https://osysharp.com/reference/ui/component/) — component state, which is where a drawing's state lives
- [animation — looping motion with no destination state](https://osysharp.com/reference/ui/animation/) — declarative motion for ordinary elements, which needs no canvas
