SDL 2D Rotation Help

I'm a bit new to programming in general. I've become stuck while trying to make a simple rotation function to work with the standard SDL library. I want to be able to rotate a surface about its center point.

I've managed to get an image scaling function. I thought I would follow the format I did for that, throw in some elementary linear algebra for the rotation and be done with it. Alas, c++ does not agree with me.

What am I doing wrong here?

Any help is appreciated.

Thank you.

PS:

The first block/paragraph inside the double for loops is the inverse rotation and translation function. It checks for pixels that will not get copied to because their pre-image pixel would be outside the bounds of the image width/height source.

The second does the opposite. It just takes pixels in the source rotates them and places them in their new locations, provided that those locations are inside the bounds of the image width/height.



//Improved, almost fixed.  However the rotated image now has a speckled black dots spaced in a grid-like pattern on it.  I think the "double" is "rounding out" some of the values.
SDL_Surface* Surface::RotateSurface(SDL_Surface* Surface, double Angle) {
	if(!Surface) return 0;
     
    SDL_Surface* _ret = SDL_CreateRGBSurface(Surface->flags, Surface->w, Surface->h, Surface->format->BitsPerPixel,
        Surface->format->Rmask, Surface->format->Gmask, Surface->format->Bmask, Surface->format->Amask);

	double CX = Surface->w / 2, CY = Surface->h / 2; //Center coordinates of image, or close enough.
	double X, Y, X2, Y2;

	for(Uint32 y = 0; y < Surface->h; y++) {
        for(Uint32 x = 0; x < Surface->w; x++) {
			X = (double)x - CX;
			Y = (double)y - CY;
			X2 = (X * cos(Angle) - Y * sin(Angle));
			Y2 = (X * sin(Angle) + Y * cos(Angle));
			X2 += CX;
			Y2 += CY;
			if( (int)X2 >= (int)Surface->w || (int)X2 < 0 || (int)Y2 >= Surface->h || (int)Y2 < 0) SetPixel32(_ret, x, y, SDL_MapRGB(Surface->format, 255, 0, 255));
			
			X = (double)x - CX;
			Y = (double)y - CY;
			X2 = (X * cos(Angle) + Y * sin(Angle));
			Y2 = (-X * sin(Angle) + Y * cos(Angle));
			X2 += CX;
			Y2 += CY;
			if( (int)X2 >= 0 && (int)X2 < Surface->w && (int)Y2 >= 0 && (int)Y2 < Surface->h) SetPixel32(_ret, (Uint32)X2, (Uint32)Y2, GetPixel32(Surface, x, y));
		}
	}

	return _ret;
}


Here are relevant functions SetPixel32 and GetPixel32:


Uint32 Surface::GetPixel32(SDL_Surface* Surface, int X, int Y) {
	SDL_LockSurface(Surface);
	Uint32* Pixels = (Uint32 *)Surface->pixels;  //Convert pixel to Uint32 Pointer
	SDL_UnlockSurface(Surface);

	return Pixels[Y * (Surface->pitch / 4) + X];  //Returns Uint32 color of pixel at x, y;
}

void Surface::SetPixel32(SDL_Surface* Surface, int X, int Y, Uint32 Color) {
	SDL_LockSurface(Surface);
	Uint32* Pixels = (Uint32 *)Surface->pixels; //Converts pixels to Uint32

	Pixels[ Y * (Surface->pitch / 4) + X ] = Color;  //Sets pixel color
	SDL_UnlockSurface(Surface);
}


Finally here is the scaling function which works perfectly:


SDL_Surface* Surface::ScaleSurface(SDL_Surface* Surface, Uint32 Width, Uint32 Height)
{
    if(!Surface || !Width || !Height)
        return 0;
     
    SDL_Surface* _ret = SDL_CreateRGBSurface(Surface->flags, Width, Height, Surface->format->BitsPerPixel,
        Surface->format->Rmask, Surface->format->Gmask, Surface->format->Bmask, Surface->format->Amask);
 
    double  _stretch_factor_x = (double)(Width)  / (double)(Surface->w);
	double  _stretch_factor_y = (double)(Height) / (double)(Surface->h);
 
    for(Uint32 y = 0; y < Surface->h; y++) {
        for(Uint32 x = 0; x < Surface->w; x++) {
            for(Uint32 o_y = 0; o_y < _stretch_factor_y; ++o_y) {
                for(Uint32 o_x = 0; o_x < _stretch_factor_x; ++o_x) {
                    SetPixel32(_ret, (Uint32)(_stretch_factor_x * x) + o_x, 
                        (Uint32)(_stretch_factor_y * y) + o_y, GetPixel32(Surface, x, y));
				}
			}
		}
	}

    return _ret;
}


Any help is appreciated. Always drives me nuts when I don't know what's wrong.
Last edited on
What is the problem, does it not compile? Does it crash? does it not rotate your image?

Something to note, that you might have noticed with your stetch function alreadty : For a better quality result, it's better to keep somewhere the initial image and stretch/rotate it than to take the previously stretched/rotated image and re stretch/rotate.
I think you should use Surface->w instead of Surface->pitch so change
double CX = Surface->pitch / 2
to
double CX = Surface->w / 2

When you do the rotation, X is changed on the first line so the new X value is used instead of the old X value in the calculation of the new Y.

Be careful when comparing signed and unsigned integers. This (Uint32)X < 0 can never be anything other than false because (Uint32)X is an unsigned integer.
OMG. Thank you SOO much Peter87! I'm a giant moron. That, or I was really tired, or both.

Alright the fixes you suggested improve it tremendously! Before the image was just being drawn as a black square with a pink line on it. Now the image is drawn (save for a few dots) and correctly rotated.

There is still a small problem left. The rotated image has a grid-like pattern of black dots on it. I think this is due to rounding errors caused by the (int) casting, or repeating decimals in binary, or perhaps because euclidean coordinates correspond to the top left corner of each pixel.

Any ideas how I can not "miss" any pixels?

Again thank you SO much for all the help!!
Last edited on
What if you iterate through the pixels in the _ret surface instead of the original surface? That way you will not miss any pixels.
You know Peter87 I was just thinking the same thing. I did this:

for(Uint32 y = 0; y < Surface->h; y++) {
        for(Uint32 x = 0; x < Surface->w; x++) {
			X = (double)x - CX;
			Y = (double)y - CY;
			X2 = (X * cos(Angle) - Y * sin(Angle));
			Y2 = (X * sin(Angle) + Y * cos(Angle));
			X2 += CX;
			Y2 += CY;
			if( (int)X2 >= (int)Surface->w || (int)X2 < 0 || (int)Y2 >= Surface->h || (int)Y2 < 0) SetPixel32(_ret, x, y, SDL_MapRGB(Surface->format, 255, 0, 255));
			else SetPixel32(_ret, x, y, GetPixel32(Surface, X2, Y2));
		}
	}


When I do this it works better. It doesn't miss any pixels and it rotates it. The only downside here is that the rotated image is not the "BEST" copy in the world (sort of blocky around the edges depending on the angle of rotation).

I can see why this method doesn't miss pixels. However the first method where I did two separate transforms missed pixels. Was this due to rounding errors?
Last edited on
I don't think it was rounding errors with the floating point numbers that was the problem. I guess you could say "rounding error" in the sense that it has to "round" to closest pixel. Some pixels in the original surface will probably be written to the same pixel in _ret, but I'm not sure that's the whole truth.

With the second approach I guess you could get better result if you used supersampling. http://en.wikipedia.org/wiki/Supersampling
So instead instead of looking up one pixel value you can look up 4 (or more) pixel values around (X2, Y2) and calculate the average colour that you write to (x, y).
I may look into supersampling later. I want to get some other stuff done with this, since I wasted SO much time beating my face against that. I often make careless mistakes that hurt me later. Hopefully programming corrects my lazy ways.

Thanks again Peter87. I really appreciate it!
I think you also have the option to use cairo directly on SDL surfaces. It must have a built-in rotation method (and hopefully you could take advantage of hardware acceleration)
If you want to use other libraries, SDL_gfx has a function rotozoomSurface that can both zoom and rotate surfaces at the same time.
IMO You should really just let the video hardware do rotations. Making the cpu do it is kind of a waste of time (both yours, and the cpu's)

EDIT:

To clarify, you'd need to use a lib that taps into this capability of the video hardware. SDL is not such a lib. It is based on some extremely dated drawing concepts.

SFML is an alternative lib which allows for this. I would recommend it over SDL any day of the week.
Last edited on
Topic archived. No new replies allowed.