Drawing a circle (solid)

Share your advanced PureBasic knowledge/code with the community.
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Drawing a circle (solid)

Post by Mistrel »

I was experimenting with different ways of drawing a circle. This one draws one quadrant and mirrors it over to the other three.

The circles come out OK but they don't look as nice as the ones that PureBasic draws. I tried compensating with custom smoothing "thresholds" (commented out) in an effort to help the smaller circles match PureBasic's look and hoped that (1-1.0/r) would be sufficient for larger circles to drop the last pixel along the edge. But it still looks kind of bad.

All of the custom thresholds besides the default have been commented out. You can uncomment them to see how they look for comparison.

The example code will draw two circles. The one on the left is from this drawCircle2i() and the one on the right is PureBasic's Circle(). There is a button on the window you can click which will increase the radius by 1 each time it's clicked and redraw the circles.

There are other algorithms to draw circles but it would be interesting to know if this method can be improved.

Circle drawing code:

Code: Select all

Procedure drawCircle2i(x, y, radius, color.l)
  Protected r=radius+1 ;/ Radius does not include the center pixel
  Protected d=r*2 ;/ Diameter : (r<<1)
  Protected rr=r*r ;/ r^2
  Protected xi, yi ;/ Integer 0 to r along x / y
  Protected dx, dy ;/ Delta x / y for pythagorean (xi - r)^2 and (yi - r)^2
  Protected dcx2, dcy2 ;/ Delta from center axis * 2
  Protected centerX=r
  Protected centerY=r
  Protected threshold.f ;/ Smooth the last pixel along the radius
  
  threshold.f=1
  
  ;/ Manually tweak threshold for small circles
  Select radius
  Case 1
    Plot(1,0,color)
    Plot(0,1,color)
    Plot(1,1,color)
    Plot(2,1,color)
    Plot(1,2,color)
    
    ProcedureReturn
;   Case 4
;     threshold.f=0.75
;   Case 5, 13
;     threshold.f=0.9
;   Case 6, 7, 8
;     threshold.f=0.88
;   Case 16, 17
;     threshold.f=0.93
;   Case 21
;     threshold.f=0.94
;   Case 22, 23, 24, 25, 26, 27
;     threshold.f=0.95
;   Case 28
;     threshold.f=0.96
;   Case 29, 30
;     threshold.f=0.9623
;   Case 33
;     threshold.f=0.965
;   Case 37
;     threshold.f=0.983
;   Default
    threshold.f=1-1.0/r
  EndSelect
  
  ;/ The circle is shifted -1 along x and y to account for
  ;/ a single pixel appearing at each pole. The pixel is
  ;/ accounted for by dx+dy '<' rr instead of '<='
  ;/ to by drawing pixels which are within the radius but
  ;/ not those which lay on it exactly.
  x-1
  y-1
  
  For yi=0 To r
    dy=(r-yi)*(r-yi)
    dcy2=(centerY-yi)*2
    
    For xi=0 To r
      dx=(r-xi)*(r-xi)
      
      ;/ Point is on or outside the circle
      If Not dx+dy<rr
        Continue
      EndIf
      
      ;/ Distance from center 0-1
      distance.f=(dx+dy)/rr
      
      ;/ Edge threshold (smoothing)
      If distance.f>threshold.f
        Continue
      EndIf
      
      dcx2=(centerX-xi)*2
      
      ;/ Vertical axis
      If centerX=xi
        Plot(x+xi,y+yi,color)
        Plot(x+xi,y+yi+dcy2,color)
      ;/ Horizontal axis  
      ElseIf centerY=yi
        Plot(x+xi,y+yi,color)
        Plot(x+xi+dcx2,y+yi,color)
      Else
        ; Top left quadrant
        Plot(x+xi,y+yi,color)
        
        ;/ Top right quadrant
        Plot(x+xi+dcx2,y+yi,color)
        
        ;/ Buttom left quadrant
        Plot(x+xi,y+yi+dcy2,color)
        
        ;/ Bottom right quadrant
        Plot(x+xi+dcx2,y+yi+dcy2,color)
      EndIf
    Next xi
  Next yi
EndProcedure
Example:

Code: Select all

window=OpenWindow(#PB_Any,0,0,320,320,"")
CreateImage(#Image,320,320,32,#PB_Image_Transparent)
button=ButtonGadget(#PB_Any,65,50,20,20,"")
radius=29
diameter=radius*2
outline=5

r=radius

ImageGadget(#ImageGadget,0,0,320,320,ImageID(#Image))

StartDrawing(ImageOutput(#Image))
DrawingMode(#PB_2DDrawing_AlphaBlend)

drawCircle2i(1,1,radius,RGBA(0,0,0,255))

Circle(radius*2+4+radius,radius+1,radius,RGBA(0,0,0,255))

offsetX=50
offsetYn=9
offsetXY=51
offsetYXn=10

StopDrawing()
SetGadgetState(#ImageGadget,ImageID(#Image))

Repeat
  event=WaitWindowEvent(1)
  
  If event=#PB_Event_Gadget And EventGadget()=button
    radius+1
    
    FreeImage(#Image)
    CreateImage(#Image,320,320,32,#PB_Image_Transparent)
    ImageGadget(#ImageGadget,0,0,320,320,ImageID(#Image))
    
    StartDrawing(ImageOutput(#Image))
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    
    drawCircle2i(1,1,radius,RGBA(0,0,0,255))
    Circle(radius*2+4+radius,radius+1,radius,RGBA(0,0,0,255))
    
    StopDrawing()
    SetGadgetState(#ImageGadget,ImageID(#Image))
  EndIf
Until event=#PB_Event_CloseWindow
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Drawing a circle (solid)

Post by Mistrel »

Here is another, much simpler attempt I had made earlier in my experiments:

Code: Select all

Procedure drawCircle2f(x.f, y.f, radius.f, color.l)
  Protected r.f=radius.f ;/ Radius
  Protected d=radius.f*2 ;/ Diameter
  Protected r2.f=radius.f*radius.f ;/ r^2
  Protected deltaX.f ;/ (x-r)^2
  Protected deltaY.f ;/ (y-r)^2
  Protected xi, yi ;/ Integer x/y
  
  For yi=0 To d
    deltaY.f=(yi-r.f)*(yi-r.f)
    
    For xi=0 To d
      deltaX.f=(xi-r.f)*(xi-r.f)
      
      If deltaX.f+deltaY.f<=r2.f
        Plot(xi+x.f,yi+y.f,color)
      EndIf
    Next xi
  Next yi
EndProcedure
Post Reply