"Drawing does not persist across frames; redraw it every frame" was the advice, and it is not sufficient on its own. The host runs a fixed number of source lines and then presents, and presenting discards the drawing buffer -- so a run of drawing verbs longer than one budget is **torn**, not merely transient, and an `SSHAPE` at the end of it captures only what was issued since the present, over whatever the frame before left behind. Measured against the standalone frontend's 256: after synchronising to a jiffy edge, 220 lines of drawing survive a capture and 250 do not. There is no fix available that does not change what a host owns -- the budget is the host's and so is the present -- so this is documentation, in the three places it belongs. Chapter 13 beside the note it qualifies, with the numbers. Chapter 6 where `SSHAPE` is introduced, which is where a program meets it. Chapter 14 from the step loop's side, naming `AKBASIC_FRONTEND_STEPS_PER_FRAME` and explaining the one thing a script can do about it: `settime()` is called once a frame, so `TI#` changes on the first step of a batch and nowhere else, and spinning until it changes is the only frame synchronisation this dialect has. **Untested, deliberately.** Reproducing it needs the real frontend, a clock and a timing window, and a test that reproduced it would be reproducing a race. Recorded in TODO.md rather than left looking like an oversight. TODO.md section 9 item 5, struck as a documentation outcome. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
255 lines
8.3 KiB
Markdown
255 lines
8.3 KiB
Markdown
# 6. Graphics
|
|
|
|
Everything in this chapter needs the SDL build and a graphics device. Without one each
|
|
verb refuses by name:
|
|
|
|
```basic requires=noakgl
|
|
10 DRAW 1, 0, 0 TO 100, 100
|
|
```
|
|
|
|
```output
|
|
? 10 : RUNTIME ERROR DRAW needs a graphics device and this runtime has none
|
|
|
|
```
|
|
|
|
## The coordinate space
|
|
|
|
**A drawing coordinate is a pixel of the host's window**, with (0, 0) at the top left.
|
|
On the standalone interpreter's 800 by 600 window, `DRAW 1, 799, 599` lands on the
|
|
bottom-right pixel and everything in between is reachable. A game embedding the
|
|
interpreter gets whatever size its own renderer is.
|
|
|
|
`RGR` is how a program finds out:
|
|
|
|
```basic requires=akgl
|
|
10 PRINT "THE SCREEN IS"
|
|
20 PRINT RGR(1)
|
|
30 PRINT "BY"
|
|
40 PRINT RGR(2)
|
|
50 DRAW 1, 0, 0 TO RGR(1) - 1, RGR(2) - 1
|
|
```
|
|
|
|
Subtracting one is not a wart, it is the last pixel: a window `RGR(1)` wide has
|
|
columns 0 through `RGR(1) - 1`.
|
|
|
|
**A C128 listing assumes 320 by 200 and will draw in the top-left corner.** Give it
|
|
the whole window by naming the space it was written for:
|
|
|
|
```basic requires=akgl
|
|
10 SCALE 1, 319, 199
|
|
20 BOX 1, 0, 0, 319, 199
|
|
```
|
|
|
|
That box is now the border of the window whatever size the window is. When no device
|
|
answers the size question at all, 320 by 200 is what the interpreter assumes — the
|
|
space a C128 listing was written for is the right thing to fall back to.
|
|
|
|
## Colour
|
|
|
|
`COLOR` binds a *source* to a palette index, and the drawing verbs name the source
|
|
rather than the colour:
|
|
|
|
```basic requires=akgl
|
|
10 COLOR 1, 3
|
|
20 DRAW 1, 10, 20
|
|
```
|
|
|
|
Sources are numbered 0 to 6; palette indices are 1 to 16, as on a C128. That
|
|
indirection is BASIC 7.0's, and it is why every drawing verb's first argument is a
|
|
small number that is not a colour.
|
|
|
|
## The verbs
|
|
|
|
### GRAPHIC
|
|
|
|
`GRAPHIC mode` chooses a screen mode; `GRAPHIC CLR` clears it. Mode 0 is text and
|
|
refuses to draw.
|
|
|
|
Every picture in this chapter is generated by running the listing above it; see
|
|
`MAINTENANCE.md` if you are editing one.
|
|
|
|
### DRAW
|
|
|
|
```basic requires=akgl screenshot=draw
|
|
10 COLOR 1, 8
|
|
20 DRAW 1, 20, 180 TO 90, 40 TO 160, 150 TO 230, 20 TO 300, 120
|
|
30 COLOR 2, 6
|
|
40 DRAW 2, 20, 190 TO 300, 190
|
|
50 LOCATE 160, 100
|
|
60 COLOR 3, 3
|
|
70 DRAW 3
|
|
```
|
|
|
|

|
|
|
|
One coordinate pair plots a point. Two or more, separated by `TO`, draw a polyline. A
|
|
bare `DRAW 3` — the last line above, and the single red pixel in the middle of the
|
|
picture — plots wherever `LOCATE` left the pixel cursor.
|
|
|
|
### BOX
|
|
|
|
```basic requires=akgl screenshot=box
|
|
10 COLOR 1, 8
|
|
20 BOX 1, 20, 30, 130, 140
|
|
30 COLOR 2, 6
|
|
40 BOX 2, 180, 30, 290, 140, 30
|
|
50 COLOR 3, 3
|
|
60 LOCATE 300, 190
|
|
70 BOX 3, 20, 160
|
|
```
|
|
|
|

|
|
|
|
Corners, and an optional rotation angle — the green box is the same box turned 30
|
|
degrees about its own centre. Two coordinates instead of four take the other corner
|
|
from the pixel cursor, which is the red box.
|
|
|
|
**`BOX` always outlines; it cannot fill.** BASIC 7.0 selects fill with a seventh
|
|
argument and that is not implemented here — see Chapter 13. `PAINT` is the fill you
|
|
have.
|
|
|
|
### CIRCLE
|
|
|
|
```basic requires=akgl screenshot=circle
|
|
10 COLOR 1, 8
|
|
20 CIRCLE 1, 80, 70, 60, 60
|
|
30 COLOR 2, 6
|
|
40 CIRCLE 2, 230, 70, 75, 45
|
|
50 COLOR 3, 3
|
|
60 CIRCLE 3, 160, 140, 130, 50, 90, 270
|
|
```
|
|
|
|

|
|
|
|
Source, centre, then the two radii — equal radii give a circle and unequal ones an
|
|
ellipse. Two further arguments are a start and an end angle, which is what makes the
|
|
red arc: 90 to 270 is the bottom half, because angles here are degrees clockwise from
|
|
straight up, the same convention `MOVSPR` uses. Beyond those come a rotation and the
|
|
degree increment, and a large increment is what turns a circle into a polygon.
|
|
|
|
### PAINT
|
|
|
|
```basic requires=akgl screenshot=paint
|
|
10 COLOR 1, 8
|
|
20 CIRCLE 1, 100, 100, 70, 70
|
|
30 BOX 1, 180, 50, 290, 150
|
|
40 COLOR 2, 6
|
|
50 PAINT 2, 100, 100
|
|
60 COLOR 3, 3
|
|
70 PAINT 3, 230, 100
|
|
```
|
|
|
|

|
|
|
|
Flood-fills the region containing a point, stopping at whatever is already drawn — so
|
|
the outline you fill inside can come from any verb. If the region is too large for the
|
|
fill's own working space it stops and reports rather than leaving a half-painted screen
|
|
with no explanation.
|
|
|
|
### LOCATE
|
|
|
|
Moves the pixel cursor, which is where a bare `DRAW` plots and where a `BOX` with two
|
|
coordinates finishes.
|
|
|
|
### SCALE
|
|
|
|
```basic requires=akgl screenshot=scale size=640x400
|
|
10 COLOR 1, 3
|
|
20 BOX 1, 0, 0, 319, 199
|
|
30 SCALE 1, 319, 199
|
|
40 COLOR 2, 6
|
|
50 BOX 2, 0, 0, 319, 199
|
|
60 DRAW 2, 0, 0 TO 319, 199
|
|
```
|
|
|
|

|
|
|
|
That picture is 640 by 400, and both boxes name the same four numbers. The red one is
|
|
drawn with `SCALE` off, so its coordinates are pixels and it covers exactly the
|
|
top-left 320 by 200 of the window — which is what a C128 listing does here. The green
|
|
one is drawn after `SCALE 1, 319, 199` and fills the window, and the diagonal confirms
|
|
that 319, 199 reaches the last pixel rather than stopping one short of it.
|
|
|
|
Turns on user coordinates and gives their maxima. With it on, your coordinates are
|
|
mapped onto the drawing surface: 0 is the first pixel and the maximum you gave is the
|
|
*last* one, so `DRAW 1, 1023, 1023` above reaches the bottom-right corner rather than
|
|
missing it by a pixel. `SCALE 1` on its own uses 7.0's 1023 by 1023. `SCALE 0` turns
|
|
it off and coordinates go back to being window pixels.
|
|
|
|
### RGR
|
|
|
|
`RGR(0)` is the current `GRAPHIC` mode. `RGR(1)` and `RGR(2)` are the drawing
|
|
surface's width and height in pixels — those two are ours rather than 7.0's, and they
|
|
are what a program needs to use a window whose size it did not choose. They
|
|
refuse when there is no graphics device, unlike `RGR(0)`, which is a mode this
|
|
interpreter recorded rather than a screen it has to go and measure.
|
|
|
|
**`RGR(3)` and `RGR(4)` are a character cell's width and height**, also ours. They come
|
|
from the *text* device rather than the graphics one — a character grid belongs to the
|
|
sink — so they refuse by naming that instead. Between them and
|
|
[`RWINDOW`](12-function-reference.md), which gives the current text window in columns
|
|
and rows, a program can place a character and a sprite at the same spot without
|
|
hardcoding a number measured against whatever font the host loaded:
|
|
|
|
```basic requires=akgl
|
|
10 CW# = RGR(3)
|
|
20 CH# = RGR(4)
|
|
30 COL# = 12
|
|
40 ROW# = 3
|
|
50 CHAR 1, COL#, ROW#, "X"
|
|
60 MOVSPR 1, COL# * CW#, ROW# * CH#
|
|
```
|
|
|
|
`RWINDOW` follows a `WINDOW` call, because it reports the window. `RGR(3)` and `RGR(4)`
|
|
do not, because windowing does not change how big a character is.
|
|
|
|
### WIDTH
|
|
|
|
`WIDTH 1` or `WIDTH 2` sets how thick a drawn line is. A thick line is drawn as
|
|
parallel passes; see Chapter 13.
|
|
|
|
## Saving and stamping regions
|
|
|
|
`SSHAPE` copies a rectangle off the screen and `GSHAPE` stamps it back:
|
|
|
|
```basic requires=akgl screenshot=shapes
|
|
10 COLOR 1, 8
|
|
20 CIRCLE 1, 40, 40, 30, 30
|
|
30 COLOR 2, 6
|
|
40 PAINT 2, 40, 40
|
|
50 SSHAPE A$, 8, 8, 72, 72
|
|
60 GSHAPE A$, 120, 20
|
|
70 GSHAPE A$, 200, 60
|
|
80 GSHAPE A$, 120, 120
|
|
```
|
|
|
|

|
|
|
|
One disc drawn, captured, and stamped three times.
|
|
|
|
A string array works: `SSHAPE SH$(2), ...` writes into element 2 and `GSHAPE SH$(2)`
|
|
stamps what element 2 names, so a program can keep a set of shapes in one array.
|
|
|
|
**`A$` holds a handle, not the pixels.** On a C128 the string holds the bitmap, so a
|
|
program could save it to disk or take its `LEN`. Here a string is a fixed 255 bytes and
|
|
the region is a device surface, so what goes in the string is a reference to it —
|
|
`SHAPE:0`. You can pass it to `GSHAPE` and to `SPRSAV`, which is everything BASIC ever
|
|
does with one, but you cannot store it or measure it.
|
|
|
|
## What is not here
|
|
|
|
`FILTER` parses and then refuses: there is no filter stage to configure. See Chapter 7.
|
|
|
|
The graphics verbs draw straight to the renderer rather than into a display list, so
|
|
anything drawn is overwritten by the text layer on the next frame. A program that wants
|
|
its drawing to persist has to redraw it. This is recorded as a defect rather than a
|
|
design; see Chapter 13.
|
|
|
|
**A redraw also has to fit inside one batch.** The host runs a fixed number of source
|
|
lines and then presents, and presenting throws the drawing buffer away — so a run of
|
|
drawing verbs longer than one batch is torn rather than merely transient, and an
|
|
`SSHAPE` at the end of it captures only the part issued since the present. Watching
|
|
`TI#` change is how a program finds the boundary; Chapter 13 has the measured numbers and
|
|
[Chapter 18](18-tutorial-breakout-artwork.md#step-4-find-the-frame-boundary) has a
|
|
routine that uses them.
|