0

I'm trying to blit a surface created by IMG_Load (SDL_image) onto one created via SDL_CreateRGBSurface. When both surfaces are loaded with IMG_Load, this works fine, but not when the target surface was created using SDL_CreateRGBSurface.

By experimentation, I figured out that if I call SDL_SetAlpha on the source surface, it suddenly works fine. The documentation is a bit lacking, but how I understand it, the way I call it below should clear the SDL_SRCALPHA flag, presumably set by IMG_Load. It seems like the alpha flag of the source surface is what matters when blitting, so I guess this turns off alpha blending altogether.

The big question is: Why is this SDL_SetAlpha necessary in the first place? And is this really the right way to do it?

Here's code that reproduces this:

#include <SDL/SDL.h>
#include <SDL_image/SDL_image.h>

SDL_Surface* copy_surface(SDL_Surface* source)
{
    SDL_Surface *target;

    target = SDL_CreateRGBSurface(0, source->w, source->h,
                                  source->format->BitsPerPixel,
                                  source->format->Rmask, source->format->Gmask,
                                  source->format->Bmask, source->format->Amask);

    /*
     * I really don't understand why this is necessary. This is supposed to
     * clear the SDL_SRCALPHA flag presumably set by IMG_Load. But why wouldn't
     * blitting work otherwise?
     */
    SDL_SetAlpha(source, 0, 0);

    SDL_BlitSurface(source, 0, target, 0);
    return target;
}

int main(int argc, char* argv[])
{
    SDL_Event event;
    SDL_Surface *copy, *screen, *source;

    SDL_Init(SDL_INIT_VIDEO);
    screen = SDL_SetVideoMode(800, 600, 0, 0);
    SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0xff, 0xff, 0xff));
    source = IMG_Load("source.png");
    if (!source) {
        fprintf(stderr, "Unable to load source image\n");
        return 1;
    }
    copy = copy_surface(source);
    SDL_BlitSurface(copy, 0, screen, 0);
    SDL_Flip(screen);
    while (SDL_WaitEvent(&event))
        if (event.type == SDL_QUIT
            || (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE))
            break;
    SDL_Quit();
    return 0;
}

You'll need source.png. I'm using a 400x400 transparent PNG with some black scribbling.

futlib
  • 8,258
  • 13
  • 40
  • 55
  • http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsetalpha.html explains behaviour for each RGB/RGBA combination. Blitting RGBA to RGBA is kind of logically problematic (yet still behaviour is well-defined). – keltar May 05 '14 at 07:00
  • Why does it work with two RGBA images loaded via SDL_image or SDL_ttf then? I feel there's something I need to do with this surface I create, but I can't figure out what. – futlib May 05 '14 at 09:34
  • Well, image you getting from SDL_image contains data, while the one you've created manually is initialised with 0. Blending something with absolutely transparent black is probably bad idea. And, after this blend, surface is still absolutely transparent (because destination alpha is left untouched). If you will fill surface with white colour first, it will work as you want it to. – keltar May 05 '14 at 10:13
  • And for white I mean opaque white (`SDL_FillRect(target, 0, -1)`); – keltar May 05 '14 at 10:19
  • Ah, now I get it. What I actually try to do is to copy part of "source" onto "target" (not the whole of it) - is there an alternative to SDL_BlitSurface I can use for this? Or is turning off alpha blending with the SDL_SetAlpha() above the reasonable way? – futlib May 05 '14 at 20:55
  • For clarification, what I find weird here is that I do this to the SOURCE surface. I'd rather keep that one unmodified. I guess restoring the SDL_SRCALPHA flag after blitting is one option... – futlib May 05 '14 at 20:56
  • Rethinking this, I find it completely unintuitive. Is that how alpha blending is supposed to work? The alpha channel in the _target_ surface shouldn't matter at all... – futlib May 05 '14 at 21:04
  • You blending source image on top of target. Yes, only source alpha matters (also true for OpenGL and others). Formula is `SRC_COLOUR * SRC_ALPHA + DST_COLOUR * (1 - SRC_ALPHA)` (if alpha is converted to [0.0; 1.0] range). You should either drop alpha flag and restore it later or copy pixels manually, ignoring alpha (could be tricky/slow if source and destination pixel formats are different). You could also try saving `source->flags` field somewhere in local variable and restore it later, but I'm not sure it is guaranteed to work. – keltar May 06 '14 at 03:14
  • I think I see your problem now. Relevant part in documentation is "The alpha channel in the destination surface is left untouched". When you blitting into absolutely transparent destination - colours are blended, but result is still absolutely transparent. When you later blit it on screen, it produces no visible results. So destination alpha is used when you blitting on screen, not when you blitting source to target. – keltar May 06 '14 at 03:34
  • Ah, I finally get it. Yeah I can see how it can make sense not to ignore the alpha channel of the target surface. Still, I find the default a bit unintuitive here. And I find it somewhat hard to believe that I need to change the SOURCE surface to ignore the alpha channel of the target surface. Maybe I should reset the alpha channel of that one? – futlib May 07 '14 at 18:01

1 Answers1

1

http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsetalpha.html describes blitting for each possible pixel format combination.

When alpha blending is performed, standard formula is SRC_COLOUR * SRC_ALPHA + DST_COLOUR * (1 - SRC_ALPHA). Destination alpha isn't used in here.

If alpha blending is enabled, and SRCALPHA flag is enabled for source surface, source alpha is used to re-calculate (blend) updated destination colours, but alpha channel of destination is left untouched. Next time this 'dst' used as source of blitting, its alpha would be used.

To avoid your problem, there are several possible ways:

  1. Disable SRCALPHA for source surface (you already tried that)
  2. Disable SRCALPHA for destination surface, so it will not be blended with screen. It does not affect initial blit, but does affect next one, when your 'target' (or 'copy') used as source to blit on screen.
  3. Fill destination surface with opaque white. It does, however, affect blitting, on [partially]-transparent parts of source (alpha blending with white and with black produces different results).
  4. Manually copy pixels. If pixel format and surface dimensions match, mere memcpy would be enough. This will overwrite contents of destination, not blend it. It also would be noticeably faster than blend. If dimensions do not match - depending on pitch parameter you probably would need number_of_rows memcpy's. If pixel formats do not match - copying is problematic, you need to decode each pixel from source pixel format and encode it to destination pixel format.
  5. Use SDL_ConvertSurface (e.g. SDL_ConvertSurface(source, source->format, 0). It will create new surface with the same contents. I think this way is preferred. Don't forget to free new surface later.
  6. Use SDL_DisplayFormatAlpha if you use SDL 1.2. This is almost the same as SDL_ConvertSurface, but converts surface to display format, not source surface's one. It is no longer available in SDL2.

Also note that (4), (5) and (6) are only ways I see that allows you to keep source alpha. All blitting operations discarding source alpha, leaving destination with its own alpha values - as blitting never updates destination alpha.

keltar
  • 17,711
  • 2
  • 37
  • 42