SDL2 Pixel Drawing

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:

sdl2-pixel-drawing-grey

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:

sdl2-pixel-drawing-white

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:

sdl2-pixel-drawing-final

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.

40 thoughts on “SDL2 Pixel Drawing”

  1. Note: You can draw directly to the renderer.
    if (leftMouseButtonDown)
    SDL_RenderDrawPoint(Renderer,event.motion.x,event.motion.y);

    You don’t need to set up a texture (array, memset, rendercopy).

  2. @Carsten Holtkamp
    Note that SDL_RenderDrawPoint is WAY slower. Filling a 1080p screen with pixels takes about 650ms to 635ms for me, while drawing to a texture then copying to renderer takes about 18ms to 6ms to refresh the entire screen, pixel by pixel.

    @Gigi
    Thanks so much for posting this! I’ve been searching for days trying to figure out how to draw to a texture (or any way to draw pixels quickly). Every example I’ve found uses very confusing methods of defining the pixel data, but this was very easy to follow, simple, and well explained! I had to figure out how to change some things because I am using C (like malloc instead of new), I am finally drawing pixels at a reasonable speed! Do you have a tutorial on texture streaming?

    1. Thanks for your kind words! I am not really sure what “texture streaming” actually means, and if it’s not among my list of SDL2 Tutorials, then no.

      However, I do remember that there’s an alternative to SDL_TEXTUREACCESS_STATIC which is SDL_TEXTUREACCESS_STREAMING, so you might want to look into that. See SDL_TextureAccess in the SDL docs.

      1. You are right. They even link to this tutorial from the libsdl website: http://slouken.blogspot.com/2011/02/streaming-textures-with-sdl-13.html

        But it doesn’t work for me. He’s not using pointers which causes C unknown size errors and then in the code example link at the bottom he uses completely different code with pointers, but then does not use a surface like on the tutorial page, but appears to be manipulated pixels on a void pointer, but doesn’t explain how. All very confusing! All of the tutorials I’ve found so far have similar issues. You can see why I was so glad to find your tutorial. I hope you make more!

        1. That blog post is using SDL 1.x, while mine uses the more recent SDL2. They are very different!

          By the way, if I’m not mistaken, slouken (the owner of that blog) is Sam Lantinga, the guy who wrote SDL in the first place.

    2. Thanks for noting that you used malloc instead of new as I have no knowledge of C++ so just assumed it was a new variable name or something. You sped up my workaround a lot!

      For anyone else like me use this instead of ‘new’ and ‘delete’.
      Code for C users:
      Uint32 *pixels;
      pixels = (Uint32 *) malloc (sizeof(Uint32)*640*480);

      free(pixels);

  3. The link from libsdl to the blog post (https://wiki.libsdl.org/Tutorials under “Video”) says “Streaming textures with SDL 2.0”, but his actual blog says “1.3”. I assumed the blog was in error, or that it was still compatible 2.0 or they wouldn’t have linked it that way. It must just be a typo.

    I guess I can stop stressing over that post! Thanks again.

    Btw, the basic idea with texture streaming, is that you lock a texture for write-only access and somehow you can quickly make on-the-fly updates to the screen. Now that I can draw pixels thanks to your tutorial, my goal is to figure out if texture streaming is faster or slower than texture drawing.

    1. Interesting – I suppose that would give you benefits if you’re drawing e.g. a lot of sprites on the same texture, but probably not so much for just pixel drawing (it’s hard to beat just dumping pixels into a buffer and copying it to the texture in a single operation).

      Anyhow, let me know how it goes! šŸ™‚

  4. Looks like you are right again. I did get SDL_LockTexture working, but you apparently still have to copy the texture to the renderer and then present it. It is actually slower (about 17ms vs 6ms for SDL_UpdateTexture in my pixel plotting test). So I’m not sure what the point is. I could be missing something, but I see other people reporting UpdateTexture being faster as well.

    1. My guess (hey, I’m just a hobbyist) is that it will give you benefit when you want to write many times to the same texture. Every time you touch video memory, you are essentially beginning and ending a transaction (so to speak – but I’ve seen this concept in a number of graphics libraries). Doing this every time you copy something to the texture is expensive.

      But if you open this context/transaction/whatever once, copy everything to the texture as a batch, and then close it, it’s a lot more efficient.

      If you’re drawing pixels, you don’t need to do this, as you’re making a single update anyway. But if you have, say, a scene with a background image and 20 sprites, then you’ll get a decent speedup there.

      Again, I’m not a real game/graphics developer and this is just what I think will happen in theory. But I think it makes sense.

  5. Great tutorial but how to achieve that with fast mouse (or tablet) movements there are no dots but continuous pixels? Is it something to do with mouse polling speed or other factors? How does it work in painting programs, are the ‘holes’ are filled with the using of some algorithm?

    1. its basically connect the dots, run a line drawing alg to connect dot 1 to dot 2, dot 2 to dot 3, dot 3 to dot 4, etc

  6. For filling the holes the solution would be to draw lines from the previous point to the new one if the new point (mouse coordinates) is at a position more far than 1pixel in any of the directions. Question is that what would be the fastest way to determine if line need to be drawn instead of just pixels. As i see all other painting program doing the same.

    1. You said it yourself, the criteria is whether the new point is further than 1 pixel from the old. Just store the last point, and compute distance to the new one using Pythagorean distance.

  7. I can draw nice continous strokes using Bresenham’s line algorithm and Pythagorean distance (its rather fast, dont see any lag or delay), but what disturbes me is the constant 10-15% CPU utilization even if i did not touch the drawing area or the application window (so event polling cannot be the problem). Any idea why the high CPU consumption?

    GPU utilization was also high (>70%) but moving SDL_UpdateTexture and other SDL related updates to the SDL_MOUSEMOTION branch solved the problem.)

    1. Getting ‘SDL_Delay(20)’ before ‘SDL_PollEvent’ solved the problem, CPU load has fallen back to 0% on application idle. (Maybe there is more elegant way but its ok for now.)
      Thanks for the tutorial.

  8. Tanks for this article !
    But I get a segmentation fault in the line where I wrote the “SDL_RenderCopy(…)” do you know why ?
    I’m using C so malloc instead of new.

    1. Hard to say without having your code, but try to debug your program and check what pointers you are passing to the function. Most likely one of them is NULL or otherwise incorrectly set.

    2. Hi @AcL,
      You are right, this code example has this fatal bug in it.
      The reason is the “SDL_MOUSEMOTION” event.
      It takes over the coordinates from this event, which can come from outside the window already, if the button is being held down while pressed within the window previously (sorry if my grammar is not perfect, I hope it’s understandable).

      Actually, I can’t believe nobody else noticed this.

      My fix proposal is to keep the “SDL_MOUSEBUTTONUP” and “SDL_MOUSEBUTTONDOWN” events as is, and to replace the “SDL_MOUSEMOTION” event by pure “if(leftMouseButtonDown){..}”. It will ensure the coordinates to be taken over from one of the previously occurred events, “SDL_MOUSEBUTTONUP” or “SDL_MOUSEBUTTONDOWN”, which can only occur within your window – at least I hope šŸ™‚

      By the way thank you, @Gigi, for nice tutorial, it helped me a lot to enter the SDL. Without it, I would never even tried, seems too complicated and cumbersome to me.

  9. This method can only be used for gray-scale, if I set a pixel to the Uint32 (255 >> 24) + (255 >> 16) + (0 >> 8) + (0) (with the intention of making it red) I see no color.

    1. You’re just doing an incorrect calculation that gives a zero result. 0x00FF0000 gives me red, 0x000000FF gives me blue, etc.

        1. It’s easiest to do it in hex like I did (if you’ve played with colours in HTML or CSS, it’s very similar). That’s assuming it’s of the format 0xAARRGGBB, although in SDL2 it can vary and there are functions you can use to specify and convert between pixel formats.

          But I digress… I suppose you want it as a formula. I believe you need to shift in the opposite direction and in different amounts. If you don’t care about alpha, use the following:

          (255 << 16) + (255 << 8) + (0) If you want alpha, just add another value with << 24.

  10. Thanks for this tutorial, it helped me implement mouse button input on another program I’ve been working on.

    Can you please provide an example of how to use interpolation to fill in the gaps between the pixels?

    I was able to get fairly smooth lines by using SDL_RENDERER_ACCELERATED instead of SDL_RENDERER_PRESENTVSYNC, along with SDL_PollEvent instead of SDL_WaitEvent, as well as by drawing 3×3 pixels at a time, rather than a single pixel, but when moving the mouse quickly, there are still gaps.

    I have found lots of mathematical explanations discussing how to do such interpolation, but no practical examples that I can figure out how to implement with this program.

    1. Hi, unfortunately I don’t have time to write that up, but I suggest reading through the comments here as several people have already managed to solve this.

  11. This is nice, but I have 2 problems:
    1. SDL_UpdateTexture and SDL_RenderCopy both take forever. Like milliseconds.
    2. The whole thing slows down gradually, with fps degrading. It starts at around 3ms per frame and decays to 9ms/frame after a couple dozen seconds.

    1. Are you sure you’re not micro-optimising? 9ms/frame is still well over 100fps. Such tiny fluctuations could be due to anything… even just your OS scheduling less time to the process. What’s the real problem you’re experiencing?

      1. I come from a high perfomance industry that counts latency in nanoseconds.
        If the main loops takes milliseconds and the fps is capped at, say, 150, with 7ms/frame eaten away by the lib, well my game/demo/whatever starts with a serious handicap. That’s 7 whole milliseconds lost to produce an image, for instance.
        And the image shown on the screen is already 7ms old.
        If i aim for 90 fps, i could take 11ms to do what i have to do. But with 7ms taken by SDL2’s rendering, all i have is 4ms now.
        See?
        So, apart from threading the rendering phase (which would give me back the 7ms lost, but still would provide the viewer with 7ms old images), is there some other way to reduce the boilerplate’s CPU time?

        1. Well, I’m afraid I can’t help you much there. What I can say is that copying your image data from memory to video memory is always going to incur a cost, which I assume can be a few milliseconds, but I don’t have any actual numbers. It would probably be a good idea to ask at the SDL2 forums where some people might be more familiar with this level of optimisation.

      2. If there isn’t, it’s ok. I’m not pointing fingers. Just evaluating the viability of the lib for my purpose.

  12. Thanks for the tutorials, Im just stepping into SDL2 .
    Suppose I would want the right click event to clear the previously drawn pixels, so I can start over.
    if I make the right click event delete[] pixels, thats a seg fault.
    if I try redrawing the renderer, nothing happens. I also tried updating the renderer after redrawing, but the pixels drawn persist.

Leave a Reply

Your email address will not be published. Required fields are marked *