Fixed bug 3029 - software renderer cuts off edges when rotate-blitting with a multiple of 90 degrees
authorSam Lantinga <slouken@libsdl.org>
Fri, 07 Oct 2016 18:00:30 -0700
changeset 104891e1ce9f6d215
parent 10488 3e61babccbbe
child 10490 24472af11074
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.
src/render/SDL_render.c
src/render/software/SDL_render_sw.c
src/render/software/SDL_rotate.c
     1.1 --- a/src/render/SDL_render.c	Fri Oct 07 17:58:02 2016 -0700
     1.2 +++ b/src/render/SDL_render.c	Fri Oct 07 18:00:30 2016 -0700
     1.3 @@ -1771,7 +1771,7 @@
     1.4      SDL_FRect frect;
     1.5      SDL_FPoint fcenter;
     1.6  
     1.7 -    if (flip == SDL_FLIP_NONE && angle == 0) { /* fast path when we don't need rotation or flipping */
     1.8 +    if (flip == SDL_FLIP_NONE && (int)(angle/360) == angle/360) { /* fast path when we don't need rotation or flipping */
     1.9          return SDL_RenderCopy(renderer, texture, srcrect, dstrect);
    1.10      }
    1.11  
     2.1 --- a/src/render/software/SDL_render_sw.c	Fri Oct 07 17:58:02 2016 -0700
     2.2 +++ b/src/render/software/SDL_render_sw.c	Fri Oct 07 18:00:30 2016 -0700
     2.3 @@ -677,8 +677,8 @@
     2.4      }
     2.5  
     2.6      if (!retval) {
     2.7 -        SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, -angle, &dstwidth, &dstheight, &cangle, &sangle);
     2.8 -        surface_rotated = SDLgfx_rotateSurface(surface_scaled, -angle, dstwidth/2, dstheight/2, GetScaleQuality(), flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, dstwidth, dstheight, cangle, sangle);
     2.9 +        SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, angle, &dstwidth, &dstheight, &cangle, &sangle);
    2.10 +        surface_rotated = SDLgfx_rotateSurface(surface_scaled, angle, dstwidth/2, dstheight/2, GetScaleQuality(), flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL, dstwidth, dstheight, cangle, sangle);
    2.11          if(surface_rotated) {
    2.12              /* Find out where the new origin is by rotating the four final_rect points around the center and then taking the extremes */
    2.13              abscenterx = final_rect.x + (int)center->x;
     3.1 --- a/src/render/software/SDL_rotate.c	Fri Oct 07 17:58:02 2016 -0700
     3.2 +++ b/src/render/software/SDL_rotate.c	Fri Oct 07 18:00:30 2016 -0700
     3.3 @@ -110,31 +110,105 @@
     3.4                                 int *dstwidth, int *dstheight,
     3.5                                 double *cangle, double *sangle)
     3.6  {
     3.7 -    double x, y, cx, cy, sx, sy;
     3.8 -    double radangle;
     3.9 -    int dstwidthhalf, dstheighthalf;
    3.10 -
    3.11 -    /*
    3.12 -    * Determine destination width and height by rotating a centered source box
    3.13 -    */
    3.14 -    radangle = angle * (M_PI / 180.0);
    3.15 -    *sangle = SDL_sin(radangle);
    3.16 -    *cangle = SDL_cos(radangle);
    3.17 -    x = (double)(width / 2);
    3.18 -    y = (double)(height / 2);
    3.19 -    cx = *cangle * x;
    3.20 -    cy = *cangle * y;
    3.21 -    sx = *sangle * x;
    3.22 -    sy = *sangle * y;
    3.23 -
    3.24 -    dstwidthhalf = MAX((int)
    3.25 -        SDL_ceil(MAX(MAX(MAX(SDL_fabs(cx + sy), SDL_fabs(cx - sy)), SDL_fabs(-cx + sy)), SDL_fabs(-cx - sy))), 1);
    3.26 -    dstheighthalf = MAX((int)
    3.27 -        SDL_ceil(MAX(MAX(MAX(SDL_fabs(sx + cy), SDL_fabs(sx - cy)), SDL_fabs(-sx + cy)), SDL_fabs(-sx - cy))), 1);
    3.28 -    *dstwidth = 2 * dstwidthhalf;
    3.29 -    *dstheight = 2 * dstheighthalf;
    3.30 +    /* The trig code below gets the wrong size (due to FP inaccuracy?) when angle is a multiple of 90 degrees */
    3.31 +    int angle90 = (int)(angle/90);
    3.32 +    if(angle90 == angle/90) { /* if the angle is a multiple of 90 degrees */
    3.33 +        angle90 %= 4;
    3.34 +        if(angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
    3.35 +        if(angle90 & 1) {
    3.36 +            *dstwidth  = height;
    3.37 +            *dstheight = width;
    3.38 +            *cangle = 0;
    3.39 +            *sangle = angle90 == 1 ? -1 : 1; /* reversed because our rotations are clockwise */
    3.40 +        } else {
    3.41 +            *dstwidth  = width;
    3.42 +            *dstheight = height;
    3.43 +            *cangle = angle90 == 0 ? 1 : -1;
    3.44 +            *sangle = 0;
    3.45 +        }
    3.46 +    } else {
    3.47 +        double x, y, cx, cy, sx, sy;
    3.48 +        double radangle;
    3.49 +        int dstwidthhalf, dstheighthalf;
    3.50 +        /*
    3.51 +        * Determine destination width and height by rotating a centered source box
    3.52 +        */
    3.53 +        radangle = angle * (M_PI / -180.0); /* reverse the angle because our rotations are clockwise */
    3.54 +        *sangle = SDL_sin(radangle);
    3.55 +        *cangle = SDL_cos(radangle);
    3.56 +        x = (double)(width / 2);
    3.57 +        y = (double)(height / 2);
    3.58 +        cx = *cangle * x;
    3.59 +        cy = *cangle * y;
    3.60 +        sx = *sangle * x;
    3.61 +        sy = *sangle * y;
    3.62 +        
    3.63 +        dstwidthhalf = MAX((int)
    3.64 +            SDL_ceil(MAX(MAX(MAX(SDL_fabs(cx + sy), SDL_fabs(cx - sy)), SDL_fabs(-cx + sy)), SDL_fabs(-cx - sy))), 1);
    3.65 +        dstheighthalf = MAX((int)
    3.66 +            SDL_ceil(MAX(MAX(MAX(SDL_fabs(sx + cy), SDL_fabs(sx - cy)), SDL_fabs(-sx + cy)), SDL_fabs(-sx - cy))), 1);
    3.67 +        *dstwidth = 2 * dstwidthhalf;
    3.68 +        *dstheight = 2 * dstheighthalf;
    3.69 +    }
    3.70  }
    3.71  
    3.72 +/* Computes source pointer X/Y increments for a rotation that's a multiple of 90 degrees. */
    3.73 +static void
    3.74 +computeSourceIncrements90(SDL_Surface * src, int bpp, int angle, int flipx, int flipy,
    3.75 +                          int *sincx, int *sincy, int *signx, int *signy)
    3.76 +{
    3.77 +    int pitch = flipy ? -src->pitch : src->pitch;
    3.78 +    if (flipx) {
    3.79 +        bpp = -bpp;
    3.80 +    }
    3.81 +    switch (angle) { /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
    3.82 +    case 0: *sincx = bpp; *sincy = pitch - src->w * *sincx; *signx = *signy = 1; break;
    3.83 +    case 1: *sincx = -pitch; *sincy = bpp - *sincx * src->h; *signx = 1; *signy = -1; break;
    3.84 +    case 2: *sincx = -bpp; *sincy = -src->w * *sincx - pitch; *signx = *signy = -1; break;
    3.85 +    case 3: default: *sincx = pitch; *sincy = -*sincx * src->h - bpp; *signx = -1; *signy = 1; break;
    3.86 +    }
    3.87 +    if (flipx) {
    3.88 +        *signx = -*signx;
    3.89 +    }
    3.90 +    if (flipy) {
    3.91 +        *signy = -*signy;
    3.92 +    }
    3.93 +}
    3.94 +
    3.95 +/* Performs a relatively fast rotation/flip when the angle is a multiple of 90 degrees. */
    3.96 +#define TRANSFORM_SURFACE_90(pixelType) \
    3.97 +    int dy, dincy = dst->pitch - dst->w*sizeof(pixelType), sincx, sincy, signx, signy;                      \
    3.98 +    Uint8 *sp = (Uint8*)src->pixels, *dp = (Uint8*)dst->pixels, *de;                                        \
    3.99 +                                                                                                            \
   3.100 +    computeSourceIncrements90(src, sizeof(pixelType), angle, flipx, flipy, &sincx, &sincy, &signx, &signy); \
   3.101 +    if (signx < 0) sp += (src->w-1)*sizeof(pixelType);                                                      \
   3.102 +    if (signy < 0) sp += (src->h-1)*src->pitch;                                                             \
   3.103 +                                                                                                            \
   3.104 +    for (dy = 0; dy < dst->h; sp += sincy, dp += dincy, dy++) {                                             \
   3.105 +        if (sincx == sizeof(pixelType)) { /* if advancing src and dest equally, use memcpy */               \
   3.106 +            SDL_memcpy(dp, sp, dst->w*sizeof(pixelType));                                                   \
   3.107 +            sp += dst->w*sizeof(pixelType);                                                                 \
   3.108 +            dp += dst->w*sizeof(pixelType);                                                                 \
   3.109 +        } else {                                                                                            \
   3.110 +            for (de = dp + dst->w*sizeof(pixelType); dp != de; sp += sincx, dp += sizeof(pixelType)) {      \
   3.111 +                *(pixelType*)dp = *(pixelType*)sp;                                                          \
   3.112 +            }                                                                                               \
   3.113 +        }                                                                                                   \
   3.114 +    }
   3.115 +
   3.116 +static void
   3.117 +transformSurfaceRGBA90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
   3.118 +{
   3.119 +    TRANSFORM_SURFACE_90(tColorRGBA);
   3.120 +}
   3.121 +
   3.122 +static void
   3.123 +transformSurfaceY90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
   3.124 +{
   3.125 +    TRANSFORM_SURFACE_90(tColorY);
   3.126 +}
   3.127 +
   3.128 +#undef TRANSFORM_SURFACE_90
   3.129  
   3.130  /* !
   3.131  \brief Internal 32 bit rotozoomer with optional anti-aliasing.
   3.132 @@ -341,7 +415,7 @@
   3.133  {
   3.134      SDL_Surface *rz_src;
   3.135      SDL_Surface *rz_dst;
   3.136 -    int is32bit;
   3.137 +    int is32bit, angle90;
   3.138      int i;
   3.139      Uint8 r = 0, g = 0, b = 0;
   3.140      Uint32 colorkey = 0;
   3.141 @@ -431,6 +505,18 @@
   3.142          SDL_LockSurface(rz_src);
   3.143      }
   3.144  
   3.145 +    /* check if the rotation is a multiple of 90 degrees so we can take a fast path and also somewhat reduce
   3.146 +     * the off-by-one problem in _transformSurfaceRGBA that expresses itself when the rotation is near
   3.147 +     * multiples of 90 degrees.
   3.148 +     */
   3.149 +    angle90 = (int)(angle/90);
   3.150 +    if (angle90 == angle/90) {
   3.151 +        angle90 %= 4;
   3.152 +        if (angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
   3.153 +    } else {
   3.154 +        angle90 = -1;
   3.155 +    }
   3.156 +
   3.157      /*
   3.158      * Check which kind of surface we have
   3.159      */
   3.160 @@ -438,10 +524,11 @@
   3.161          /*
   3.162          * Call the 32bit transformation routine to do the rotation (using alpha)
   3.163          */
   3.164 -        _transformSurfaceRGBA(rz_src, rz_dst, centerx, centery,
   3.165 -            (int) (sangleinv), (int) (cangleinv),
   3.166 -            flipx, flipy,
   3.167 -            smooth);
   3.168 +        if (angle90 >= 0) {
   3.169 +            transformSurfaceRGBA90(rz_src, rz_dst, angle90, flipx, flipy);
   3.170 +        } else {
   3.171 +            _transformSurfaceRGBA(rz_src, rz_dst, centerx, centery, (int) (sangleinv), (int) (cangleinv), flipx, flipy, smooth);
   3.172 +        }
   3.173          /*
   3.174          * Turn on source-alpha support
   3.175          */
   3.176 @@ -458,9 +545,11 @@
   3.177          /*
   3.178          * Call the 8bit transformation routine to do the rotation
   3.179          */
   3.180 -        transformSurfaceY(rz_src, rz_dst, centerx, centery,
   3.181 -            (int) (sangleinv), (int) (cangleinv),
   3.182 -            flipx, flipy);
   3.183 +        if(angle90 >= 0) {
   3.184 +            transformSurfaceY90(rz_src, rz_dst, angle90, flipx, flipy);
   3.185 +        } else {
   3.186 +            transformSurfaceY(rz_src, rz_dst, centerx, centery, (int)(sangleinv), (int)(cangleinv), flipx, flipy);
   3.187 +        }
   3.188          SDL_SetColorKey(rz_dst, /* SDL_SRCCOLORKEY */ SDL_TRUE | SDL_RLEACCEL, _colorkey(rz_src));
   3.189      }
   3.190