Serebyte


Home | Tutorials | Portfolio

Win32 Software Renderer in C: Part 7 - Drawing a Circle


Note: this tutorial series requires significant knowledge of the C programming language


In this tutorial we will look at drawing a circle to our render buffer.




The Theory:

Drawing a circle to the render buffer requires us to use the equation of said circle in the form (x - a)^2 + (y - b)^2 = r^2, so make sure that you are familiar with this equation.

As we will be using the square root function (sqrt) in this tutorial, we will need to include math.h into our main.h file.




Creating Our drawCircle Function

We need to create a drawCircle function. This should not return anything (i.e. be a void function) and should take six parameters. The first three are integers, and are the centre x coordinate (named centreX), the centre y coordinate (named centreY) and the radius (named radius) in that order. The next three are all 8-bit unsigned integers and are the red, green and blue values of the colour we wish to draw our circle with.

The code for drawing our circle is actually very simple. First, we should think about the equation that defines our circle: (x - a)^2 + (y - b)^2 = r^2, where the centre of the circle is (a, b) and r is the radius. We know a, b and r as they are values that are passed into our function as arguments. We will step through each of the rows between centreY - radius (the bottom of the circle) and centreY + radius (the top of the circle), so we know y for each row.


We need to find the starting and ending x coordinates for each row. We can calculate this with the data we know. If (x - a)^2 + (y - b)^2 = r^2, then (x - a)^2 = k, where k = r^2 - (y - b)^2. Therefore, (x - a) = + or - sqrt(k). This means that x = a + or - sqrt(k). Therefore, the start of the row is at x = a - sqrt(k), and the end of the row is at x = a + sqrt(k).


For our function code, we first need to create a loop counter, which I will call i, using the line "int i;". We then need to iterate through all rows of our circle using the line "for(i = centreY - radius; i <= centreY + radius; i++){". For each iteration, we need to calculate our k value. Remember, k = r^2 - (y - b)^2, so we can use the line: "double k = radius * radius - (i - centreY) * (i - centreY);".


Finally, we want to draw our row for each iteration. We can do this with the line "drawRow((int) centreX - sqrt(k), (int) centreX + sqrt(k), i, red, green, blue);".


Inside our render function, we actually need to call our drawCircle function. I will be doing this with the line: "drawCircle(BUFFER_WIDTH / 2, BUFFER_HEIGHT / 2, 100, 0, 255, 0);"


That's all the theory for this tutorial. I would recommend you look through the code below, and have a go at writing out the progam yourself.




The Code:

Here is the code for drawing a circle. As we have made a few small changes to main.h (including math.h and declaring our drawCircle function) I have included both main.h and render.c below. You will, of course, also need a main.c file, but we have not changed the code of this for a few tutorials, so I have not included it below. As usual, I will be compiling this with MinGW and will be using the command: "gcc main.c -lgdi32 -o app".


main.h

/*
 Software Renderer in C - Part 7

 main.h

 This header file groups all our headers, structure declaration, global variables and function declarations into one file.
*/


//include headers
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>

//define window size constants
#define BUFFER_WIDTH 640
#define BUFFER_HEIGHT 480

//create pixel structure
//this is a 24-bit structure that stores a red, green and blue byte
typedef struct Pixel {
 uint8_t blue;
 uint8_t green;
 uint8_t red;
} Pixel;

//define render buffer structure
typedef struct RenderBuffer {
 HWND windowHandle; //handle to the window that the buffer belongs to
 HDC deviceContextHandle; //handle to device context (a device context is an area of memory stored by the OS that will be rendered directly to the window client area)
 Pixel * pixels; //pixel buffer to render to the screen
 BITMAPINFO bitmapInfo; //a bitmap info structure that stores necessary metadata for our renderBuffer (required to draw our data to the screen)
 int scale;
 int windowClientWidth;
 int windowClientHeight;
} RenderBuffer;

//create global variables
int running = 1; //1 when program is running, 0 when program is closed
RenderBuffer renderBuffer; //our global render buffer

//main loop function
void mainLoop();

//handle events function
void handleEvents();

//declare render functions
void drawRectangle(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue);
void drawLine(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue);
void drawRow(int x1, int x2, int y, uint8_t red, uint8_t green, uint8_t blue);
void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, uint8_t red, uint8_t green, uint8_t blue);
void drawCircle(int centreX, int centreY, int radius, uint8_t red, uint8_t green, uint8_t blue);
void render();

//declare window procedure (event handler) function
LRESULT CALLBACK windowProcedure(HWND windowHandle, UINT messageID, WPARAM wParam, LPARAM lParam);

render.c

/*
 render.c

 This file contains all of the code used for rendering shapes, sprites, etc.

 This file is included into main.c directly, so we can still use global variables.
*/


//draw rectangle
void drawRectangle(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue){
/*
  drawRectangle
  
  This function draws a rectangle to the render buffer.
  It takes seven parameters:
   -The x coordinate of the bottom left corner
   -The y coordinate of the bottom left corner
   -The x coordinate of the top right corner
   -The y coordinate of the top right corner
   -The red value of the pixel
   -The green value of the pixel
   -The blue value of the pixel
 */


//create pixel pointer
 Pixel * pixel;

//iterate from startY to endY (iterate through each row)
 int i = 0;
 int j = 0;

 for(i = startY; i <= endY; i++){
  /*
   The bottom left corner of the render buffer is (0,0).
   We need to set the pixel pointer to left side of rectangle on row i so that we
   can begin rendering the new row.
   Therefore, we want set the pixel pointer to renderBuffer.pixels + i * BUFFER_WIDTH + startX.
   renderBuffer.pixels marks the start of the buffer. We want to add a row (of width BUFFER_WIDTH) to our
   pixel pointer until we get to the row we want to draw on. Therefore, we want to move i rows
   up. We then add startX to move the pixel pointer to the left side of the rectangle on that row.
  */


  //set pixel pointer to left side of rectangle on current row
  pixel = renderBuffer.pixels + i * BUFFER_WIDTH + startX;
  
  //iterate from startX to endX and draw pixels on row
  for(j = startX; j <= endX; j++){
   //check if pixel is on screen 
   if(i >= 0 && i < BUFFER_HEIGHT && j >= 0 && j < BUFFER_WIDTH){
    //render pixel by setting red, green and blue values
    pixel->red = red;
    pixel->blue = blue;
    pixel->green = green;
   };
   
   //increment pixel
   pixel++;
  };
 };
};

//draw line
void drawLine(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue){
//calculate changes in x and y
 int dx = endX - startX;
 int dy = endY - startY;

//individual steps for x and y
 float xStep;
 float yStep;
 int totalSteps = 0;

//check if dy > dx
 if(abs(dy) > abs(dx)){
  /*
  The total number of steps is equal to the total increase in y, as we are increasing y by 1 (or -1 if dy < 0) each time.
  We are increasing y by 1 (or -1 if dy < 0) because it dy is larger than dx in terms of magnitude, so we must choose to increment y
  to avoid skipping pixels. If we were to increase x by 1, we would get increases of y larger than 1 as dy/dx > 1. This would cause
  some pixels to be left out of our line.
  */

  totalSteps = abs(dy);
 } else {
  /*
  The total number of steps is equal to the total increase in x, as we are increasing x by 1 (or -1 if dx < 0) each time.
  We are increasing x by 1 (or -1 if dx < 0) because it dx is larger than dy in terms of magnitude, so we must choose to increment x
  to avoid skipping pixels. If we were to increase y by 1, we would get increases of x larger than 1 as dx/dy > 1. This would cause
  some pixels to be left out of our line.
  */

  totalSteps = abs(dx);
 };

//absolute value of gradient is steeper than 1, so increase by 1 along the y-axis each step
 yStep = (float) dy / totalSteps; //calculate the change in y per step
 xStep = (float) dx / totalSteps; //the increase in x per unit y (i.e. the amount to increase x by each step)

//create loop counter and set to 0
 int i = 0;

//create x and y values
 float x = startX;
 float y = startY;

//create pixel pointer - this will always point to the pixel to set
 Pixel * pixel;

//iterate for all steps
 for(int i = 0; i <= totalSteps; i++){
  //check that x and y are within bounds
  if(x >= 0 && x < BUFFER_WIDTH && y >= 0 && y < BUFFER_HEIGHT){
   //plot (x,y)
   //first, set pixel pointer to current pixel (equal to renderBuffer.pixels + y * BUFFER_WIDTH + x)
   //Note that we add y * BUFFER_WIDTH, as we are adding y rows of size BUFFER_WIDTH to the start of our pixel pointer to move it to the correct y-value
   pixel = renderBuffer.pixels + ((int) y) * BUFFER_WIDTH + (int) x;
   
   //set pixel colours
   pixel->red = red;
   pixel->green = green;
   pixel->blue = blue;
  };
  
  //increase x and y
  x += xStep;
  y += yStep;
 };
};

//draw row
void drawRow(int x1, int x2, int y, uint8_t red, uint8_t green, uint8_t blue){
//create pixel pointer
 Pixel * pixel;

//create min and max x coordinates
 int minX;
 int maxX;

//check x coordinates
 if(x1 < x2){
  minX = x1;
  maxX = x2;
 } else {
  minX = x2;
  maxX = x1;
 };

//set pixel to start of row
 pixel = renderBuffer.pixels + (BUFFER_WIDTH * y) + minX;

//iterate through all pixels in the row
 int i;
 for(i = minX; i <= maxX; i++){
  //check if on screen
  if(i >= 0 && i < BUFFER_WIDTH && y >= 0 && y < BUFFER_HEIGHT){
   pixel->red = red;
   pixel->green = green;
   pixel->blue = blue;
  };
  
  //increment pixel
  pixel ++;
 };
};

//render triangle
void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, uint8_t red, uint8_t green, uint8_t blue){
//create lists of points
 int pointsX[3];
 int pointsY[3];

 pointsX[0] = x1;
 pointsX[1] = x2;
 pointsX[2] = x3;

 pointsY[0] = y1;
 pointsY[1] = y2;
 pointsY[2] = y3;

 int i = 0;

 for(i = 1; i < 3; i++){
  int j = i;
  
  while(j > 0 && pointsY[j - 1] > pointsY[j]){
   //swap
   int temp = pointsY[j];
   pointsY[j] = pointsY[j - 1];
   pointsY[j - 1] = temp;
   
   temp = pointsX[j];
   pointsX[j] = pointsX[j - 1];
   pointsX[j - 1] = temp;
   
   //decrement j
   j -= 1;
  };
 };

//draw first half of triangle
//create x coordinates for lines of triangles
 float lineX1 = pointsX[0];
 float lineX2 = pointsX[0];

//calculate the change in x per unit change in y for the lines 0->1 and 0->2
 float xStep1 = (float) (pointsX[1] - pointsX[0]) / (pointsY[1] - pointsY[0]);
 float xStep2 = (float) (pointsX[2] - pointsX[0]) / (pointsY[2] - pointsY[0]);

//iterate through all rows
 for(i = pointsY[0]; i < pointsY[1]; i++){
  //draw row between x1 and y1
  drawRow((int) lineX1, (int) lineX2, i, red, green, blue);
  
  //increase line x coordinates
  lineX1 += xStep1;
  lineX2 += xStep2;
 };

//draw second half of triangle
//recalculate xStep1, so that it is now the change in x per unit y
 xStep1 = (float) (pointsX[2] - pointsX[1]) / (pointsY[2] - pointsY[1]);

//iterate through all rows
 for(i = pointsY[1]; i < pointsY[2]; i++){
  //draw row between x1 and y1
  drawRow((int) lineX1, (int) lineX2, i, red, green, blue);
  
  //increase line x coordinates
  lineX1 += xStep1;
  lineX2 += xStep2;
 };
};

//draw circle
void drawCircle(int centreX, int centreY, int radius, uint8_t red, uint8_t green, uint8_t blue){
/*
  The circle is made up of 2 * radius rows of pixels.
  We want to iterate from the bottom-most row (centreY - radius) to
  the top-most row (centreY + radius).
 */

 int i = 0;

 for(i = centreY - radius; i <= centreY + radius; i++){
  /*
   The equation of a circle is given by the formula (x - a)^2 + (y - b)^2 = r^2,
   where (a,b) is the centre of the circle and r is the radius.
   We know that y = i, a = centreX, b = centreY and r = radius.
   This gives the equation: (x - a)^2 = k, where k is a constant
   equal to r^2 - (y - b)^2
   Therefore, x - a = + or - sqrt(k).
   Therefore the two values of x to draw a row between are:
   a + sqrt(k)
   a - sqrt(k)
  */

  
  //set k value
  double k = radius * radius - (i - centreY) * (i - centreY);
  
  //draw row from centreX - sqrt(k) to centreX + sqrt(k)
  drawRow((int) centreX - sqrt(k), (int) centreX + sqrt(k), i, red, green, blue);
 }; 
};

//render function
void render(){
//set all pixels to 0 red, 0 blue, 0 green
 memset(renderBuffer.pixels, 0, BUFFER_WIDTH * BUFFER_HEIGHT * sizeof(Pixel));

//render test circle
 drawCircle(BUFFER_WIDTH / 2, BUFFER_HEIGHT / 2, 100, 0, 255, 0);

/*
  Send renderbuffer data to client area of window.
  We can do this with the StretchDIBits function.
  This takes many parameters, which are detailed below:
 */

 StretchDIBits(
  renderBuffer.deviceContextHandle, //a handle to the device context we wish to render to
  renderBuffer.windowClientWidth / 2 - (renderBuffer.scale * BUFFER_WIDTH) / 2, //the x coordinate of the top left coordinate of our buffer on the window client area
  renderBuffer.windowClientHeight / 2 - (renderBuffer.scale * BUFFER_HEIGHT) / 2, //the y coordinate of the top left coordinate of our buffer on the window client area
  BUFFER_WIDTH * renderBuffer.scale, //the width of the buffer on the window client area
  BUFFER_HEIGHT * renderBuffer.scale, //the height of the buffer on the window client area
  0, //the starting x coordinate on the source buffer to render from (we want to render all data, so this is 0)
  0, //the starting y coordinate on the source buffer to render from (we want to render all data, so this is 0)
  BUFFER_WIDTH, //the width of the source buffer
  BUFFER_HEIGHT, //the height of the source buffer
  renderBuffer.pixels, //a pointer to the actual data we want to send
  &renderBuffer.bitmapInfo, //a pointer to the bitmap info structure for our renderBuffer
  DIB_RGB_COLORS, //use RGB colours
  SRCCOPY //copy the source into the window's buffer
 );
};



Running our Program:

If we run the program, we should see a circle appear on screen, similar to the image below:

That's all for this tutorial. In the next tutorial we will look at loading a bitmap from a file.