Search code examples
assemblynasmtasm

What is the easiest way to draw a perfectly filled circle(disc) in assembly?


I'd like to draw a perfectly filled* circle(disc) in assembly in 320x200 mode with a 100 pixel radius. What's the quickest/easiest way?

(*: I mean a disc is perfectly filled when its color is e.g. constant white and has no black pixels in it.)


Solution

  • If by quickest you mean the quickest to write, here a simple solution for DOS.

    It doesn't use any DOS service but the exit one.
    It is meant to generate a COM file (raw output with NASM is fine, just rename it with COM extension).

    BITS 16
    
    ORG 100h
    
    push 0a000h           ;Video memory graphics segment
    pop es
    
    mov ax, 0013h         ;320x200@8bpp
    int 10h
    
    
    push 09h              ;Blue
    push 159              ;cX
    push 99               ;cY
    push 60               ;Radius
    call drawFilledCircle
    
    ;Wait for a key
    xor ah, ah
    int 16h
    
    ;Restore text mode
    mov ax, 0003h
    int 10h
    
    ;Return
    mov ax, 4c00h
    int 21h
    
    
    ;Color
    ;cX
    ;cY
    ;R
    drawFilledCircle:
     push bp
     mov bp, sp
    
     sub sp, 02h
    
     mov cx, WORD [bp+04h]   ;R
    
     mov ax, cx              
     mul ax                  ;AX = R^2
     mov WORD [bp-02h], ax   ;[bp-02h] = R^2
    
    
    
     mov ax, WORD [bp+06h]
     sub ax, cx              ;i = cY-R
     mov bx, WORD [bp+08h]
     sub bx, cx              ;j = cX-R
    
     shl cx, 1
     mov dx, cx              ;DX = Copy of 2R
    
    .advance_v:
     push cx
     push bx
    
     mov cx,  dx
    
    .advance_h:
      ;Save values
      push bx
      push ax
      push dx
    
      ;Compute (i-y) and (j-x)
      sub ax, WORD [bp+06h]
      sub bx, WORD [bp+08h]
    
      mul ax                  ;Compute (i-y)^2
    
      push ax
      mov ax, bx             
      mul ax
      pop bx                  ;Compute (j-x)^2 in ax, (i-y)^2 is in bx now
    
      add ax, bx              ;(j-x)^2 + (i-y)^2
      cmp ax, WORD [bp-02h]   ;;(j-x)^2 + (i-y)^2 <= R^2
    
      ;Restore values before jump
      pop dx
      pop ax
      pop bx
    
      ja .continue            ;Skip pixel if (j-x)^2 + (i-y)^2 > R^2
    
      ;Write pixel
      push WORD [bp+0ah]
      push bx
      push ax
      call writePx
    
    
    .continue:
    
      ;Advance j
      inc bx
     loop .advance_h
    
     ;Advance i
     inc ax
    
    
     pop bx            ;Restore j
     pop cx            ;Restore counter
    
    loop .advance_v
    
     add sp, 02h
    
    
     pop bp
     ret 08h
    
    
    
    ;Color
    ;X
    ;Y
    writePx:
     push bp
     mov bp, sp
    
     push ax
     push bx
    
     mov bx, WORD [bp+04h]
     mov ax, bx
     shl bx, 6
     shl ax, 8
     add bx, ax       ;320 = 256 + 64
    
     add bx, WORD [bp+06h]
     mov ax, WORD [bp+08h]
    
     ;TODO: Clip
    
     mov BYTE [es:bx], al
    
     pop bx
     pop ax
    
     pop bp
     ret 06h
    

    It simply a common technique for writing plane figures given some parameters, it's called rasterization.

    Given the center C(x, y) and the radius R of the circle, the algorithm is as follow

    1. For i = y-R to y+R
    1.1   For j = x-R to x+R
    1.1.1     If (i-y)^2 + (j-x)^2 <= R^2 Then
    1.1.1.1      DrawPixel(j, i)
    1.1.1     End if
    1.1   End For
    1. End For
    

    This is not optimized for speed, at all!
    I indeed create multiple routine for the sake of clarity. Also I use the stack a lot.

    Note writePx does not clip the coordinates!


    If you want to speed up the things you need to be more specific on your requirement.
    For example is the radius always fixed? If yes you can use a block of memory that encode a quarter of circle, something like this

    0 0 0 0 0 1 1 1
    0 0 0 1 1 1 1 1
    0 0 1 1 1 1 1 1
    0 1 1 1 1 1 1 1
    0 1 1 1 1 1 1 1
    0 1 1 1 1 1 1 1
    1 1 1 1 1 1 1 1
    1 1 1 1 1 1 1 1 
    

    Where each digits may be a bit or a byte depending on your speed vs memory constraints.
    You can then copy this block directly into video memory or use it as a template (kind of chroma key technique).
    For printing the other three quarters of the circle simple play with the counters.

    If the radius is not fixed you can boost the code above by

    • Inlining the functions call
    • Avoid the use of the stack as much as possible
    • Don't compute the distance at every cycle but compute it from previous value using basic calculus.
    • Compute more that one pixel at a time and combine writings.