SDL_BlitSurface with alpha mod not blitting rightmost pixels properly

Hello everyone.

I just tried surface blitting with alpha-mod and noticed weird behavior. I think
there is a bug in SDL3’s implementation of SDL_BlitSurface (but SDL2’s
implementation was OK).

Bugged scenario: try blitting a 10x10 surface with alpha mod enabled onto the
window’s surface. The resulting blit will have wrong colors at the rightmost
vertical pixel stripes. But it seems to work ok if the width is a multiple of 4,
the source image is fully blitted onto the destination.

The above scenario presented no issues in pure SDL2. However after installing
SDL2-compat (which actually uses SDL3 under the hood), alpha blitting on my SDL2
code started to misbehave as described.

I suspect the bug was introduced by SDL3, so I tried writing a tiny sample from
scratch to test it out in pure SDL3. Here I copy the C code for SDL3 (compile
with recent gcc using -lSDL3 to link).

The program fills a fully white 10x10 square on the screen at the upper right
corner, then copies it into a 10x10 surface and from there it blits it twice
onto the screen below the original square (so you see three squares): first
without alpha mod and then with alpha mod (with alpha value lower than 255). The
first two boxes are all white as expected, and the third box should be all gray.
However you can clearly see a blue stripe at the right of the lowest box.

If you set the box width to a multiple of 4, e.g. by changing “w=10” to “w=16”
in DrawScene(), then it works ok: the third 16x10 rectangle is fully gray as
expected.

Tried on SDL 3.4.2 for Linux 64 bits, and SDL 3.2.18 for Windows 32 bits.

I imagine the equivalent SDL2 code, when run without SDL2-compat, should work
properly.

Any ideas and suggestions are most welcome.

#include <stdio.h>
#include <assert.h>
#include <SDL3/SDL.h>

#define SCREEN_WIDTH 300
#define SCREEN_HEIGHT 400

/* main window framebuffer surface */
SDL_Surface *screen;

/* maps {r,g,b} -> Uint32, in standard screen pixel format */
#define RGB(r,g,b) SDL_MapSurfaceRGB(screen, r, g, b)

void DrawScene() {
  /* clear screen */
  SDL_FillSurfaceRect(screen, NULL, RGB(0, 0, 0));

  /* box width */
  const int w = 10;             /* w=4,8,12,16 etc are fine.  */

  /* draw a full yellow rectangle */
  SDL_FillSurfaceRect(screen, & (SDL_Rect) {.x=0, .y=0, .w=w, .h=10},
                      RGB(255, 255, 255));

  /* copy the yellow rectangle into a separate surface `imgsurf` */
  SDL_Surface *imgsurf = SDL_CreateSurface(w, 10, screen->format);
  SDL_BlitSurface(screen, & (SDL_Rect) {.x=0, .y=0, .w=w, .h=10},
                  imgsurf, NULL);

  /* do a normal blit, no alpha */
  SDL_BlitSurface(imgsurf, NULL,
                  screen, & (SDL_Rect) {.x=0, .y=11});

  /* do an alpha blended blit */
  SDL_SetSurfaceBlendMode(imgsurf, SDL_BLENDMODE_BLEND);
  SDL_SetSurfaceAlphaMod(imgsurf, 150);
  SDL_BlitSurface(imgsurf, NULL,
                  screen, & (SDL_Rect) {.x=0, .y=22});

  /* zoomed copy for visual clarity */
  SDL_BlitSurfaceScaled(screen, &(SDL_Rect){.x=0, .y=0, .w=w, .h=50},
                        screen, &(SDL_Rect){.x=100, .y=0, .w=w*10, .h=500},
                        SDL_SCALEMODE_NEAREST);

  SDL_DestroySurface(imgsurf);

  /* so ... is the third box fully blitted with the same color ? */
}

int main() {
  if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
    fprintf(stderr, "failed to init SDL: %s", SDL_GetError());
    return 1;
  }

  SDL_Window *window = NULL;
  SDL_Event event;

  /* create and display the window */
  window = SDL_CreateWindow("SDL3 Alpha Blended Blit",
                            SCREEN_WIDTH, SCREEN_HEIGHT,
                            0);
  assert(window != NULL);

  /* get window's surface */
  screen = SDL_GetWindowSurface(window); /* do not SDL_FreeSurface ! */
  assert(screen != NULL);

  /* draw everything, just once */
  DrawScene();

  /* run the event loop */
  bool quit = false;
  while (!quit) {               /* main loop */
    while (!quit && SDL_PollEvent(&event)) { /* event loop */
      switch (event.type) {
      case SDL_EVENT_QUIT:
        quit = true;
        break;
      case SDL_EVENT_KEY_DOWN:
        if (event.key.key == SDLK_Q || event.key.key == SDLK_ESCAPE) {
          quit = true;
        }
      } /* switch event type */
    } /* event loop */

    /* display framebuffer on window */
    SDL_UpdateWindowSurface(window);
    SDL_Delay(50);
  } /* main loop */

  /* deinitialize and quit */
  SDL_DestroyWindow(window);
  SDL_Quit();

  return 0;
}

1 Like