Skip to content

Commit

Permalink
Fixed bug 3029 - software renderer cuts off edges when rotate-blittin…
Browse files Browse the repository at this point in the history
…g with a multiple of 90 degrees

Adam M.

When doing a rotated texture copy with the software renderer, where the angle is a multiple of 90 degrees, one or two edges of the image get cut off. This is because of the following line in sw_rotate.c:
    if ((unsigned)dx < (unsigned)sw && (unsigned)dy < (unsigned)sh) {
which is effectively saying:
    if (dx >= 0 && dx < src->w-1 && dy >= 0 && dy < src->h-1) {

As a result, it doesn't process pixels in the right column or bottom row of the source image (except when they're accessed as part of the bilinear filtering for nearby pixels). This causes it to look like the edges are cut off, and it's especially obvious with an exact multiple of 90 degrees.
  • Loading branch information
slouken committed Oct 8, 2016
1 parent 5c2320f commit 9c48365
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/render/SDL_render.c
Expand Up @@ -1771,7 +1771,7 @@ SDL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
SDL_FRect frect;
SDL_FPoint fcenter;

if (flip == SDL_FLIP_NONE && angle == 0) { /* fast path when we don't need rotation or flipping */
if (flip == SDL_FLIP_NONE && (int)(angle/360) == angle/360) { /* fast path when we don't need rotation or flipping */
return SDL_RenderCopy(renderer, texture, srcrect, dstrect);
}

Expand Down
4 changes: 2 additions & 2 deletions src/render/software/SDL_render_sw.c
Expand Up @@ -677,8 +677,8 @@ SW_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
}

if (!retval) {
SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, -angle, &dstwidth, &dstheight, &cangle, &sangle);
surface_rotated = SDLgfx_rotateSurface(surface_scaled, -angle, dstwidth/2, dstheight/2, GetScaleQuality(), flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, dstwidth, dstheight, cangle, sangle);
SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, angle, &dstwidth, &dstheight, &cangle, &sangle);
surface_rotated = SDLgfx_rotateSurface(surface_scaled, angle, dstwidth/2, dstheight/2, GetScaleQuality(), flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, dstwidth, dstheight, cangle, sangle);
if(surface_rotated) {
/* Find out where the new origin is by rotating the four final_rect points around the center and then taking the extremes */
abscenterx = final_rect.x + (int)center->x;
Expand Down
149 changes: 119 additions & 30 deletions src/render/software/SDL_rotate.c
Expand Up @@ -110,31 +110,105 @@ SDLgfx_rotozoomSurfaceSizeTrig(int width, int height, double angle,
int *dstwidth, int *dstheight,
double *cangle, double *sangle)
{
double x, y, cx, cy, sx, sy;
double radangle;
int dstwidthhalf, dstheighthalf;
/* The trig code below gets the wrong size (due to FP inaccuracy?) when angle is a multiple of 90 degrees */
int angle90 = (int)(angle/90);
if(angle90 == angle/90) { /* if the angle is a multiple of 90 degrees */
angle90 %= 4;
if(angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
if(angle90 & 1) {
*dstwidth = height;
*dstheight = width;
*cangle = 0;
*sangle = angle90 == 1 ? -1 : 1; /* reversed because our rotations are clockwise */
} else {
*dstwidth = width;
*dstheight = height;
*cangle = angle90 == 0 ? 1 : -1;
*sangle = 0;
}
} else {
double x, y, cx, cy, sx, sy;
double radangle;
int dstwidthhalf, dstheighthalf;
/*
* Determine destination width and height by rotating a centered source box
*/
radangle = angle * (M_PI / -180.0); /* reverse the angle because our rotations are clockwise */
*sangle = SDL_sin(radangle);
*cangle = SDL_cos(radangle);
x = (double)(width / 2);
y = (double)(height / 2);
cx = *cangle * x;
cy = *cangle * y;
sx = *sangle * x;
sy = *sangle * y;

dstwidthhalf = MAX((int)
SDL_ceil(MAX(MAX(MAX(SDL_fabs(cx + sy), SDL_fabs(cx - sy)), SDL_fabs(-cx + sy)), SDL_fabs(-cx - sy))), 1);
dstheighthalf = MAX((int)
SDL_ceil(MAX(MAX(MAX(SDL_fabs(sx + cy), SDL_fabs(sx - cy)), SDL_fabs(-sx + cy)), SDL_fabs(-sx - cy))), 1);
*dstwidth = 2 * dstwidthhalf;
*dstheight = 2 * dstheighthalf;
}
}

/*
* Determine destination width and height by rotating a centered source box
*/
radangle = angle * (M_PI / 180.0);
*sangle = SDL_sin(radangle);
*cangle = SDL_cos(radangle);
x = (double)(width / 2);
y = (double)(height / 2);
cx = *cangle * x;
cy = *cangle * y;
sx = *sangle * x;
sy = *sangle * y;

dstwidthhalf = MAX((int)
SDL_ceil(MAX(MAX(MAX(SDL_fabs(cx + sy), SDL_fabs(cx - sy)), SDL_fabs(-cx + sy)), SDL_fabs(-cx - sy))), 1);
dstheighthalf = MAX((int)
SDL_ceil(MAX(MAX(MAX(SDL_fabs(sx + cy), SDL_fabs(sx - cy)), SDL_fabs(-sx + cy)), SDL_fabs(-sx - cy))), 1);
*dstwidth = 2 * dstwidthhalf;
*dstheight = 2 * dstheighthalf;
/* Computes source pointer X/Y increments for a rotation that's a multiple of 90 degrees. */
static void
computeSourceIncrements90(SDL_Surface * src, int bpp, int angle, int flipx, int flipy,
int *sincx, int *sincy, int *signx, int *signy)
{
int pitch = flipy ? -src->pitch : src->pitch;
if (flipx) {
bpp = -bpp;
}
switch (angle) { /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
case 0: *sincx = bpp; *sincy = pitch - src->w * *sincx; *signx = *signy = 1; break;
case 1: *sincx = -pitch; *sincy = bpp - *sincx * src->h; *signx = 1; *signy = -1; break;
case 2: *sincx = -bpp; *sincy = -src->w * *sincx - pitch; *signx = *signy = -1; break;
case 3: default: *sincx = pitch; *sincy = -*sincx * src->h - bpp; *signx = -1; *signy = 1; break;
}
if (flipx) {
*signx = -*signx;
}
if (flipy) {
*signy = -*signy;
}
}

/* Performs a relatively fast rotation/flip when the angle is a multiple of 90 degrees. */
#define TRANSFORM_SURFACE_90(pixelType) \
int dy, dincy = dst->pitch - dst->w*sizeof(pixelType), sincx, sincy, signx, signy; \
Uint8 *sp = (Uint8*)src->pixels, *dp = (Uint8*)dst->pixels, *de; \
\
computeSourceIncrements90(src, sizeof(pixelType), angle, flipx, flipy, &sincx, &sincy, &signx, &signy); \
if (signx < 0) sp += (src->w-1)*sizeof(pixelType); \
if (signy < 0) sp += (src->h-1)*src->pitch; \
\
for (dy = 0; dy < dst->h; sp += sincy, dp += dincy, dy++) { \
if (sincx == sizeof(pixelType)) { /* if advancing src and dest equally, use memcpy */ \
SDL_memcpy(dp, sp, dst->w*sizeof(pixelType)); \
sp += dst->w*sizeof(pixelType); \
dp += dst->w*sizeof(pixelType); \
} else { \
for (de = dp + dst->w*sizeof(pixelType); dp != de; sp += sincx, dp += sizeof(pixelType)) { \
*(pixelType*)dp = *(pixelType*)sp; \
} \
} \
}

static void
transformSurfaceRGBA90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
{
TRANSFORM_SURFACE_90(tColorRGBA);
}

static void
transformSurfaceY90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
{
TRANSFORM_SURFACE_90(tColorY);
}

#undef TRANSFORM_SURFACE_90

/* !
\brief Internal 32 bit rotozoomer with optional anti-aliasing.
Expand Down Expand Up @@ -341,7 +415,7 @@ SDLgfx_rotateSurface(SDL_Surface * src, double angle, int centerx, int centery,
{
SDL_Surface *rz_src;
SDL_Surface *rz_dst;
int is32bit;
int is32bit, angle90;
int i;
Uint8 r = 0, g = 0, b = 0;
Uint32 colorkey = 0;
Expand Down Expand Up @@ -431,17 +505,30 @@ SDLgfx_rotateSurface(SDL_Surface * src, double angle, int centerx, int centery,
SDL_LockSurface(rz_src);
}

/* check if the rotation is a multiple of 90 degrees so we can take a fast path and also somewhat reduce
* the off-by-one problem in _transformSurfaceRGBA that expresses itself when the rotation is near
* multiples of 90 degrees.
*/
angle90 = (int)(angle/90);
if (angle90 == angle/90) {
angle90 %= 4;
if (angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
} else {
angle90 = -1;
}

/*
* Check which kind of surface we have
*/
if (is32bit) {
/*
* Call the 32bit transformation routine to do the rotation (using alpha)
*/
_transformSurfaceRGBA(rz_src, rz_dst, centerx, centery,
(int) (sangleinv), (int) (cangleinv),
flipx, flipy,
smooth);
if (angle90 >= 0) {
transformSurfaceRGBA90(rz_src, rz_dst, angle90, flipx, flipy);
} else {
_transformSurfaceRGBA(rz_src, rz_dst, centerx, centery, (int) (sangleinv), (int) (cangleinv), flipx, flipy, smooth);
}
/*
* Turn on source-alpha support
*/
Expand All @@ -458,9 +545,11 @@ SDLgfx_rotateSurface(SDL_Surface * src, double angle, int centerx, int centery,
/*
* Call the 8bit transformation routine to do the rotation
*/
transformSurfaceY(rz_src, rz_dst, centerx, centery,
(int) (sangleinv), (int) (cangleinv),
flipx, flipy);
if(angle90 >= 0) {
transformSurfaceY90(rz_src, rz_dst, angle90, flipx, flipy);
} else {
transformSurfaceY(rz_src, rz_dst, centerx, centery, (int)(sangleinv), (int)(cangleinv), flipx, flipy);
}
SDL_SetColorKey(rz_dst, /* SDL_SRCCOLORKEY */ SDL_TRUE | SDL_RLEACCEL, _colorkey(rz_src));
}

Expand Down

0 comments on commit 9c48365

Please sign in to comment.