Search code examples
c#sdl-2image-rotation

Rotating SDL_Surface unpredictably produces incorrect result


Have been experimenting with SDL2 in c#, and have tried to create a maxrects-driven texture packer. In order for the atlases to be packed in a space efficient manner, rotating textures is almost always necessary. The program uses SDL_Surfaces to load images from files and blit them into a larger SDL_Surface and write it to the disk. This is how I implemented rotations:

// get pointer to original SDL_Surface
var sptr = (SDL_Surface*)surfaces[bestIndex].Item2;
// check if maxrects algorithm has rotated the rectangle "best"
if (best.w != sptr->w) {
    var rotd = (SDL_Surface*)SDL_CreateRGBSurfaceWithFormat(0, sptr->h, sptr->w, 32, SDL_PIXELFORMAT_RGBA8888);
    IntPtr rotdPixels = rotd->pixels;
    IntPtr sptrPixels = sptr->pixels;

    for (int y = 0; y < sptr->h; y++) {
        for (int x = 0; x < sptr->w; x++) {
            // get target pointer by swapping dimensions and flipping one, resulting in a rotation
            uint* target = (uint*)(rotdPixels + x * rotd->pitch + (sptr->h - y - 1) * 4);
            // set target from the expected location. Bswsp is necessary.
            *target = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(*(uint*)(sptrPixels + y * sptr->pitch + x * 4));
        }
    }

    if (SDL_BlitSurface(new IntPtr(rotd), IntPtr.Zero, atlas, ref best) < 0)
        throw new Exception(SDL_GetError());
} else {
    if (SDL_BlitSurface(new IntPtr(sptr), IntPtr.Zero, atlas, ref best) < 0)
        throw new Exception(SDL_GetError());
}

This is a highly unsafe method that I'm surprised works as well as it does in it's current form, however it presents a seemingly unpredictable bug in the resulting atlas (and in separate images if I save the rotated surface before blitting), that can only be described best by the mis-transformed image itself:

Imgur doesn't preserve original data, instead here Google Drive links to before and after:
After rotation (png)

Before rotation (jpg)

Note that this isn't restricted to .jpg files nor does that format necessarily lead to the issue, .png files will do the same occasionally too, but less often. If I were to explain what was happening: the image is being slightly squashed, a sector cut and repositioned, and the colours splitting into almost CRT-looking scanline effect that appears to be repeating in patterns of 3 rows, but still somewhat reflects the original image, rotated.

Does anyone know what could be causing this, how to prevent it, or how to programmatically detect when it will happen and to avoid it. Or, if must be the case, a better way to go about doing this? I currently can't find any consistency as to which formats/dimensions cause this issue, and have confirmed that it is not the blitting process causing the issue, and the maxrects algorithm is working as expected - the rectangles returned are valid. If you can help, thanks in advance..


Solution

  • As @kelter pointed out, I should have been checking the surface format initially. By optionally converting the surface if need be on image load, the code worked as intended.

    As an explanation for the specific oddities, the two formats of files I was using happened to be ABGR8888 and RGB24, which was why the byteswap made the original work in part, the patterns of 3 were due to the 3byte vs 4byte memory layout, which caused the squashing by 'skipping' over certain pixels, and the overflow caused the extra duplicate cut in the final image.