How do I modify existing graphics before sending them to the screen?


#1

An example of this would be a drawing board. I create a white rectangle. I want to be able to modify this white rectangle by adding colored pixels, images, etc. I don’t simply want to put the rectangle on the screen and then add the other things, but I want the pixels and images to be part of the rectangle image so that I’m only adding one thing.

My current approach is to create a surface, draw a white rectangle on that, and then add pixels, shapes, images, etc. later. I am having trouble doing this, even following different Internet guides. I’m not really sure where to start, but here is some of my code so far (you may have to make some modifications to run it):

SDL_Init(SDL_INIT_EVERYTHING);

window = SDL_CreateWindow("Test Window", SDL_WINDOWPOS_UNDEFINED,
                            SDL_WINDOWPOS_UNDEFINED, 800, 600, 0);

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED |
                                SDL_RENDERER_PRESENTVSYNC);

SDL_Surface * white_square = SDL_CreateRGBSurfaceWithFormat(0, 100, 100, 32, SDL_PIXELFORMAT_RGBA32);

Uint32 color = SDL_MapRGB(white_square->format, 255, 255, 255);
SDL_FillRect(white_square, NULL, color);

SDL_LockSurface(white_square);
for (auto i : new_pixels)
{
  if ((i.x >= 0) && (i.x < 100) && (i.y >= 0) && (i.y < 100))
  {
    Uint8 * pixel = (Uint8*)white_square->pixels;
    pixel += (i.y * white_square->pitch) + (i.x * sizeof(Uint8));
    Uint32 test_color = 0xFF000000;
    *pixel = test_color;
  }
}
SDL_UnlockSurface(white_square);

SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, white_square);
SDL_Rect rect = {0, 0, 100, 100};
SDL_RenderCopy(renderer, texture, NULL, & rect);
SDL_DestroyTexture(texture);
texture = NULL;
SDL_FreeSurface(white_square);
SDL_DestroyRenderer(renderer);
renderer = NULL;
SDL_DestroyWindow(window);
window = NULL;
SDL_Quit();

Please feel free to offer suggestions and modifications. I’m not set on any one method, as long as I can modify graphics I load into the program and then output the modified graphics.

Thanks!


#2

Have you looked at SDL_TEXTUREACCESS_TARGET ? Create a texture with SDL_CreateTexture using that flag. SDL_SetRenderTarget to that texture. Draw & render on that texture, then SDL_SetRenderTarget with null, then you can draw that texture to the display however you want.

Then keep an eye out for the SDL_RENDER_TARGETS_RESET event. That will tell you that all your target textures have been invalidated, so you’ll need to reconstruct them. It can happen when you alt-tab, or switch screen modes, etc.


#3

I’m glad to know that I can write directly to a texture instead of the screen. Is this what I should be doing instead of writing each character, background tile, etc. to the renderer?

I’m hoping that I did this right. It appears to work. I am a little unsure about my method for changing the renderer back to itself, but perhaps you have some insight. Please take a look at my new code and see if this is what you meant.

Also, how can I actually use SDL_RENDER_TARGETS_RESET effectively? It seems that if at any time I can lose all of my texture changes, then I’d be in a real mess if I was making custom characters, battle damage modification, etc. Is there some way I can make a copy that won’t be destroyed, or something? In other words, if this can happen then is there any use to drawing on a texture since at any time the changes could be destroyed?

In any event, here is my new code:

#include <SDL.h>
#include <SDL_ttf.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char* argv[])
{
  srand (time(NULL));
  SDL_Init(SDL_INIT_EVERYTHING);

  window = SDL_CreateWindow("Lots o Dots", SDL_WINDOWPOS_UNDEFINED,
                            SDL_WINDOWPOS_UNDEFINED, w, h, 0);

  renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED |
                                SDL_RENDERER_PRESENTVSYNC);

  texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, 
                 SDL_TEXTUREACCESS_TARGET, 200, 200);

  bool quit = false;

  // Main loop
  while (!quit)
  {
  
    // Quit if someone closes the window.
    while (SDL_PollEvent(&e))
    {
      switch(e.type)
      {
        case SDL_QUIT:
          return true;
          break;
        default:
          break;
      }
    }
  
    // Clear screen to black.
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    // Set the render target to the texture.
    SDL_SetRenderTarget(renderer, texture);

    // Draw 20 random magenta pixels on the texture.
    SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
    for (int i = 0; i < 20; i++)
    {
      SDL_RenderDrawPoint(renderer, rand() % 200, rand() % 200);
    }

    // Reset the renderer to renderer.
    SDL_SetRenderTarget(renderer, NULL);

    // Draw the texture to the renderer at a random location.
    SDL_Rect dst = {rand() % 600, rand() % 400, 200, 200};  
    SDL_RenderCopy( renderer, texture, NULL, &dst );

    // Draw the renderer to the screen.
    SDL_RenderPresent(renderer);
  }

  return 0;
}

Please note that I have some of this code in classes, but pieced it together so it was shorter and easier to see the main part of the example. In other words, some changes may be needed to run this.


#4

You’ve done the changing the renderer back to itself ok. It looks fine at a quick glance.

If you’re using a targeted texture you have no choice other than to be ready to refresh it when requested, otherwise you could end up drawing empty black space. It’s the nature of the method.

Don’t be afraid to abandon drawing to a texture and just draw each frame of your game from scratch, and when it comes to drawing the part where you were using targeted textures, just draw all the components that made up your custom image from fresh. You can render 10,000 textures to the screen at 60fps very fast. The only reason to pre-render something to a target texture is if it’s going to really slow things down because it’s complicated (say, 100,000 static textures), or you want to manipulate the pixels.


#5

That pixel manipulation you mentioned at the end was kind of what I meant. For example, I am making a small game where you mow grass. Whenever the lawnmower travels over the light-green grass texture, it becomes dark green. Since the mower can move at different angles and speeds, I created a function to calculate the pixels between the mower’s previous and current location. A loop looks at each pixel and sees if it is on the lawn. If it is, then that pixel is set to dark green, if not, then nothing is drawn at that location.

As you can see, if I lost that drawing, then the program would be messed up. The only other option I can think of is to create a boolean 2D array that represents the pixels over the lawn. They all start as false and instead of drawing to the texture, the mowed area will change false values in the array to true. Then the lawn can be drawn as a green square and a 2-level loop can iterate through the array and draw a dark-green pixel wherever there is a true. Does that sound like a better option?


#6

It makes a lot of sense now, yes.

This is how games would have been made back in the 80s. Using actual video memory to keep track of collisions (or mowed grass in your case). I do miss them days.

I would highly recommend using the 2D array method. Is your game relatively low-res pixel art? You could still use a targeted texture and just keep a copy of the grass state in the array for if you need to refresh the texture. You can even render the texture from fresh every frame with loop that incrementally steps through the texture with a pointer (very quick - I’ve done it and get 1000s of fps plotting a whole screen of pixels). You could also save / load the game mid-level that way.


#7

Also, it sounds like you just need a simple polygon routine to mow a specific patch of grass depending on the mower’s previous location. Or if the mower moves like a grid-bug (up / down / left / right), just a SDL_RenderDrawRect would do. Sorry, getting carried away now :slight_smile:


#8

For right now I am using the array method, and yes the game is low-res at the moment. For things like the grass texture I see it being easy to store the values, especially booleans, but what about for more complicated textures? Also, how exactly do I go through the texture with a pointer? I saw something about that with surfaces I believe, but my efforts didn’t yield anything.

My current code for the array is simply:

// Main loop

SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);

SDL_SetRenderTarget(renderer, texture);
for (int i = 0; i < max_width; i++)
{
  for (int j = 0; j < max_height; j++)
  {
    if (my_array[i][j])
      SDL_SetRenderDrawColor(renderer, 0, 100, 0, 255);
    else
      SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
    SDL_RenderDrawPoint(renderer, i, j);
  }
}
SDL_SetRenderTarget(renderer, NULL);
SDL_Rect dst = {0, 0, 200, 200};
SDL_RenderCopy( renderer, texture, NULL, &dst );
SDL_RenderPresent(renderer);

Here is my variant without the texture:

// Main loop

SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);

for (int i = 0; i < max_width; i++)
{
  for (int j = 0; j < max_height; j++)
  {
    if (my_array[i][j])
      SDL_SetRenderDrawColor(renderer, 0, 100, 0, 255);
    else
      SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
    SDL_RenderDrawPoint(renderer, i, j);
  }
}
SDL_RenderPresent(renderer);

This version is shorter, and it leaves me wondering if there is an advantage to one over the other. This also got me thinking: if I’m loading in a lot of graphics from files, is there any way to safely preserve them in memory? I would imagine that having to reload all of your graphics because your textures got dumped would be a real pain in the fps. I could load them in as arrays of colors if you think that would be best, but then I’d have to draw them pixel-by-pixel.

In response to your second recent reply, yes, I am using a simple polygon routine. Here is the code. Please feel free to mention any improvements:

struct Point
{
  double x;
  double y;
};

// Mower is a basic class with x, y, width, length, angle variables.

double distance(Point p1, Point p2)
{
  return sqrt((p2.x - p1.x) * (p2.x - p1.x)  + (p2.y - p1.y) * (p2.y - p1.y));
}

bool pixel_array[200][200];
for (int i = 0; i < 200; i++)
{
  for (int j = 0; j < 200; j++)
    pixel_array[i][j] = false;
}

double HALF_PI = PI / 2.0;

// Main loop

Point previous_left = {mower.get_x() + (mower.get_width() / 2.0) *
                                   cos(mower.get_angle() + HALF_PI),
                                   mower.get_y() - (mower.get_width() / 2.0) *
                                   sin(mower.get_angle() + HALF_PI)};
Point previous_right = {mower.get_x() + (mower.get_width() / 2.0) *
                                     cos(mower.get_angle() - HALF_PI),
                                     mower.get_y() - (mower.get_width() / 2.0) *
                                     sin(mower.get_angle() - HALF_PI)};

int left_to_right_dist = ceil(distance(previous_left.x, previous_left.y,
                                          previous_right.x, previous_right.y));

// Move mower code

Point current_left = {mower.get_x() + (mower.get_width() / 2.0) *
                                 cos(mower.get_angle() + HALF_PI),
                                 mower.get_y() - (mower.get_width() / 2.0) *
                                 sin(mower.get_angle() + HALF_PI)};

for (int i = 0; i < left_to_right_dist; i++)
{
  int point_dist = ceil(distance(previous_left.x + i * cos(mower.get_angle() - HALF_PI),
                                 previous_left.y - i * sin(mower.get_angle() - HALF_PI),
                                 current_left.x + i * cos(mower.get_angle() - HALF_PI),
                                 current_left.y - i * sin(mower.get_angle() - HALF_PI)));

  for (int j = 0; j < point_dist; j++)
  {
    int x_pos = round(previous_left.x + i * cos(mower.get_angle() - HALF_PI) +
                                   j * cos(mower.get_angle()));
    int y_pos = round(previous_left.y - i * sin(mower.get_angle() - HALF_PI) -
                                   j * sin(mower.get_angle()));
    if ((x_pos >= 0) && (x_pos < 200) &&
        (y_pos >= 0) && (y_pos < 200))
          pixel_array[x_pos][y_pos] = true;
  }
}

// Clear screen, draw background, draw pixel array, draw to screen