This article was originally posted on 13th February 2014 at Programmer’s Ranch. It has been slightly updated here. The source code for this article is available at the Gigi Labs BitBucket repository.
Greetings! 🙂
In “Handling Keyboard and Mouse Events in SDL2“, we saw how we could handle keyboard and mouse events to allow the user to interact with whatever we are displaying on the screen. In today’s article, we’ll learn how to draw individual pixels onto our window, and we’ll use mouse events to create a drawing program similar to a limited version of Microsoft Paint.
We’ll start off with some code similar to that in “Showing an Empty Window in SDL2“:
#include <SDL.h> int main(int argc, char ** argv) { bool quit = false; SDL_Event event; SDL_Init(SDL_INIT_VIDEO); SDL_Window * window = SDL_CreateWindow("SDL2 Pixel Drawing", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0); while (!quit) { SDL_WaitEvent(&event); switch (event.type) { case SDL_QUIT: quit = true; break; } } SDL_DestroyWindow(window); SDL_Quit(); return 0; }
Since we’re going to draw pixels directly rather than load an image, our course of action in this article is going to be a little different from past articles. First, we need a renderer, so we use SDL_CreateRenderer() as we have been doing all along:
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);
But then, rather than creating a texture from a surface, we’re now going to create one from scratch using SDL_CreateTexture():
SDL_Texture * texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, 640, 480);
We pass in several parameters. The first one is the renderer, and the last two are the width and height of the texture.
The second parameter, which we have set to SDL_PIXELFORMAT_ARGB8888, is the pixel format. There are many possible formats (see SDL_CreateTexture() documentation), but in this case we’re saying that each pixel is a 32-bit value, where there are 8 bits for alpha (opacity/transparency), 8 bits for red, 8 bits for green and 8 bits for blue. These four items are collectively known as channels (e.g. the alpha channel), so each channel consists of one byte and it can range between 0 and 255. The arrangement below thus represents a single pixel:
Alpha | Red | Green | Blue |
8 bits | 8 bits | 8 bits | 8 bits |
The SDL_TEXTUREACCESS_STATIC defines a method of accessing the texture. Since we’re storing our pixels in CPU memory and then copying them over to the GPU, static access is suitable. On the other hand, streaming access is used to allocate pixels in a back buffer in video memory, and that’s suitable for more complex scenarios.
Finally, we initialise a set of pixels that we’ll be copying onto the window. When we draw stuff, we’ll modify these pixels, and then they’ll be copied onto the window to reflect the update.
Uint32 * pixels = new Uint32[640 * 480];
We’ll need to clean up all the stuff we just allocated, so add the following just before the other cleanup calls at the end of the code:
delete[] pixels; SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer);
At the beginning of the while
loop, we may now add the following call:
SDL_UpdateTexture(texture, NULL, pixels, 640 * sizeof(Uint32));
In this code, we are using SDL_UpdateTexture() to copy our pixels onto the window. We pass it our texture
, a region of the texture to update (in our case NULL
means to update the entire texture), our array of pixels, and the size in bytes of a single row of pixels (called the pitch). In our case, our window has a width of 640 pixels. Therefore a single row consists of 640 4-byte pixels, hence the multiplication.
At the end of our while
loop, we may now render our texture as we have done in previous articles:
SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer);
Great. So far, we have… uh… this:
Let’s clear the background to white, so that we can draw black pixels over it. We could do that using SDL_SetRenderDrawColor() as we did in “Handling Keyboard and Mouse Events in SDL2“. But since we can now manipulate pixels directly, let’s try something. First, add this at the top:
#include <iostream>
Now, we can use the standard function memset()
to overwrite our pixels. Add this right after the declaration of pixels
:
memset(pixels, 255, 640 * 480 * sizeof(Uint32));
Good, it’s white now:
Now, let’s add some code to draw black pixels when the mouse is clicked. First, add the following flag at the beginning of main()
:
bool leftMouseButtonDown = false;
Then, add this inside the switch
statement:
case SDL_MOUSEBUTTONUP: if (event.button.button == SDL_BUTTON_LEFT) leftMouseButtonDown = false; break; case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_LEFT) leftMouseButtonDown = true; case SDL_MOUSEMOTION: if (leftMouseButtonDown) { int mouseX = event.motion.x; int mouseY = event.motion.y; pixels[mouseY * 640 + mouseX] = 0; } break;
Right, so we’re keeping track of whether the left mouse button is pressed. When it’s pressed (SDL_MOUSEBUTTONDOWN), leftMouseButtonDown
is set to true, and when it’s released (SDL_MOUSEBUTTONUP), leftMouseButtonDown
is set to false.
If the mouse is moving (SDL_MOUSEMOTION) and the left mouse button is currently down, then the pixel at its location is set to black (or zero, which is the same thing). Note that I intentionally left out a break
statement at the end of the SDL_MOUSEBUTTONDOWN
case, so that the drawing code in SDL_MOUSEMOTION
is executed along with its own.
So now we can draw our hearts out:
Sweet! That was all we needed in order to draw pixels directly onto our window.
In this article, we learned how to draw pixels directly using SDL2. This involved first creating an SDL2 texture with a specified pixel format, then allocating an array to store those pixels, and finally copying the pixels from the array to the texture. We also used a variety of mouse events to allow the user to actually draw pixels onto the window with the mouse, which is like a simplified version of Microsoft Paint.