Commit 9c483655240c36daf1a10359bb801a8a9e3c9bd6

Sam Lantinga 2016-10-07T18:00:30

Fixed bug 3029 - software renderer cuts off edges when rotate-blitting 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.

diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 556d68d..1631a72 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -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);
     }
 
diff --git a/src/render/software/SDL_render_sw.c b/src/render/software/SDL_render_sw.c
index dadd244..e7a6cd8 100644
--- a/src/render/software/SDL_render_sw.c
+++ b/src/render/software/SDL_render_sw.c
@@ -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;
diff --git a/src/render/software/SDL_rotate.c b/src/render/software/SDL_rotate.c
index 356f6c1..b93454d 100644
--- a/src/render/software/SDL_rotate.c
+++ b/src/render/software/SDL_rotate.c
@@ -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.
@@ -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;
@@ -431,6 +505,18 @@ 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
     */
@@ -438,10 +524,11 @@ SDLgfx_rotateSurface(SDL_Surface * src, double angle, int centerx, int centery, 
         /*
         * 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
         */
@@ -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));
     }