ROCoding #1: Rectangles

Friday, December 12, 2025

Coming back to RISC OS has been interesting. I’ve had to re-learn a number of things, like using the WIMP and, in particular, graphics programming. I hope this article will be the first of a series explaining my learning curve, and hopefully providing some useful programs. It’s fairly basic — in all senses! — but does assume some familiarity with RISC OS and BBC BASIC.

We’ll start with some things you can do when drawing rectangles. While writing the Solar application I wanted to provide some better backgrounds for the graphs. A plain background is easy, of course: just use RECTANGLE FILL with some appropriate colour, like this.

rect0.webp
GCOL 0,&ff,&dd,&ff
RECTANGLE FILL 0,0,256

…which draws a 256×256 square at the graphics origin. More generally, RECTANGLE accepts both width and height, which can be negative.

First up, we’ll generate a blend between two colours, from the bottom to the top of the rectangle. We’re assuming a full-colour display here, which is necessary for displaying blends with any fidelity. The Solar application graphs are built up by redirecting all graphics output to a sprite, then letting the Wimp handle displaying them on screen; this takes care of any mismatch between the screen and graph colour depths.

It’s useful when defining a routine to make it as general as possible. For graphics operations, that means finding out how OS coordinates map to pixels. We do this by reading the so-called eigen values like this, in PROCinit (which will be run before any of the demos here):

DEF PROCinit
SYS ”OS_ReadModeVariable”,-1,4 TO ,,xeig%
SYS ”OS_ReadModeVariable”,-1,5 TO ,,yeig%
ENDPROC

By using BASIC’s shift operations, we can convert OS coordinates to pixels like this:

xpix% = xos%>>xeig%: ypix% = yos%>>yeig%

Note that we use an arithmetic shift (>> rather than >>>) to preserve any sign.

So, first we work out how to blend between two colours. The height of the rectangle (in pixels) gives us the number of horizontal lines we need to draw, and for each line we need to modify each of the red, green and blue channel values. To get the modifications for each step, we’ll define PROCblend_steps like this:

DEF PROCblend_steps(r0,g0,b0, r1,g1,b1, steps, RETURN rs,RETURN gs,RETURN bs)
rs=(r1-r0)/steps: gs=(g1-g0)/steps: bs=(b1-b0)/steps
ENDPROC

The first three input variables r0, g0 and b0 give the starting colour, and the next three the end colour. The steps variable specifies the number of lines, and we RETURN three values, the stepping quantities for each channel. Note that we need to use real variables for everything; it will quickly go wrong if you use integers. And even using reals may accumulate rounding errors, so the last colour used might not perfectly match r1, g1 and b1. For these purposes though it’ll be close enough. This gives us a blend between black and white:

rect1.webp
PROCinit
ysp%=128:                   REM The height of the rectangle in pixels
s%=1<<yeig%:                REM The height of a single line in OS units
x%=0: y%=0:                 REM The rectangle’s corner
r=0: g=0: b=0:              REM Black, blending to white…
PROCblend_steps(r,g,b, 255,255,255, ysp%, rs,gs,bs)
FOR n% = 1 TO ysp%:         REM For each line…
 GCOL r,g,b:                REM …set the colour…
 LINE x%,y%,256,y%:         REM …draw the line…
 y%+=s%:                    REM …step to next line…
 r+=rs: g+=gs: b+=bs:       REM …and step each colour channel
NEXT

Incidentally, all these images are saved in WebP format. This one started out as a 64k sprite, which as a WebP image takes up … 60 bytes.

For a nice sky-type blend, which I’ve used for some Solar graphs, try this:

rect2.webp
…
r=234: g=234: b=255
PROCblend_steps(r,g,b, 188,188,255, ysp%, rs,gs,bs)
…

Modifying this for a horizontal blend, along the X axis, is easy. This does green to white:

rect3.webp
PROCinit
xsp%=128:                   REM The width of the rectangle
s%=1<<xeig%:                REM The width of a single line in OS units
x%=0: y%=0:                 REM The rectangle’s corner
r=0: g=255: b=0:            REM Green, blending to white…
PROCblend_steps(r,g,b, 255,255,255, xsp%, rs,gs,bs)
FOR n% = 1 TO xsp%:         REM For each line…
 GCOL r,g,b:                REM …set the colour…
 LINE x%,y%,x%,256:         REM …draw the line…
 x%+=s%:                    REM …step to next line…
 r+=rs: g+=gs: b+=bs:       REM …and step each colour channel
NEXT

Generalising

To generalise this and construct a useful library procedure, we’ll start by defining colours as palette entries, rather than separate red, green and blue components. This is the form usually used by the ColourTrans module, and encodes a colour into a single word &bbggrr00, where b, g and r are hex digits (the 00 is often used for transparency). So &ffffff00 is white, and &00400000 is a dark green. Colours are a lot easier to pass around in this form.

Here’s a function to encode a triplet, and a procedure to split an encoded colour into its components:

DEF FNencode_col(r%,g%,b%)=b%<<24 OR (g%AND&FF)<<16 OR (r%AND&FF)<<8
DEF PROCdecode_col(c%,RETURN r%,RETURN g%,RETURN b%)
r%=c%>>8AND&FF: g%=c%>>16AND&FF: b%=c%>>>24
ENDPROC

Note the use of a logical shift >>> for b%, which means we don’t need the AND operation.

Define some colours in PROCinit, and get the SWI number for ColourTrans_SetGCOL which we’ll be using later on:

DEF PROCinit
SYS ”OS_ReadModeVariable”,-1,4 TO ,,xeig%
SYS ”OS_ReadModeVariable”,-1,5 TO ,,yeig%
SYS ”OS_SWINumberFromString”,,”ColourTrans_SetGCOL” TO CT_SetGCOL%
black%=0: white%=&ffffff00: red%=&0000ff00: green%=&00ff0000: blue%=&ff000000
cyan%=&ffff0000: magenta%=&ff00ff00: yellow%=&00ffff00
ENDPROC

Now we’ll redefine PROCblend_steps to accept the encoded colours s% and e% (start and end):

DEF PROCblend_steps(s%,e%,steps,RETURN r,RETURN g,RETURN b,RETURN rs,RETURN gs,RETURN bs)
LOCAL r2,g2,b2
PROCdecode_col(s%,r,g,b): PROCdecode_col(e%,r2,g2,b2)
rs=(r2-r)/steps: gs=(g2-g)/steps: bs=(b2-b)/steps
ENDPROC

The RETURNed values are the starting r, g and b colours, and the step values.

Now we can define a generalised procedure. We assume that the variables xeig% and yeig% are defined globally:

DEF PROCrectangle_blend(x%,y%,w%,h%,startcol%,endcol%,flags%)
LOCAL p%,s%,r,g,b,rs,gs,bs,n%
IF w%<0 w%=ABSw%: x%-=w%
IF h%<0 h%=ABSh%: y%-=h%
IF flags% AND 1: p%=w%>>yeig%: s%=1<<xeig% ELSE p%=h%>>xeig%: s%=1<<yeig%
PROCblend_steps(startcol%,endcol%,p%,r,g,b,rs,gs,bs)
IF flags% AND 1 THEN
  FOR n%=0 TO p%: GCOL r,g,b: MOVE x%,y%: DRAW BY 0,h%: x%+=s%: r+=rs: g+=gs: b+=bs: NEXT
 ELSE
  FOR n%=0 TO p%: GCOL r,g,b: MOVE x%,y%: DRAW BY w%,0: y%+=s%: r+=rs: g+=gs: b+=bs: NEXT
ENDIF
ENDPROC

There are a couple of points to note here. Firstly, BASIC’s RECTANGLE statement accepts negative widths/heights, so we make ours do that too by adjusting the coordinates so that the rectangle’s origin is at the bottom left. Secondly, BASIC’s RECTANGLE’s coordinates are inclusive. If there are 2 OS coordinates for each pixel (as there are for many modes), the command RECTANGLE 0,0,16 will actually draw a square 9×9 pixels, not 8×8. The same applies to DRAW BY, so MOVE 0,0: DRAW BY 16,0 will draw a 9-pixel line. To match RECTANGLE’s behaviour we start the FOR loop at zero, and use DRAW BY.

The flags% parameter is used to specify the blend type: if bit 0 is zero it’s a vertical blend, otherwise it’s horizontal. (Wouldn’t it be nice if BASIC allowed optional parameters with default values?) Note that you could use another bit of flags% to specify exclusive coordinates, as described above; modifying the code is left as an exercise.

Here’s an example, using some negative sizes and various orientations:

rect4.webp
PROCinit
PROCrectangle_blend(128,128,96,96,white%,blue%,1)
PROCrectangle_blend(128,128,-96,96,green%,white%,1)
PROCrectangle_blend(128,128,-96,-96,red%,white%,0)
PROCrectangle_blend(128,128,96,-96,white%,magenta%,0)

Generalising more

So far we can only blend between two colours. This is useful in itself, so we’ll define a new procedure to let us use more colours in a different fashion. We’ll also add a way to specify the GCOL action — we can then use logical operations to combine the blend with what’s already on the screen. Here it is:

DEF PROCrectangle_gradient(x%,y%,w%,h%,nc%,gradient%(),flags%)
LOCAL a%,p%,s%,gs,k,n%
a%=flags%>>8AND&FF: REM Extract the GCOL action
IF w%<0 w%=ABSw%: x%-=w%
IF h%<0 h%=ABSh%: y%-=h%
IF flags% AND 1: p%=w%>>yeig%: s%=1<<xeig% ELSE p%=h%>>xeig%: s%=1<<yeig%
gs=nc%/p%: k=0
IF flags% AND 2: k=nc%-1: gs=-gs: REM Invert the gradient
IF flags% AND 1 THEN
  FOR n%=1 TO p%: SYS CT_SetGCOL%,gradient%(k),,,,a%: MOVE x%,y%: PLOT9,0,h%: x%+=s%: k+=gs: NEXT
 ELSE
  FOR n%=1 TO p%: SYS CT_SetGCOL%,gradient%(k),,,,a%: MOVE x%,y%: PLOT9,w%,0: y%+=s%: k+=gs: NEXT
ENDIF
ENDPROC

Instead of start and end colours, we’re supplying gradient%(), a vector of encoded colours; this can be of any length, but we’re using 256 here (you’ll see why later). The nc% parameter specifies the number of entries to use.

The flags% parameter has been extended:

  • Bit 0 = direction: 0 = vertical, 1 = horizontal.
  • Bit 1 = invert gradient. To add this to PROCrectangle_blend, just add the line:
    IF flags% AND 2 SWAP startcol%,endcol%
  • Bits 8-15 (ie byte 1) = GCOL action code. Again, easy to add to PROCrectangle_blend.

And now we’re using the ColourTrans module to set the colour.

For my purposes I wanted a 16×16 square to be exactly 8×8 pixels (so it no longer works like RECTANGLE — see above). Hence the use of PLOT 9, which does a relative solid line excluding the end point, and the FOR loop starting at 1.

Here’s a simple demonstration. The gradient g%() is initialised to blend the red channel from black to red, then this gradient is moved to the green and blue channels by the array multiplication. The green channel is inverted:

grad5.webp
PROCinit
DIM g%(256)
FOR i%=0 TO 127: g%(i%)=FNencode_col(i%*2,0,0): NEXT
PROCrectangle_gradient(0,0, 256,72, 128, g%(), %01)
g%()=g%()*256
PROCrectangle_gradient(0,92, 256,72, 128, g%(), %11)
g%()=g%()*256
PROCrectangle_gradient(0,184, 256,72, 128, g%(), %01)

You could do that with PROCrectangle_blend of course, but you couldn’t do this next example. Set up a gradient with eight alternating black and white colours, and draw two overlapping gradients, one vertical, one horizontal. The second uses the EOR action (GCOL 3) … which will give us a chessboard:

grad1.webp
PROCinit
g%()=black%,white%,black%,white%,black%,white%,black%,white%
PROCrectangle_gradient(0,0,256,256,8,g%(),0)
PROCrectangle_gradient(0,0,256,256,8,g%(),3<<8 OR 1)

RISC OS palette files contain colour definitions, usually (though not necessarily) for 256 colours. If we read in a palette file we can then pass it to PROCrectangle_gradient. Here’s a function to do this:

DEF FNload_palette_file(f$,RETURN pal%())
REM Load a RISC OS palette file into pal%()
REM Returns number of colours in palette
LOCAL h%,d%,ncols%,palsize%
SYS ”OS_File”,17,f$ TO h%,,d%
IF NOT h%=1 AND d%>>>8=&ffffed ERROR 100,”Not a palette file”
IF DIM(pal%())<>1 ERROR 100,”Palette array must be a vector”
pal%()=0: palsize%=DIM(pal%(),1)
h%=OPENIN f$
REPEAT
  IF BGET#h%<>19 CLOSE#h%: ERROR 100,”Bad palette file”
  d%=BGET#h%: REM Colour number, which is ignored
  IF BGET#h%=16 THEN
    pal%(ncols%)=BGET#h%<<8 OR BGET#h%<<16 OR BGET#h%<<24
    ncols%+=1
    IF ncols%>palsize% CLOSE#h%: ERROR 100,”Too many colours”
   ELSE
    REM We ignore mouse/border etc stuff
    d%=BGET#h%: d%=BGET#h%: d%=BGET#h%:
  ENDIF
UNTIL EOF#h%
CLOSE#h%
=ncols%

This takes a full filename and a vector as parameters. The returned value is the number of colours read, and the vector now contains the encoded colour definitions.

Here’s an example, using a rainbow palette file (this assumes a system variable Palettes$Path has been set appropriately). It draws both horizontal and vertical gradients, so you can see how the colour gradient is stretched or shrunk to fill the area:

PROCinit
DIM rainbow%(256)
pf$=”Palettes:bow2/pal”
ncols%=FNload_palette_file(pf$,rainbow%())
PROCrectangle_gradient(0,300,1280,128,ncols%,rainbow%(),1)
PROCrectangle_gradient(0,0,1280,128,ncols%,rainbow%(),0)
grad2.webp
grad3.webp

Incidentally, these palette files were all generated by my Gradgrind program.

Finally, this uses a pastel palette shown in four orientations:

grad4.webp
PROCinit
pf$=”Palettes:pastel/pal”
ncols%=FNload_palette_file(pf$,g%())
PROCrectangle_gradient(128,128,128,128,ncols%,g%(),%11)
PROCrectangle_gradient(128,128,-128,128,ncols%,g%(),%01)
PROCrectangle_gradient(128,128,-128,-128,ncols%,g%(),%10)
PROCrectangle_gradient(128,128,128,-128,ncols%,g%(),%00)