Bresenham Line Algorithm test-1


'
? "title - Bresenham Line Algorithm test-1"
? "by Ted Clayton"
? "ref - http://members.chello.at/~easyfilter/bresenham.html"
'
x0 = 50 : y0 = 50 ' Starting-point of a line...
x1 = 600 : y1 = 400 ' ... and the end-point.
'
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change
'
IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left
'
IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down
'
err = dx + dy ' error value e_xy
'
D$ = "" ' init DRAW-string
'
REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF
D$ = D$ + "M" + x0 + "," + y0 ' add points to DRAW-string
UNTIL x0 = x1 AND y0 = y1
'
DRAW D$ ' execute DRAW-string
'
END

Well as far as I can see, no difference to a regular line only slower. Built-in LINE must already use this algo.

'
' "title - Bresenham Line Algorithm test-1"
' "by Ted Clayton"
' "ref - http://members.chello.at/~easyfilter/bresenham.html"
'
' MGA modifications: Bresenham Line Algorithm put into sub
' and fixed with PSET and compared to regular
' line drawing. 2017-03-22
'
for i = 1 to 100 'test bline with 100 random lines
x0 = rnd * xmax\1 : x1 = rnd * xmax\1
y0 = rnd * ymax\1 : y1 = rnd * ymax\1
color rgb(rnd*255, rnd* 255, rnd*255)
locate 1,1 : print space(40)
locate 1,1 : print "(";x0;", ";y0;") - (";x1;", ";y1;")"
bline x0, y0, x1, y1 '< comment out and uncomment next line for compare
'line x0, y0, x1, y1 '< this the same as bline only very much faster
next
pause

sub
bline(x0, y0, x1, y1) '<<<< Bresenham Line Algorithm test-1"
local dx, dy, sx, sy, D, err, e2
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change

IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left

IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down

err = dx + dy ' error value e_xy

D = "" ' init DRAW-string

REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF
D = D + "M" + x0 + "," + y0 ' add points to DRAW-string
UNTIL x0 = x1 AND y0 = y1

pset x0, y0 '<<<<<<<<<<<<<<<<<<<<<<<<< this is needed to set first point
DRAW D ' execute DRAW-string
END


I look forward to seeing how this applies to thickening lines.

Append: Upon closer examination of two types of lines, the nearly horizontal or nearly vertical appear more jagged with bline but built-in line looks like candy cane with dark spiral through the line probably the effects of alias?

take a screencap and zoom in until there is a pixel grid.

printsc will copy the screen, ctrl-v will paste into your paint program, though more recent versions of windows have (debateably) more convenient screencap options as well.

Confirmed, the sub bline makes 'steps' and regular built-in line makes 'ropes'... also blines contain more white when using the default color. Line must use a much more sophisticated algo.

MGA notes, "[N]o difference to a regular line only slower." Yes, for the first step & test, I went with the simple-line Bresenham case.

Thanks for the test-version! Right, a SUB or FUNC is better.

figosdev suggests a zoom & pixel-grid, which shows jaggies or steps on Bresenham. That too is as it should be, since this first test-line doesn't do any anti-alias. That's a normal enhancement though.

Here is the PDF paper, with all the guts & glory (it's linked from my ref page; they're the same work):
http://members.chello.at/%7Eeasyfilter/Bresenham.pdf

Forget the Draw command! And here is a side by side comparison of bline and line:


'
' "title - Bresenham Line Algorithm test-1"
' "by Ted Clayton"
' "ref - http://members.chello.at/~easyfilter/bresenham.html"
'
' MGA modifications: Bresenham Line Algorithm put into sub
' and fixed with PSET and compared to regular
' line drawing. 2017-03-22

' mod #2 no draw string, it works a heck of lot faster!!!
'
for i = 1 to 100
bline 0, 300 - i*3, 600, 300
line 0, 0, 600, i*3
next
locate 14, 10 : ? "Top diagonal normal line, bottom bline."
pause

sub
bline(x0, y0, x1, y1) '<<<< Bresenham Line Algorithm test-1"
local dx, dy, sx, sy, D$, err, e2
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change

IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left

IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down

err = dx + dy ' error value e_xy

D = "" ' init DRAW-string

REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF
pset x0, y0 '<<<< no draw string!!!
UNTIL x0 = x1 AND y0 = y1
END

I put an identical LINE in a loop with timing .. but soon saw that much of the resulting number would be the loop, not the LINE. This will need a second test, to measure an empty loop (or near as we can get to empty). There is sometimes a "No-Op" language element (NOP). Do we have anything like that in SmallBASIC?

Suffice to say, yeah, optimized LINE is fabulously faster than BASIC-coded Bresenham.

I probably got the cart ahead of the horse, chasing after DRAW, at this point. But it slaps all the dots of the screen, PDQ.

With DRAW, all the coordinates are calculated & all the GDL commands are stuffed in the string first, and only THEN do we see anything happen. So there is an initial delay or pause. With PSET in the loop, dots are lining up as the loop runs.

Ted



' alternate bline - same results without the complication
m = 50
while m > .5
cls
for i = 1 to 300/m
bline 0, 300 - i*m, 600, 300
line 0, 0, 600, i*m
next
locate 14, 10 : ? "Top diagonal normal line, bottom bline."
delay 500
if m > 10 then m = m - 5 else m = m - 1
wend
locate 16, 10 : ? "Notice which method fills better!"
pause

sub
bline(x0, y0, x1, y1)
local dx, dy, d, i
dx = x1 - x0 : dy = y1 - y0
d = sqr(dx * dx + dy * dy)
dx = dx / d : dy = dy / d
pset x0, y0
repeat
x0 = x0 + dx
y0 = y0 + dy
pset x0, y0
until abs(x0 - x1) < 1
end


Opps! Alternate bline had a little problem found in following test, fixed!


' alternate bline - same results without the complication
m = pi/10
while m > .001
cls
for a = 0 to 2 *pi step m
bline 200, 200, 200 + 200 * cos(a), 200 + 200 * sin(a)
line 600, 200, 600 + 200 * cos(a), 200 + 200 * sin(a)
next
delay 500
m = m/2
wend
pause

sub
bline(x0, y0, x1, y1)
local dx, dy, d, i
dx = x1 - x0 : dy = y1 - y0
d = sqr(dx * dx + dy * dy)
dx = dx / d : dy = dy / d
pset x0, y0
repeat
x0 = x0 + dx
y0 = y0 + dy
pset x0, y0
until abs(x0 - x1) < 1 and abs(y0 - y1) < 1 '<<<< fix a little problem
end


Hey! an alternate way to make and fill a circle!!!

Here's another one... Brute force version of Bresenham's circle algorithm...

radius = 100
originx = 100
originy = 100
origin2x = 300
origin2y = originy

color rgb(255,255,0)
for y = -radius to radius
y = y + 0.001
for x = -radius to radius
x = x + 0.001
if x*x + y*y <= radius*radius then
pset originx + x,originy + y
pset origin2x + x,origin2y + y
end if
next
next

Why compare 2 circles with same algo?

and Why:

radius = 100
originx = 100
originy = 100
origin2x = 300
origin2y = originy

color rgb(255,255,0)
for
y = -radius to radius
y = y + 0.001 '<<<<<<<<<<<<<<<<<< ??? why
for x = -radius to radius
x = x + 0.001 '<<<<<<<<<<<<<<<<< ??? why
if x*x + y*y <= radius*radius then
pset originx + x,originy + y
pset origin2x + x,origin2y + y
end if
next
next
pause


radius = 100
ox = 200
oy = 200

color rgb(255,255,0)
r2 = radius*radius
for
x = 0 to radius
y = sqr(r2-x*x)
line ox-x, oy+y, ox-x, oy-y
line ox+x, oy+y, ox+x, oy-y
next
pause

To fill a whole screen with circles at various points, use an array of ox, oy points and go through the array with each calc of x, y and draw the 2 lines. By the time x = radius, you'd have all the circles drawn!

It may be. But if memory serves correctly, additions and multiplications, process a bit quicker than sqr().

J

SQR is probably slower than add but lets count steps and decisions (decisions take longer than simple math operations).

If the radius is 10 then mine takes 0 to 10 = 11 steps + 11 decisions (at each loop) = 22

Yours first goes -10 to 10 for y's = 21 steps
and for each of those steps it goes from -10 to 10 more steps for x = 21 X 21 = 441 steps PLUS a decision for everyone of those steps and then a decision to loop = 882 decisions. Total = 441 steps + 882 decisions.
(We won't even mention what appears to be unnecessary x = x +.001, y = y + .001)

Then yours draws pi * 100 points and mine draws 11 lines; though its probably true a line takes longer to draw than a point. ;-))

Why? Because the original list had 'y=y+1' and it produced an image that looked like a waffle. Reduced the amount to '0.001' to get a completely filled circle. But, as to the reason why the 'y' increment was added, I have no idea.

J

You might quote your source, if you didn't create something on your own.

I wonder if the y = y + 1 wasn't working with a while /wend eg while y < radius...

The context of the source would explain the y = y + 1, I suspect it wasn't using FOR/ NEXT loops.

Hi johnno56,

When the formalized, official approach flogs itself to a frazzle, is hard to read-follow-understand, and takes 5 times as long as counting on yer fingers, a person can start to wonder.

There's a Russian-something that does a ballistic curve, like in GORILLAS, all by check a status and adding or subracting a "1". Pulverizes the official-method gorilla. Other curves too. - Ted

Yes MGA, the Bresenham topic comes to circles quickly. And it fills them. And the fill can be textured.

There is an easy way to 'spy' on CIRCLE-implementations, works good on a dog machine. With later-day Colossii it can be harder to see; multiple concentric circles helps. Make the screen as big as possible, and make a CIRCLE that mostly fills it; biggest circle you have room for. Put CIRCLE and CLS in a loop. On fast machines, maybe a delay after CLS. Blink-blink-blink-blink.

CIRCLE can sometimes be seen rendering the figure as four independent quadrants, simultaneously. Bresenham does it that way .. and it also readily drawns lines from both ends, at the same time.

Ted

Thank you all for the excellent input & feedback! I am off soon for another day working outside, so just a brief ack; back tonight. Will soon have a little down-time, maybe tomorrow.

I have a bigger, messy version of this code, with timing & report code inserted. It needs several versions of the main loop, to make fair comparisons of the different methods & factors, so it's much bigger. I am cleaning this up, and will post it. Using PSET is very slow, and DRAW, even point-by-point, is much faster. DRAW will be limited by the size of the STRING that is generated: this one for this simple LINE is over 4K. So rendering can't just go 'on and on' indefinitely, with DRAW. But, once a DRAW-string is built, it can be saved as text, and it is also available for further processing.

In our context, Bresenham isn't intended to compete with existing BASIC. It can make eg the thick line ... but also many other sorts of custom lines, curves, fills, textures. In the literature - I have a fine paper that I'll link - it is mainly about advanced lines, like Beziers & Splines. Pragmatically, it may be in PLOT contexts that it becomes very useful to have customized lines. Also great for my main working interest, GIS & maps.

The thick-line version is coming along good.

Ted

Ah yes Ted, having draw string might be handy for other things but you are using built-in LINE when you Move from point to point with DRAW string (specially if the distance between points is greater than 1, then you are drawing lines with some version of LINE). I can't imagine why it would be faster unless distance between points is greater than 1, I will have to make a timed test.

Yes Johnno, combine Bresenham line algo with a version of what you posted would be drawing thick lines with circles, just what I was doing to create thick lines! Rick and I did a circle study at Naalaa, calculate the edge in one quadrant then use symmetries and draw lines from one edge to the other, fairly fast, very filled circle but using built-in CIRCLE is much faster.

Hmm... loading the points to be drawn into an array, process to eliminate all duplicate, draw lines instead of points whenever possible say by sorting on x coordinate, would make drawing something over and over again more efficient.

For drawing a very curvy item with tiny lines, I am sure you need to draw filled circles at either end of each line segment.

DRAW has 2 prefix-commands, B and N; to move-without-plotting, and to move-and-return. Both eliminate the issue of drawing lines from one dot to the next.

I have not done the careful tests in SmallBASIC yet, but I've done them in other BASICs. A quick check in SB strongly suggests that its DRAW routine, like the others, apparently uses 'magic'. It shouldn't be this fast, but it is. I knew this, from years back.

When we add the DRAW-commands to eliminate drawing lines from point-to-point, it will probably run even faster. The string gets bigger, which we don't like, but it knows it doesn't have to 'fool around' between isolated points.

DRAW asserts in the DOCs that it is based on some formal Graphics Definition Language, GDL. Since several BASICs exhibit similar - startling - performance, I suspect this was some Official Spec from back in the old days, and it came with example implementation-code. People have been just cabbaging onto the oldie but goodie, ever since.

I've done 10s of 1,000s of RND (isolated) points on the screen, and it just hoses them on. Tests do need to be done in SmallBASIC.

Bear in mind, that we are doing all the calculations, and concatenating all the little point-strings, before DRAW does anything - there's a delay. With PSET, we are making a dot with each pass in the loop.

But you're right, that PSET is the place to start, and latter on we can branch out. It's the simply & basic way, and it's just BASIC afterall; fast isn't an early priority.

Ted

33+ times faster is PSET over DRAW in my test here:

'timed tests

for
test = 1 to 3
start = ticks
cls
select case test
case 1
for i = 0 to ymax
tline 0, 0, xmax, i
next
t1 = ticks - start
locate 25, 1: input "Ted's Bresenham (fixed) ";OK
case 2
for i = 0 to ymax
pline 0, 0, xmax, i
next
t2 = ticks - start
locate 25, 1: input "Ted's Bresenham with PSET replacing DRAW ";OK
case 3
for i = 0 to ymax
bline 0, 0, xmax, i
next
t3 = ticks - start
locate 25, 1: input "MGA's bline: Dang, look at the holes, press enter ";OK
end select
next
cls
? "Test 1 ms using tline (Ted's Bresenham with DRAW string): ";t1
? "Test 2 ms using pline (Ted's Bresenham with DRAW string replaced by PSET): ";t2
? "Test 3 ms using bline (MGA's bline): ";t3
? "The first test (with DRAW) is ";t1/t2;" times the 2nd test (with just PSET)."
? "MGA's bline is ";t3/t2;" times the 2nd test."
pause
end

sub
bline(x0, y0, x1, y1)
local dx, dy, d, i
dx = x1 - x0 : dy = y1 - y0
d = sqr(dx * dx + dy * dy)
dx = dx / d : dy = dy / d
pset x0, y0
repeat
x0 = x0 + dx
y0 = y0 + dy
pset x0, y0
until abs(x0 - x1) < 1 and abs(y0 - y1) < 1 '<<<< fix a little problem
end


sub
tline(x0, y0, x1, y1) '<<<< Bresenham Line Algorithm test-1"
local dx, dy, sx, sy, D, err, e2
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change
IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left
IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down
err = dx + dy ' error value e_xy
D = "" ' init DRAW-string
REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF

D = D + "M" + x0 + "," + y0 ' add points to DRAW-string
UNTIL x0 = x1 AND y0 = y1
pset x0, y0 '<<<<<<<<<<<<<<<<<<<<<<<<< this is needed to set first point
draw D
END

sub
pline(x0, y0, x1, y1) '<<<< Bresenham Line Algorithm test-1"
local dx, dy, sx, sy, err, e2
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change

IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left

IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down

err = dx + dy ' error value e_xy

REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF
pset x0, y0
UNTIL x0 = x1 AND y0 = y1
END


Also, I was testing the sub with DRAW with a very large circle at middle of screen (to test lines at all angles) and DRAW started throwing errors about D missing a "," separator??? Is it possible DRAW has a limit on number of places or integers only? (Perhaps it was a mistake on my part, I will try and replicate the error.)

I've got your test-program loaded & running MGA. I'm seeing some unexpected things and am chasing it down...

Ted

A limit on DRAW will be the limits of a string, in BASIC/SB. Though, DRAW can 'hand off' execution from one string to another. - TC


'timed tests
const cx = xmax/2
const cy = ymax/2
const rr = min(cx, cy)

for
test = 2 to 4 'test only works for 3
start = ticks
cls
select case test
case 1
for a = 0 to 2 * pi step pi/720
xx = rr * cos(a)
yy = rr * sin(a)
tline cx, cy, cx + xx, cy + yy
next
t1 = ticks - start
locate 25, 1: input "Ted's Bresenham (fixed) ";OK
case 2
for a = 0 to 2 * pi step pi/720
xx = rr * cos(a)
yy = rr * sin(a)
pline cx, cy, cx + xx, cy + yy
next
t2 = ticks - start
locate 25, 1: input "Ted's Bresenham with PSET replacing DRAW ";OK
case 3
for a = 0 to 2 * pi step pi/720
xx = rr * cos(a)
yy = rr * sin(a)
bline cx, cy, cx + xx, cy + yy
next
t3 = ticks - start
locate 25, 1: input "MGA's bline: Dang, press enter ";OK
case 4
for a = 0 to 2 * pi step pi/720
xx = rr * cos(a)
yy = rr * sin(a)
bline2 cx, cy, cx + xx, cy + yy
next
t4 = ticks - start
locate 25, 1: input "MGA's newly formulated bline2, press enter ";OK
end select
next
cls
'? "Test 1 ms using tline (Ted's Bresenham with DRAW string): ";t1
? "Test 2 ms using pline (Ted's Bresenham with DRAW string replaced by PSET): ";t2
? "Test 3 ms using bline (MGA's bline): ";t3
? "Test 4 ms using bline2 (MGA's new bline version): ";t4
'? "The first test (with DRAW) is ";t1/t2;" times the 2rd test (with just PSET)."
? "MGA's bline is ";t3/t2;" times the 2nd test."
? "MGA's bline2 is ";t4/t3;" times bline."
pause
end

sub
bline(x0, y0, x1, y1)
local dx, dy, d, i
dx = x1 - x0 : dy = y1 - y0
d = sqr(dx * dx + dy * dy)
dx = dx / d : dy = dy / d
pset x0, y0
repeat
x0 = x0 + dx
y0 = y0 + dy
pset x0, y0
until abs(x0 - x1) < 1 and abs(y0 - y1) < 1 '<<<< fix a little problem
end

sub
bline2(x0, y0, x1, y1)
local dx, dy, d, i
dx = x1 - x0 : dy = y1 - y0
d = sqr(dx * dx + dy * dy)
dx = dx / d : dy = dy / d
pset x0, y0
for i = 1 to d 'for/next loop takes the decisions out of the loop process
x0 = x0 + dx
y0 = y0 + dy
pset x0, y0
next
end

sub
tline(x0, y0, x1, y1) '<<<< Bresenham Line Algorithm test-1"
local dx, dy, sx, sy, D, err, e2, ix, iy
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change
IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left
IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down
err = dx + dy ' error value e_xy
D = "" ' init DRAW-string
REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF
ix = str(int(x0)) : iy = str(int(y0)) '<<<<<<<<<<<<<<<< OK ix and iy are string integers for DRAW string
D = D + "M" + ix + "," + iy ' add points to DRAW-string
until abs(x0 - x1) < 1 and abs(y0 - y1) < 1 '<<<< fix a little problem
pset x0, y0 '<<<<<<<<<<<<<<<<<<<<<<<<< this is needed to set first point
draw D
END

sub
pline(x0, y0, x1, y1) '<<<< Bresenham Line Algorithm test-1"
local dx, dy, sx, sy, err, e2
dx = abs(x1-x0) ' find how much x..
dy = -abs(y1-y0) ' .. and y change

IF x0 < x1 THEN sx = 1 ' is the line left-to-right..
IF x0 >= x1 THEN sx = -1 ' .. or right-to-left

IF y0 < y1 THEN sy = 1 ' does line slope up..
IF y0 >= y1 THEN sy = -1 ' .. or down

err = dx + dy ' error value e_xy

REPEAT ' find points-on-line
e2 = err + err
IF e2 >= dy THEN ' e_xy + e_x > 0
err = err + dy
x0 = x0 + sx
ENDIF
IF e2 <= dx THEN ' e_xy + e_y < 0
err = err + dx
y0 = y0 + sy
ENDIF
pset x0, y0
until abs(x0 - x1) < 1 and abs(y0 - y1) < 1 '<<<< fix a little problem
END


' Johnno56 Brute Force Circle
' mod by Ted Clayton 2017/03/26
'
' 1. Removed the tiny increments, and it ran the same.
' 2. Removed code for duplicate-circle, no difference.
' 3. Reorganized calculation, to reduce re-calculation.
' 4. Assigned compact variable-names, use INTegers.
'
' First goal was to PSET only for circle, not to FILL disk.
' Second, use opposite circle x-values, for a level LINE for FILL.
' (Horizontal & vertical only versions of LINE can be faster.)
' Evident further goals, unaddressed.
' Issue with polar circle-gaps.
'
radius = 50
originx = 100
originy = 100
'
ro = INT(radius) ' make compact INTs
xo = INT(originx)
yo = INT(originy)
rs = INT(radius * radius)
'
FOR y = -ro TO ro
ys = INT(y * y) ' square y once, here
FOR x = -ro TO ro
xs = INT(x * x) ' square x
IF xs + ys <= rs THEN ' sum-of-squares in ro-squared?
xyr = xo + x ' x-at-y, right
xyl = xo - x ' x-at-y, left
yx = yo + y ' y-at-x, both
yxf = yx + (2 * ro) ' fill-circle-y, 2 ro below
PSET xyr, yx ' right-hand circle dot
PSET xyl, yx ' left-hand circle dot
LINE xyl, yxf, xyr, yxf ' LINE-FILL-circle, below
x = ro ' done with this y-level, exit inner loop
ENDIF
NEXT x
NEXT
y
'
' Sorry, I inserted this in the wrong place.

Because of circle symmetry, you only need find x, y points in 1 quadrant eg for y = 0 to radius, for x = 0 to radius
If x, y pair are on the edge then so are (-x, y), (-x, -y), (x, - y).

When I first saw Bresenham's name associated with a Circle Algo, I thought it was named for employing that trick with symmetry.

Append: Oh maybe that's what you mean by BRUTE force, not using the symmetry. ;-))

No, I only wish I could claim to have been honoring the brutishness of Johnno's gem. ;)

And I had seen that Bresenham exploits quads in Circle; like with straight Lines, working from both ends, or pre-calculating multiple sub-segments of a FUNC/PLOT.

It turns out, Thick is a special-case, particular type/application of anti-alias. :)