Serebyte


Home | Tutorials | Portfolio

Win32 Software Renderer in C: Part 9 - Cutting Out a Sprite From a Bitmap/h2>

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


In this tutorial we will look at selecting a specific region from a bitmap (e.g. a specific sprite from a spritesheet) and displaying it to the screen. We will also look at scaling this sprite horizontally and vertically.




The Theory:

Before we can think about cutting out a sprite from a larger bitmap image, we must first have a suitable bitmap to cut out from. I will be using the following bitmap, named test.bmp. We will be looking to select only the red astronaut on the right and display it enlarged by a pair of variable horizontal and vertical scale factors.





Creating the Sprite Structure

We will want to store the information required to select a sprite from a bitmap inside a sprite structure, which I will define as a type called Sprite inside the main.h file. This Sprite structure will need to store a pointer to a Bitmap structure, which I will call bitmap. This will be the source bitmap that the sprite is extracted from.


The next four properties to store inside the Sprite structure are all integers, and they are the starting x and y coordinates and the ending x and y coordinates of the sprite within the bitmap. These will be named startX, startY, endX and endY respectively. Remember that the point (0, 0) is the bottom left corner of the bitmap image. We will use these four values to cut out a sprite from our bitmap source image.


After these, we want to store two more integers: the centre x and y values of the sprite, named centreX and centreY. These make up the point on the sprite that we want to draw from. So, for example, if we wanted to draw from the bottom corner, we would set centreX to 0 and centreY to 0. Then, when we call the function we will later define to draw the sprite, the coordinates that we input as arguments will be the coordinates at which the bottom left corner of the sprite is drawn. If we set centreX to half of the sprite width, and centreY to half of the sprite height, then the sprite will be centered, so will be drawn with it's centre at the x and y coordiantes inputted into the drawSprite function we will later create.


The last two properties to store are both floating point values. These are the horizontal and vertical scales, named xScale and yScale respectively. These are the scale factors that we will enlarge our sprite by when drawing. The centre of enlargement will be (centreX, centreY).


That is all we will need to store in our sprite structure.




Initialising Sprites

Now that we have created a Sprite structure, we need to initialise a sprite for us to later render to the screen. To do this, I will first declare a global variable in main.h. It will be a Sprite structure, and will be named spr_test. We should then declare a void function called "initialiseSprites" inside main.h. The function will take no parameters.


We will define our initialiseSprites function inside render.c. This function will simply just set up spr_test. We will first set the bitmap pointer of the sprite to the address of bitmap structure, using the line "spr_test.bitmap = &bmp_test;".


Next we need to set up the region of the source bitmap to draw. We do this by setting the startX, endX, startY and endY properties. We have a 128x64 px spritesheet, where each sprite is 64x64. We want to select the second sprite. This sprite starts at x = 64, y = 0, and ends at x = 128, y = 64. Therefore, we should set startX to 64 using the line "spr_test.startX = 64;". We should set endX to 128 using the line "spr_test.endX = 128;". We should set startY to 0 with the line "spr_test.startY = 0;", and we should set endY to 64 with the line "spr_test.endY = 64;".


After this, we should set the centre coordinates. Note that these are relative to the size of the sprite, not the source coordinates. I want the sprite to be centred, and, since the sprite we have cut out is 64x64 px, I will set the centre to the coordinates (32, 32). This can be done with the lines: "spr_test.centreX = 32;" and "spr_test.centreY = 32;".


The last two properties to set are the xScale and yScale properties. I will set these both to 1.0 for now, but we can look at varying these later. We can do this using the lines: "spr_test.xScale = 1.0;" and "spr_test.yScale = 1.0;".




Overview of our Sprite Drawing Algorithm

In this section we will briefly discuss how we can cut out a specific sprite from a bitmap and draw it scaled up or down. We should start by finding the width and height of the region of the bitmap we want to display. The width is equal to abs(sprite->endX - sprite->startX) and the height is equal to abs(sprite->endY - startY).


Now we need to imagine that this rectangle is at coordinate (0,0) - its bottom left coordinate is at the origin.



After this, we need to translate these points leftwards and downwards by the sprite's centreX and centreY values respectively, so that the vertices are now centred around the origin. We can accomplish this by subtracting centreX from each of the points' x coordinates, and centreY from each of the point's y coordinates, as shown below.



Now that our sprite's vertices are centred around its origin, we can multiply each of the coordinates by the horizontal and vertical scale. For demonstration purposes, I will let xScale = 2.0 and let yScale = 3.0, as you can see below:



Now we need to find the minimum and maximum x and y coordinates. This may seem unusual, but imagine if our horizontal scale was a negative number, then the points would be flipped around and the leftmost point would become the rightmost point. Therefore, we need to compare the points with each other to find the minimum and maximum bounds (which I will call minX, maxX, minY and maxY).



Now we want to add the x and y coordinates that we wish to draw the sprite at to each vertex. This will translate the center of the sprite (currently at point (0, 0)) to the x and y coordinates we specified, meaning that the sprite will be centred around those points.



Now we want to render the actual pixels. To do this, we need to calculate the x and y coordinates of pixel on the original bitmap. To do this, we need to reverse the original transformation. We most recently translated our sprite by the x and y coordinates, so we need to subtract these x and y coordinates. Then we need to divide by the x and y scales, so that the resulting sprite is of the same size as the bitmap. Then we need to add on the centre coordinates, so that the resulting bitmap has its bottom left corner at the point (0, 0). To finish, we simply need to add on the x and y coordinates we want to start from on the bitmap source image, so that we draw the specfic section we cut out. Don't worry if this is confusing for now, as we will look at it in more depth when we look at writing the code.




The Sprite Drawing Algorithm in Code

Now we've looked at the algorithm we will use, we can start to implement it. We should create a drawSprite function. It should be a void function, and should take three parameters. The first two should be integers, and should be the x and y coordinates to draw the sprite at. The last should be a pointer to the sprite structure. We can do this with the line "void drawSprite(int x, int y, Sprite * sprite){".


Next, we need to find the width and height of the sprite we have chosen to cut out. We can do this by subtracting the start coordinates from the end coordinates. This can be done with the lines "int width = abs(sprite->endX - sprite->startX);" and "int height = abs(sprite->endY - sprite->startY);".


After this, we need to create an array of vertices. These will represent the corners of the sprite's bounding box on the screen. The vertices array will be a 2d array, with four lots of sub-arrays storing two integers, the x and y coordinates of the vertex in that order.


The vertices will be initially stored in the order: bottom left, top left, top right, bottom right. We want the sprite to be centred around its centreX and centreY coordinates (specified in the sprite structure). Therefore, we should set the bottom left coordinate to (-centreX, -centreY). This can be done with the lines "vertices[0][0] = -sprite->centreX;" and "vertices[0][1] = -sprite->centreY;". We want to set the top left to the same x coordinate as the bottom left and we want to set it to the y coordinate of the bottom eft vertex added to the height of the sprite. This can be done with the lines "vertices[1][0] = vertices[0][0];" and "vertices[1][1] = vertices[0][1] + height;". Then, we can set the coordinates of the top right vertex. The x coordinate of the top right vertex should be equal to the x coordinate of the bottom left vertex added to the width of the sprite. The y coordinate should be equal to the y coordinate of the top left vertex. This can be accomplished with the lines "vertices[2][0] = vertices[0][0] + width;" and "vertices[2][1] = vertices[1][1];". Finally, the bottom right vertex should have the x coordinate of the top right vertex and the y coordinate of the bottom left vertex. This can be done with the lines "vertices[3][0] = vertices[2][0];" and "vertices[3][1] = vertices[0][1];".


Now we need to iterate through each of the vertices and multiply them by the correct scale factor in each direction. We should first create a loop counter, which I will call i. This can be done with the line "int i;". Now we need to iterate through each of the four vertices. This can be done with the line "for(i = 0; i < 4; i++){". Now we need to mutliply the x coordinate of each vertex by the sprite's xScale property. This can be done with the line "vertices[i][0] *= sprite->xScale;". We also need to multiply the y coordinate of each vertex by the sprite's yScale property. This can be done with the line "vertices[i][1] *= sprite->yScale;".


After applying horizontal and vertical scaling, we need to find the minimum and maximum x and y coordinates for the vertices. We should first create four integers, minX, maxX, minY and maxY. We should initialise these to the x an y coordinates of the bottom left (or any other) vertex. This is so that they are not assigned a random value, as we will need to compare them with the other vertices to find the minimum and maximum x an y coordinates. We can do this with the following lines: "int minX = vertices[0][0];", "int maxX = vertices[0][0];", "int minY = vertices[0][1];" and "int maxY = vertices[0][1];".


Now that we have our minimum and maximum coordinate variables, we atually have to find the minimum and maximum coordinates. We will need to iterate through each of the variables, which we can do with the line "for(i = 0; i < 4; i++){". Inide this loop, we need to check if the x coordinate of vertex i is greater than the maximum x. If so, then we should change the maxX variable to the x coordiante of vertex i. This can be done using the if statement "if(vertices[i][0] > maxX){" and writing the following line inside of it: "maxX = vertices[i][0];". We also want to check if the x coordinate of vertex i is smaller than our current minimum x value, and update said miimum x value if so. This can be done with the if statement: "if(vertices[i][0] < minX){", with the line "minX = vertices[i][0];" inside of it. We then want to do the same for the minY and maxY values. For maxY, we can use the if statement "if(vertices[i][1] > maxY){" with the line "maxY = vertices[i][1];" inside. For minY, we can use the if statement "if(vertices[i][1] < minY){" with the line "minY = vertices[i][1];" inside.


Now we need to translate the minimum an maximum x and y coordinates, so that they are centred around the x and y coordinates we wish to draw to. We can do this with the lines "minX += x;", "maxX += x;", "minY += y;" and "maxY += y;".


As we now have the correct minimum and maximum x and y coordinates, we can start to fill in our sprite's bounding box on screen with pixels. To do this, we will first need to create a pixel pointer to point to the pixel we will draw to. We can do this with the line: "Pixel * pixel;". We must also create another loop counter, which I will call j. We can do this with the line: "int j;".


With these variables set up, we can loop through all of the rows of the sprite. We can do this with the line "for(i = minY; i < maxY; i++){". Inside this loop, we first want to set the pixel pointer to the start of the row. We can do this with the line "pixel = renderBuffer.pixels + i * BUFFER_WIDTH + minX;". We then want to iterate through all the pixels on this row. We can do this with the line: "for(j = minX; j < maxX; j++){". Next, we should check if the pixel is on screen. We can do this with the line: "if(j >= 0 && j < BUFFER_WIDTH && i >= 0 && i < BUFFER_HEIGHT){". If the pixel is on screen, we want to retrieve the coordinates of the corresponding pixel on the original bitmap image. This can be done with the lines "int pixelX = (int) ((j - x) / sprite->xScale) + sprite->centreX + sprite->startX;" and "int pixelY = (int) ((i - y) / sprite->yScale) + sprite->centreY + sprite->startY;". Now we need to check if these pixel coordinates are within the specified region of our sprite. This can be done with the line "if(pixelX >= sprite->startX && pixelX < sprite->endX && pixelY >= sprite->startY && pixelY < sprite->endY){". If the pixel coordinates are in the valid range, we need to get the actual pixel on the source bitmap. This can be done with the following line: "Pixel * srcPixel = sprite->bitmap->pixels + (pixelY * sprite->bitmap->infoHeader.biWidth) + pixelX;". Next, we need to check that the pixel is not transparent. Note that I have defined three constants in main.h. These are TRANSPARENT_RED, TRANSPARENT_GREEN and TRANSPARENT_BLUE. These are the red, green and blue values of the colour I will be treating as transparent. If a pixel on the source bitmap has these RGB values, it should be treated as transparent and should not be drawn. I have defined TRANSPARENT_RED as 163, TRANSPARENT_GREEN as 73 and TRANSPARENT_BLUE as 164. We can check that the pixel is not transparent with the line: "if(srcPixel->red != TRANSPARENT_RED && srcPixel->green != TRANSPARENT_GREEN && srcPixel->blue != TRANSPARENT_BLUE){". If the source pixel is not transparent, then we want to draw it to the address given by the pixel pointer. We can do this with the lines: "pixel->red = srcPixel->red;", "pixel->green = srcPixel->green;" and "pixel->blue = srcPixel->blue;". Before the end of the pixel row loop (the j loop), we want to increment the pixel pointer to the next pixel in the row. This can be done with the line: "pixel ++;".


That's it for our drawSprite function. We need to call it in the render function. I have done this with the line: "drawSprite(BUFFER_WIDTH / 2, BUFFER_HEIGHT / 2, &spr_test);".




Optional - Scaling the Sprite in Real Time

Now that we have a working drawSprite function, we can demonstrate its abilities by modifying its scale in real time.


We can make its horizontal scale change when the left and right arrow keys are pressed. To do this, we will need to use the GetAsyncKeyState function. This takes one argument - the key code of the key to check for. We can check if the left arrow key was pressed using the line: "if(GetAsyncKeyState(VK_LEFT)){". Inside this, we should add the line: "spr_test.xScale -= 0.1;". This will decrease the sprite's horizontal scale by 0.1 when the left arrow key is pressed. For the right arrow key, we should use the if statement "if(GetAsyncKeyState(VK_RIGHT)){". Inside this if statement should be the line: "spr_test.xScale += 0.1;".


The process of varying the vertical scale is similar to the horizontal scale. For the up arrow key, we can use the if statement: "if(GetAsyncKeyState(VK_UP)){", with the line "spr_test.yScale += 0.1;" inside. For the down arrow key, we should use the if statement: "if(GetAsyncKeyState(VK_DOWN)){" with the line "spr_test.yScale -= 0.1;" inside.




The Code:

The code for this section is shown below. I have included all three of our files: main.h, render.c and main.c. I will be compiling the code with the MinGW compiler, using the comman "gcc main.c -lgdi32 -o app". You should be able to use any compiler, but be sure to link the gdi32 library.


main.h

/*
 Software Renderer in C - Part 9

 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

//define transparent colour constants
#define TRANSPARENT_RED 163
#define TRANSPARENT_GREEN 73
#define TRANSPARENT_BLUE 164 

//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;

//bitmap structure
typedef struct Bitmap {
 BITMAPFILEHEADER fileHeader;
 BITMAPINFOHEADER infoHeader;
 Pixel * pixels;
} Bitmap;

//sprite structure
typedef struct Sprite {
 Bitmap * bitmap;
 int startX;
 int startY;
 int endX;
 int endY;
 int centreX;
 int centreY;
 float xScale;
 float yScale;
} Sprite;

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

//create bitmaps
Bitmap bmp_test;

//create sprites
Sprite spr_test;

//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);
int loadBMPFile(char filePath[], Bitmap * bitmap);
void loadAllBitmaps();
void drawBitmap(int x, int y, Bitmap * bitmap);
void initialiseSprites();
void drawSprite(int x, int y, Sprite * sprite);
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);
 }; 
};

//load BMP file
int loadBMPFile(char filePath[], Bitmap * bitmap){
//attempt to open file
 FILE * file = fopen(filePath, "rb");

//check if file opened correctly
 if(!file){
  //return unsuccessful
  return 0;
 };

//get file size
 fseek(file, 0, SEEK_END);
 int fileSize = ftell(file);
 rewind(file);

//check if file is too small to be a bitmap file
 if(fileSize < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)){
  fclose(file);
  
  //return unsuccessful
  return 0;
 };

//read bitmap file header
 fread(&(bitmap->fileHeader), sizeof(BITMAPFILEHEADER), 1, file);

//read bitmap info header
 fread(&(bitmap->infoHeader), sizeof(BITMAPINFOHEADER), 1, file);

//allocate memory for pixels
//the size of the bitmap can be found using bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight * sizeof(Pixel)
 bitmap->pixels = (Pixel *) malloc(bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight * sizeof(Pixel));

//copy bitmap data into pixel buffer
 fread(bitmap->pixels, sizeof(Pixel), bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight, file);

//close file
 fclose(file);

//return successful
 return 1;
};

//load all bitmaps
void loadAllBitmaps(){
//load test bitmap
 loadBMPFile("test.bmp", &bmp_test);
};

//draw BMP file
void drawBitmap(int x, int y, Bitmap * bitmap){
//create pixel pointer
 Pixel * pixel;

//create loop counters
 int i = 0;
 int j = 0;

//iterate through all rows of bitmap
 for(i = 0; i < bitmap->infoHeader.biHeight; i++){
  //set pixel pointer to start of row y + i
  pixel = renderBuffer.pixels + (y + i) * BUFFER_WIDTH + x;
  
  //iterate through x coordinates
  for(j = 0; j < bitmap->infoHeader.biWidth; j++){
   //check that pixel is on screen
   if(j >= 0 && j < BUFFER_WIDTH && y + i >= 0 && y + i < BUFFER_HEIGHT){
    //get pointer to source pixel
    Pixel * srcPixel = bitmap->pixels + i * bitmap->infoHeader.biWidth + j;
   
    //check that pixel is not the colour we will be using as transparent
    if(srcPixel->red != TRANSPARENT_RED && srcPixel->green != TRANSPARENT_GREEN && srcPixel->blue != TRANSPARENT_BLUE){ 
     //pixel is not colour used to indicate transparency
     
     //set colour of pixel on renderBuffer pointed to by pixel pointer
     pixel->red = srcPixel->red;
     pixel->green = srcPixel->green;
     pixel->blue = srcPixel->blue;
    };
    
    //increment pixel pointer
    pixel++;
   };
  };
 };
};

//initialiseSprites
void initialiseSprites(){
//test sprite
 spr_test.bitmap = &bmp_test;
 spr_test.startX = 64;
 spr_test.endX = 128;
 spr_test.startY = 0;
 spr_test.endY = 64;
 spr_test.centreX = 32;
 spr_test.centreY = 32;
 spr_test.xScale = 1.0;
 spr_test.yScale = 1.0;
};

//draw sprite
void drawSprite(int x, int y, Sprite * sprite){
//get width and height
 int width = abs(sprite->endX - sprite->startX);
 int height = abs(sprite->endY - sprite->startY);

//create vertex array
//4 vertices in the format {x, y}
 float vertices[4][2];

//bottom left
 vertices[0][0] = -sprite->centreX;
 vertices[0][1] = -sprite->centreY;

//top left
 vertices[1][0] = vertices[0][0];
 vertices[1][1] = vertices[0][1] + height;

//top right
 vertices[2][0] = vertices[0][0] + width;
 vertices[2][1] = vertices[1][1];

//bottom right
 vertices[3][0] = vertices[2][0];
 vertices[3][1] = vertices[0][1];

//multiply each vertex by xScale and yScale
 int i;

 for(i = 0; i < 4; i++){
  vertices[i][0] *= sprite->xScale;
  vertices[i][1] *= sprite->yScale;
 };

//create minimum and maximum coordinates
 int minX = vertices[0][0];
 int maxX = vertices[0][0];
 int minY = vertices[0][1];
 int maxY = vertices[0][1];

//calculate minimum and maximum coordinates
 for(i = 0; i < 4; i++){
  if(vertices[i][0] > maxX){
   maxX = vertices[i][0];
  };
  
  if(vertices[i][0] < minX){
   minX = vertices[i][0];
  };
  
  if(vertices[i][1] > maxY){
   maxY = vertices[i][1];
  };
  
  if(vertices[i][1] < minY){
   minY = vertices[i][1];
  };
 };

//add x and y coordinates to bounding coordiantes to translate sprite to screen
 minX += x;
 maxX += x;
 minY += y;
 maxY += y;

//create pixel pointer
 Pixel * pixel;

//create second loop counter
 int j;

//iterate from min y to max y
 for(i = minY; i < maxY; i++){
  //set pixel pointer to start of row
  pixel = renderBuffer.pixels + i * BUFFER_WIDTH + minX;
  
  //iterate through x coordiantes
  for(j = minX; j < maxX; j++){
   //check the coordinates (j, i) are on screen
   if(j >= 0 && j < BUFFER_WIDTH && i >= 0 && i < BUFFER_HEIGHT){
    //get coordinates of src pixel
    int pixelX = (int) ((j - x) / sprite->xScale) + sprite->centreX + sprite->startX;
    int pixelY = (int) ((i - y) / sprite->yScale) + sprite->centreY + sprite->startY;
    
    //check that pixelX and pixelY are within sprite bounds
    if(pixelX >= sprite->startX && pixelX < sprite->endX && pixelY >= sprite->startY && pixelY < sprite->endY){
     //retrieve source pixel
     Pixel * srcPixel = sprite->bitmap->pixels + (pixelY * sprite->bitmap->infoHeader.biWidth) + pixelX;
     
     //check that pixel is not transparent
     if(srcPixel->red != TRANSPARENT_RED && srcPixel->green != TRANSPARENT_GREEN && srcPixel->blue != TRANSPARENT_BLUE){
      //draw pixel
      pixel->red = srcPixel->red;
      pixel->green = srcPixel->green;
      pixel->blue = srcPixel->blue;
     };
    };
   };
   
   //increment pixel pointer
   pixel ++;
  };
 };
};

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

//adjust sprite scale in real time
 if(GetAsyncKeyState(VK_LEFT)){
  spr_test.xScale -= 0.1;
 };

 if(GetAsyncKeyState(VK_RIGHT)){
  spr_test.xScale += 0.1;
 };

 if(GetAsyncKeyState(VK_UP)){
  spr_test.yScale += 0.1;
 };

 if(GetAsyncKeyState(VK_DOWN)){
  spr_test.yScale -= 0.1;
 };

//render bitmap
 drawSprite(BUFFER_WIDTH / 2, BUFFER_HEIGHT / 2, &spr_test);

/*
  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
 );
};

main.c

//Software Renderer in C Part 9 - Cutting Out a Sprite From a Bitmap

//include main header
#include "main.h"

//include render file
#include "render.c"

/*
 Entry point
 -In Win32, the entry point is WinMain, not main

 -Like main, WinMain returns an int

 -The calling convention of the function is called WINAPI (you do not need to know what this means as it isn't used elsewhere)

 -The calling convention of a function determines how it stores data on the program's call stack

 -Note that all parameters are passed into the function by the OS

 -On Windows, a handle is a numerical identifier used to identify objects and structures controlled by the OS. Handles pop up quite frequently.

 -A HINSTANCE is a handle to an application instance - the first parameter is the numerical identifier for a specific instance of
 the program, given to it by the OS at runtime

 -The second parameter, hPrevInstace is a feature that is now useless. It served a purpose on 16-bit Windows versions, but now
 must simply be included for compatiblity's sake (each version of Windows mostly aims to be compatible with the previous versions)

 -The third parameter is the command line arguments as a single string. LPSTR stands for long pointer to string, and is simply just
 a redefinition of a char * on modern systems.

 -Note that long pointers are also a redundant feature from 16-bit windows, when pointers could either be 16-bit or 32-bit. Nowadays,
 a long pointer and a pointer are the same on Windows.

 -The fourth parameter is an integer used to specify how the program should be displayed. Again, this is given to us by the OS - we
 do not set this, as we do not set any of the parameters of WinMain.
*/

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstace, LPSTR cmd, int nCmdShow){
//create window class structure and initialise to 0
 WNDCLASSEX windowClass = {0};

//set window class properties
//set size of structure
//cbSize is short for "count bytes size" - it is the size of the structure in bytes
 windowClass.cbSize = sizeof(WNDCLASSEX);

//set class name - this is a string used by the operating system to identify our window class
//lpszClassName is short for "long pointer to string class name"
 windowClass.lpszClassName = "MAIN_WINDOW_CLASS";

//set window style - this is the default behaviour of the windows. We are telling the window to redraw when either horizontally or vertically resized (or both)
 windowClass.style = CS_HREDRAW | CS_VREDRAW;

//set window procedure - this is the event handler function, and is declared at the top of the file
//lpfnWndProc is short for "long pointer to function window procedure", and is used to identify the window procedure (event handler function) for this window class
 windowClass.lpfnWndProc = windowProcedure;
  
/*
 set background colour - we set a handle to a colour we create.
 A brush is just a drawing style stored on the OS. Here, we are
 asking the OS to create a solid colour "brush" and return a
 handle to it so that we can use it.
 */

 windowClass.hbrBackground = (HBRUSH) CreateSolidBrush(RGB(100, 100, 100));

//send window class to operating system to be registered
 RegisterClassEx(&windowClass);

//create window size structure
//note that here, we specify the size of the client area, i.e. the area we can draw to
 RECT windowSize = {0, 0, BUFFER_WIDTH, BUFFER_HEIGHT};

//get window frame size
//AdjustWindowRectEx takes a client area, and adds the size of the window frame to it
 AdjustWindowRectEx(
  &windowSize, //pointer to client area rectangle to be modified
  WS_OVERLAPPEDWINDOW, //window style(behaviour) - WS_OVERLAPPEDWINDOW is the default
  0, //no menu bar (this is a boolean value)
  WS_EX_OVERLAPPEDWINDOW //extended style (behaviour) - WS_EX_OVERLAPPEDWINDOW is the default
 );

//windowSize now contains the size of the window (including the window frame)

//create window from window class
//a HWND is a window handle
 renderBuffer.windowHandle = CreateWindowEx(
  WS_EX_OVERLAPPEDWINDOW, //default extended window style - a basic window that may be overlapped
  windowClass.lpszClassName, //the class name of the window class this window will use
  "My First Window!!!", //the title text of the window
  WS_OVERLAPPEDWINDOW, //default window style - a basic window that may be overlapped
  CW_USEDEFAULT, CW_USEDEFAULT, //the starting x and y coordinates of the window - use default
  windowSize.right - windowSize.left, //width of window frame
  windowSize.bottom - windowSize.top, //height of window frame
  NULL, //no parent window
  NULL, //no menu bar - we will not need one for our window
  hInstance, //application instance that window belongs to
  NULL //no LPARAM (an LPARAM is an additional piece of data in Win32)
 );

//check if window created successfully
 if(!renderBuffer.windowHandle){
  //window failed to create - display an alert
  MessageBox(
   NULL, //no parent window - this should display on its own
   "Error - could not create window", //inner text
   "Window Error", //the title text of the message box
   MB_OK | MB_ICONERROR //flags - message box has an OK button, message box has an error icon
  );
  
  return -1;
 };

//allocate memory for render buffer pixels
 renderBuffer.pixels = (Pixel *) malloc(BUFFER_WIDTH * BUFFER_HEIGHT * sizeof(Pixel));

//check if memory could not be allocated - terminate program if so
 if(!renderBuffer.pixels){
  //failed to allocate memory for render buffer
  MessageBox(
   NULL, //no parent window - this should display on its own
   "Error - could not allocate memory for render buffer", //inner text
   "Render Buffer Error", //the title text of the message box
   MB_OK | MB_ICONERROR //flags - message box has an OK button, message box has an error icon
  );
  
  return -1;
 };

/*
  Get window device context and set renderBuffer context to it.
  The window's device context is the region of memory that is rendered to it.
  This is managed by the OS, so we need to retrieve and handle to it. We do this
  so that we can use the StretchDIBits function to send our buffer data to said region
  of memory, as we cannot simply write to the region ourselves.
 */

 renderBuffer.deviceContextHandle = GetDC(renderBuffer.windowHandle);

//set all bitmap info properties to 0
 memset(&renderBuffer.bitmapInfo.bmiHeader, 0 , sizeof(BITMAPINFOHEADER));

/*
  Fill out bitmapinfo structure for renderBuffer.
  This is necessary so that when we wish to send our data to the window's device context
  the OS knows how to interpret our data.
  
  Remeber that our buffer is really just a large bitmap.
  
  The BITMAPINFO structure is made up of two parts: the bitmap info header and the colour
  table. The bitmap info header contains all of the metadata we must set before we can
  draw our buffer data to the screen. We do not need to worry about the colour table, as
  it is mostly only used for defining colour codes for 16-bit or 8-bit colour palettes.
 */

 renderBuffer.bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); //the size of the BITMAPINFOHEADER structure in bytes
 renderBuffer.bitmapInfo.bmiHeader.biWidth = BUFFER_WIDTH; //the width of our buffer in pixels
 renderBuffer.bitmapInfo.bmiHeader.biHeight = BUFFER_HEIGHT; //the height of our buffer in pixels
 renderBuffer.bitmapInfo.bmiHeader.biPlanes = 1; //this is the number of planes to render - this has to be set to 1
 renderBuffer.bitmapInfo.bmiHeader.biBitCount = 24; //we are using 24-bit colours
 renderBuffer.bitmapInfo.bmiHeader.biCompression = BI_RGB; //uncompressed - we are simply using 3 bytes for the RGB values of each pixel

//there are other properties of the BITMAPINFOHEADER structure, but we can set these all to 0 as they are not relevant to us


//show window if window created successfully
//this function takes the window handle and an integer indicating how it should be shown
//this integer is the nCmdShow parameter passed by the OS upon starting our program
 ShowWindow(renderBuffer.windowHandle, nCmdShow);

//load all bitmaps
 loadAllBitmaps();

//initialise sprites
 initialiseSprites();

//start program loop
 mainLoop();

 return 0;
};


/*
 mainLoop
 -The job of this function is to iterate until the "running" global variable is set to 0
 -Each iteration, it should handle any events and render the current data to the screen
*/

void mainLoop(){
//iterate while window is running
 while(running){
  //handle events for global render buffer
  handleEvents();

  //render to global render buffer
  render();
 };
};

/*
 handleEvents
 -The job of this function is to handle all events for the window that the global render buffer belongs to
 -All messages on the message queue are processed by using the PeekMessage function in a while loop
*/

void handleEvents(){
//create message structure to store incoming event
//remember "message" is just the Windows name for event
 MSG message;

//start message loop
/*
  The PeekMessage function takes five parameters
  -The first is a pointer to a message structure to fill out
  -The second is the window to get events for. This can be
  set to NULL to detect all events that occur on the system.
  -The third is the minimum message ID to retrieve
  -The fourth is the maximum message ID to retreive
  -Note that by setting both the minimum and maximum
  message IDs to 0, the PeekMessage call will detect all
  messages and thus override the minimum-maximum range
  setting
  -The fifth is for flags. We want to add the PM_REMOVE
  flag, which will remove our message from the system's
  message queue once processed
  
  Note that PeekMessage will not block, so if no messages are
  present on the system's message queue, the loop simply just ends.
 */


//get all messages
 while(PeekMessage(&message, renderBuffer.windowHandle, 0, 0, PM_REMOVE)){
  /*
   Translate the message.
   This involves converting key codes into characters for text-based events
   we shouldn't need it, but it is a good practice to include it anyway,
   as if we decide to use a message requiring translation later and forget
   this line, the resulting errors may be hard to debug
  */

  TranslateMessage(&message);
  
  //dispatch message - send message and its corresponding window to the relevant window procedure
  DispatchMessage(&message);
 };
};

/*
 Window procedure - this is the window procedure, the event handler function.
 It takes four parameters:
 -The handle (numerical ID) to the window calling the function
 -The message ID (indicating what type of event has occurred)
 -wParam, a general purpose parameter used to store event-dependent data
 -lParam, another general purpose parameter

 Note that it is common to see WPARAM and LPARAM pop up in many Win32 functions.
 Remember that these are just general purpose parameters, and typically
 contain integer values or pointers

 Note that an LRESULT is just a "long long int" type.
*/

LRESULT CALLBACK windowProcedure(HWND windowHandle, UINT messageID, WPARAM wParam, LPARAM lParam){
//check event type/ID
 switch(messageID){
  //window is closed (i.e. X button is clicked)
  case WM_CLOSE:{
   //send quit message to close window
   PostQuitMessage(0);
   
   //set running to 0 to close program
   running = 0;
   return 0;
  };
  
  //window is resized
  case WM_SIZE:{
   //get window client area size
   RECT windowClientRect;
   GetClientRect(windowHandle, &windowClientRect);
   
   //set window width and height in renderBuffer structure
   renderBuffer.windowClientWidth = windowClientRect.right - windowClientRect.left;
   renderBuffer.windowClientHeight = windowClientRect.bottom - windowClientRect.top;
   
   //calculate the maximum scale that the renderBuffer can be increased by so that it fits in the window
   int i = 1;
   
   while(BUFFER_WIDTH * i <= renderBuffer.windowClientWidth && BUFFER_HEIGHT * i <= renderBuffer.windowClientHeight){
    //increment i
    i += 1;
   };
   
   //subtract 1 from i, since i is equal to the first integer scale where the buffer extends out of the bounds of the window and we want the largest scale before this
   i -= 1;
   
   //set buffer scale to i
   renderBuffer.scale = i;
   
   break;
  };
 };

/*
  There are thousands of different types of message
  on Windows. We cannot check for them all, so we
  can simply call the default window procedure on all
  our parameters for the vast majority of events
 */


//return default window procedure
 return DefWindowProc(windowHandle, messageID, wParam, lParam);
};



Running Our Program:

When we initially run our program, we should see something similar to this:


By holding the arrow keys, we should be able to adjust the scale of our image. Below are some examples from my program:





That's it for this tutorial. In the next tutorial, we will look at displaying text to the screen.