Jorge Gonçalves
Jorge Gonçalves

Reputation: 61

Save SDL Texture to file

I am trying to save a texture to a png and the only thing I'm getting is a screenshot of a portion of the screen.

my code example:

src_texture =  SDL_CreateTextureFromSurface( renderer, some_surface );
/*.........*/

/*create target texture */
SDL_Texture *tmp_texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_TARGET , w, h);
SDL_SetTextureBlendMode(tmp_texture, SDL_BLENDMODE_NONE);
SDL_SetRenderTarget(renderer, tmp_texture);
SDL_RenderCopy(renderer, src_texture, NULL, NULL);

/*create surface and get pixels from texture*/
PixelFormat mask = GetMask(format);
s = SDL_CreateRGBSurface(0, w, h, 32, mask.Rmask, mask.Gmask, mask.Bmask, mask.Amask);
if (s) {
    SDL_SetRenderTarget(renderer, tmp_texture);
    SDL_RenderReadPixels(renderer, NULL, s->format->format, s->pixels, s->pitch);
    IMG_SavePNG(s, "image.png");

}
SDL_DestroyTexture(tmp_texture);

Any idea how to achieve this?

Upvotes: 5

Views: 11820

Answers (4)

Czipperz
Czipperz

Reputation: 3336

Here is my solution:

void save_texture(const char* file_name, SDL_Renderer* renderer, SDL_Texture* texture) {
    SDL_Texture* target = SDL_GetRenderTarget(renderer);
    SDL_SetRenderTarget(renderer, texture);
    int width, height;
    SDL_QueryTexture(texture, NULL, NULL, &width, &height);
    SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0);
    SDL_RenderReadPixels(renderer, NULL, surface->format->format, surface->pixels, surface->pitch);
    IMG_SavePNG(surface, file_name);
    SDL_FreeSurface(surface);
    SDL_SetRenderTarget(renderer, target);
}

This function has no side effects outside of the file write. This makes it easy to insert as you don't have to worry that the render target will be clobbered. Feel free to add error handling as you see fit.

Upvotes: 11

user1902824
user1902824

Reputation:

I was looking for the same thing and was disappointed when I couldn't find a snippet that performed this "simple" task.

So, I went ahead and made one for the next person that wants it. I wouldn't use this in production code, it's just for debugging. Tested in SDL 2.0.7.

What it does

Saves any SDL_Texture to a .bmp file.

Technically, it converts any SDL_Texture into an SDL_Surface, to which it saves as a .bmp.

Example use cases

Particularly well suited for:

  • Inspecting a large texture atlas that was generated during runtime
  • Exploring large generated levels all at once
  • Saving a screenshot
  • Viewing any generated or composite textures in your favorite image editor

Usage

Pretty straightforward, just pass any SDL_Renderer along with the SDL_Texture you want to save, and a filename to save to.

Also, this will clobber whatever file is there, so be careful.

The code forces everything into an RGBA32 format, though it is simple enough to tweak to whatever format you desire. If you want to, you can load format by passing it's pointer as a parameter to SDL_QueryTexture to auto-detect the format of the input texture and use that for the output file.

Limitations

If you want to enhance this code, here are a few things to consider.

  • Memory usage: this requires roughly 4x the memory of the texture, or 3x if you are trying to save a texture that is already a renderable target. That may be too much memory, depending on how big your textures are.
  • File compression: The image data is stored in a raw format, so there is no compression. This can yield prohibitively large files. If you wanted to use this for screenshots, you probably want to use this raw format as a stepping stone to a png, jpg, or other compressed format.
  • Error handling: there isn't much in the way of error handling. You probably want something more robust.

Snippet

/* Usage example */
save_texture(sdlttyDisplay->ren, sdlttyDisplay->font_tex, "image.bmp");
void save_texture(SDL_Renderer *ren, SDL_Texture *tex, const char *filename)
{
    SDL_Texture *ren_tex;
    SDL_Surface *surf;
    int st;
    int w;
    int h;
    int format;
    void *pixels;

    pixels  = NULL;
    surf    = NULL;
    ren_tex = NULL;
    format  = SDL_PIXELFORMAT_RGBA32;

    /* Get information about texture we want to save */
    st = SDL_QueryTexture(tex, NULL, NULL, &w, &h);
    if (st != 0) {
        SDL_Log("Failed querying texture: %s\n", SDL_GetError());
        goto cleanup;
    }

    ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h);
    if (!ren_tex) {
        SDL_Log("Failed creating render texture: %s\n", SDL_GetError());
        goto cleanup;
    }

    /*
     * Initialize our canvas, then copy texture to a target whose pixel data we 
     * can access
     */
    st = SDL_SetRenderTarget(ren, ren_tex);
    if (st != 0) {
        SDL_Log("Failed setting render target: %s\n", SDL_GetError());
        goto cleanup;
    }

    SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00);
    SDL_RenderClear(ren);

    st = SDL_RenderCopy(ren, tex, NULL, NULL);
    if (st != 0) {
        SDL_Log("Failed copying texture data: %s\n", SDL_GetError());
        goto cleanup;
    }

    /* Create buffer to hold texture data and load it */
    pixels = malloc(w * h * SDL_BYTESPERPIXEL(format));
    if (!pixels) {
        SDL_Log("Failed allocating memory\n");
        goto cleanup;
    }

    st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format));
    if (st != 0) {
        SDL_Log("Failed reading pixel data: %s\n", SDL_GetError());
        goto cleanup;
    }

    /* Copy pixel data over to surface */
    surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format);
    if (!surf) {
        SDL_Log("Failed creating new surface: %s\n", SDL_GetError());
        goto cleanup;
    }

    /* Save result to an image */
    st = SDL_SaveBMP(surf, filename);
    if (st != 0) {
        SDL_Log("Failed saving image: %s\n", SDL_GetError());
        goto cleanup;
    }

    SDL_Log("Saved texture as BMP to \"%s\"\n", filename);

cleanup:
    SDL_FreeSurface(surf);
    free(pixels);
    SDL_DestroyTexture(ren_tex);
}

Upvotes: 6

LBandy
LBandy

Reputation: 11

Have you managed to solve this? Using your code my program crashes on the SDL_RenderReadPixels() call.

If you change the texture to be SDL_TEXTUREACCESS_STREAMING you can capture it correctly with the above method, but note that only in the resolution of the actual renderer. If the texture is bigger, it will be cut off.

Upvotes: 0

aso
aso

Reputation: 1316

For saving part of screen into file, better create RGBSurface:

SDL_Surface *ss = SDL_CreateRGBSurface(0, w, h, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);

Here is SDL_CreateRGBSurface documentation

Next step is the same as in your code - use SDL_RenderReadPixels with correct color format. When you got this, you can simply save with SDL_SaveBMP or other functions.

Upvotes: 0

Related Questions