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
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#define BUFFER_WIDTH 640
#define BUFFER_HEIGHT 480
typedef struct Pixel {
uint8_t blue;
uint8_t green;
uint8_t red;
} Pixel;
typedef struct RenderBuffer {
HWND windowHandle;
HDC deviceContextHandle;
Pixel * pixels;
BITMAPINFO bitmapInfo;
int scale;
int windowClientWidth;
int windowClientHeight;
} RenderBuffer;
int running = 1;
RenderBuffer renderBuffer;
void mainLoop();
void handleEvents();
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();
LRESULT CALLBACK windowProcedure(HWND windowHandle, UINT messageID, WPARAM wParam, LPARAM lParam);
render.c
void drawRectangle(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue){
Pixel * pixel;
int i = 0;
int j = 0;
for(i = startY; i <= endY; i++){
pixel = renderBuffer.pixels + i * BUFFER_WIDTH + startX;
for(j = startX; j <= endX; j++){
if(i >= 0 && i < BUFFER_HEIGHT && j >= 0 && j < BUFFER_WIDTH){
pixel->red = red;
pixel->blue = blue;
pixel->green = green;
};
pixel++;
};
};
};
void drawLine(int startX, int startY, int endX, int endY, uint8_t red, uint8_t green, uint8_t blue){
int dx = endX - startX;
int dy = endY - startY;
float xStep;
float yStep;
int totalSteps = 0;
if(abs(dy) > abs(dx)){
totalSteps = abs(dy);
} else {
totalSteps = abs(dx);
};
yStep = (float) dy / totalSteps;
xStep = (float) dx / totalSteps;
int i = 0;
float x = startX;
float y = startY;
Pixel * pixel;
for(int i = 0; i <= totalSteps; i++){
if(x >= 0 && x < BUFFER_WIDTH && y >= 0 && y < BUFFER_HEIGHT){
pixel = renderBuffer.pixels + ((int) y) * BUFFER_WIDTH + (int) x;
pixel->red = red;
pixel->green = green;
pixel->blue = blue;
};
x += xStep;
y += yStep;
};
};
void drawRow(int x1, int x2, int y, uint8_t red, uint8_t green, uint8_t blue){
Pixel * pixel;
int minX;
int maxX;
if(x1 < x2){
minX = x1;
maxX = x2;
} else {
minX = x2;
maxX = x1;
};
pixel = renderBuffer.pixels + (BUFFER_WIDTH * y) + minX;
int i;
for(i = minX; i <= maxX; i++){
if(i >= 0 && i < BUFFER_WIDTH && y >= 0 && y < BUFFER_HEIGHT){
pixel->red = red;
pixel->green = green;
pixel->blue = blue;
};
pixel ++;
};
};
void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, uint8_t red, uint8_t green, uint8_t blue){
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]){
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;
j -= 1;
};
};
float lineX1 = pointsX[0];
float lineX2 = pointsX[0];
float xStep1 = (float) (pointsX[1] - pointsX[0]) / (pointsY[1] - pointsY[0]);
float xStep2 = (float) (pointsX[2] - pointsX[0]) / (pointsY[2] - pointsY[0]);
for(i = pointsY[0]; i < pointsY[1]; i++){
drawRow((int) lineX1, (int) lineX2, i, red, green, blue);
lineX1 += xStep1;
lineX2 += xStep2;
};
xStep1 = (float) (pointsX[2] - pointsX[1]) / (pointsY[2] - pointsY[1]);
for(i = pointsY[1]; i < pointsY[2]; i++){
drawRow((int) lineX1, (int) lineX2, i, red, green, blue);
lineX1 += xStep1;
lineX2 += xStep2;
};
};
void drawCircle(int centreX, int centreY, int radius, uint8_t red, uint8_t green, uint8_t blue){
int i = 0;
for(i = centreY - radius; i <= centreY + radius; i++){
double k = radius * radius - (i - centreY) * (i - centreY);
drawRow((int) centreX - sqrt(k), (int) centreX + sqrt(k), i, red, green, blue);
};
};
void render(){
memset(renderBuffer.pixels, 0, BUFFER_WIDTH * BUFFER_HEIGHT * sizeof(Pixel));
drawCircle(BUFFER_WIDTH / 2, BUFFER_HEIGHT / 2, 100, 0, 255, 0);
StretchDIBits(
renderBuffer.deviceContextHandle,
renderBuffer.windowClientWidth / 2 - (renderBuffer.scale * BUFFER_WIDTH) / 2,
renderBuffer.windowClientHeight / 2 - (renderBuffer.scale * BUFFER_HEIGHT) / 2,
BUFFER_WIDTH * renderBuffer.scale,
BUFFER_HEIGHT * renderBuffer.scale,
0,
0,
BUFFER_WIDTH,
BUFFER_HEIGHT,
renderBuffer.pixels,
&renderBuffer.bitmapInfo,
DIB_RGB_COLORS,
SRCCOPY
);
};
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.