Commit 8272ed18196e21bf24714e6b89ffc3ae73c36d80

Sam Lantinga 2014-08-16T23:25:02

Fixed bug 2687 - SDL_BlitScaled does not handle clipping correctly Patch from Benoit Pierre: video: fix clipping handling in SDL_UpperBlitScaled - honor destination clipping rectangle - update both destination and source rectangles when clipping source rectangle to source surface and destination rectangle to destination clip rectangle - don't change scaling factors when clipping N.B.: - when no scaling is involved (source and destination width/height are the same), SDL_UpperBlit is used (so SDL_BlitScaled behaves like SDL_BlitSurface) - the final destination rectangle after all clipping is performed is saved back to dstrect (like for SDL_UpperBlit)

diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c
index 421e2b8..3f4244b 100644
--- a/src/video/SDL_surface.c
+++ b/src/video/SDL_surface.c
@@ -26,7 +26,6 @@
 #include "SDL_RLEaccel_c.h"
 #include "SDL_pixels_c.h"
 
-
 /* Public routines */
 /*
  * Create an empty RGB surface of the appropriate depth
@@ -623,7 +622,12 @@ int
 SDL_UpperBlitScaled(SDL_Surface * src, const SDL_Rect * srcrect,
               SDL_Surface * dst, SDL_Rect * dstrect)
 {
-    SDL_Rect final_src, final_dst, fulldst;
+    double src_x0, src_y0, src_x1, src_y1;
+    double dst_x0, dst_y0, dst_x1, dst_y1;
+    SDL_Rect final_src, final_dst;
+    double scaling_w, scaling_h;
+    int src_w, src_h;
+    int dst_w, dst_h;
 
     /* Make sure the surfaces aren't locked */
     if (!src || !dst) {
@@ -633,78 +637,135 @@ SDL_UpperBlitScaled(SDL_Surface * src, const SDL_Rect * srcrect,
         return SDL_SetError("Surfaces must not be locked during blit");
     }
 
-    /* If the destination rectangle is NULL, use the entire dest surface */
-    if (dstrect == NULL) {
-        fulldst.x = fulldst.y = 0;
-        fulldst.w = dst->w;
-        fulldst.h = dst->h;
-        dstrect = &fulldst;
+    if (NULL == srcrect) {
+        src_w = src->w;
+        src_h = src->h;
+    } else {
+        src_w = srcrect->w;
+        src_h = srcrect->h;
     }
 
-    /* clip the source rectangle to the source surface */
-    if (srcrect) {
-        int maxw, maxh;
+    if (NULL == dstrect) {
+        dst_w = dst->w;
+        dst_h = dst->h;
+    } else {
+        dst_w = dstrect->w;
+        dst_h = dstrect->h;
+    }
 
-        final_src.x = srcrect->x;
-        final_src.w = srcrect->w;
-        if (final_src.x < 0) {
-            final_src.w += final_src.x;
-            final_src.x = 0;
-        }
-        maxw = src->w - final_src.x;
-        if (maxw < final_src.w)
-            final_src.w = maxw;
-
-        final_src.y = srcrect->y;
-        final_src.h = srcrect->h;
-        if (final_src.y < 0) {
-            final_src.h += final_src.y;
-            final_src.y = 0;
-        }
-        maxh = src->h - final_src.y;
-        if (maxh < final_src.h)
-            final_src.h = maxh;
+    if (dst_w == src_w && dst_h == src_h) {
+        /* No scaling, defer to regular blit */
+        return SDL_BlitSurface(src, srcrect, dst, dstrect);
+    }
+
+    scaling_w = (double)dst_w / src_w;
+    scaling_h = (double)dst_h / src_h;
 
+    if (NULL == dstrect) {
+        dst_x0 = 0;
+        dst_y0 = 0;
+        dst_x1 = dst_w - 1;
+        dst_y1 = dst_h - 1;
     } else {
-        final_src.x = final_src.y = 0;
-        final_src.w = src->w;
-        final_src.h = src->h;
+        dst_x0 = dstrect->x;
+        dst_y0 = dstrect->y;
+        dst_x1 = dst_x0 + dst_w - 1;
+        dst_y1 = dst_y0 + dst_h - 1;
     }
 
-    /* clip the destination rectangle against the clip rectangle */
-    if (dstrect) {
-        int maxw, maxh;
+    if (NULL == srcrect) {
+        src_x0 = 0;
+        src_y0 = 0;
+        src_x1 = src_w - 1;
+        src_y1 = src_h - 1;
+    } else {
+        src_x0 = srcrect->x;
+        src_y0 = srcrect->y;
+        src_x1 = src_x0 + src_w - 1;
+        src_y1 = src_y0 + src_h - 1;
+
+        /* Clip source rectangle to the source surface */
 
-        final_dst.x = dstrect->x;
-        final_dst.w = dstrect->w;
-        if (final_dst.x < 0) {
-            final_dst.w += final_dst.x;
-            final_dst.x = 0;
+        if (src_x0 < 0) {
+            dst_x0 -= src_x0 * scaling_w;
+            src_x0 = 0;
         }
-        maxw = dst->w - final_dst.x;
-        if (maxw < final_dst.w)
-            final_dst.w = maxw;
-
-        final_dst.y = dstrect->y;
-        final_dst.h = dstrect->h;
-        if (final_dst.y < 0) {
-            final_dst.h += final_dst.y;
-            final_dst.y = 0;
+
+        if (src_x1 >= src->w) {
+            dst_x1 -= (src_x1 - src->w + 1) * scaling_w;
+            src_x1 = src->w - 1;
+        }
+
+        if (src_y0 < 0) {
+            dst_y0 -= src_y0 * scaling_h;
+            src_y0 = 0;
+        }
+
+        if (src_y1 >= src->h) {
+            dst_y1 -= (src_y1 - src->h + 1) * scaling_h;
+            src_y1 = src->h - 1;
         }
-        maxh = dst->h - final_dst.y;
-        if (maxh < final_dst.h)
-            final_dst.h = maxh;
-    } else {
-        final_dst.x = final_dst.y = 0;
-        final_dst.w = dst->w;
-        final_dst.h = dst->h;
     }
 
-    if (final_dst.w > 0 && final_dst.h > 0) {
-        return SDL_LowerBlitScaled(src, &final_src, dst, &final_dst);
+    /* Clip destination rectangle to the clip rectangle */
+
+    /* Translate to clip space for easier calculations */
+    dst_x0 -= dst->clip_rect.x;
+    dst_x1 -= dst->clip_rect.x;
+    dst_y0 -= dst->clip_rect.y;
+    dst_y1 -= dst->clip_rect.y;
+
+    if (dst_x0 < 0) {
+        src_x0 -= dst_x0 / scaling_w;
+        dst_x0 = 0;
     }
 
-    return 0;
+    if (dst_x1 >= dst->clip_rect.w) {
+        src_x1 -= (dst_x1 - dst->clip_rect.w + 1) / scaling_w;
+        dst_x1 = dst->clip_rect.w - 1;
+    }
+
+    if (dst_y0 < 0) {
+        src_y0 -= dst_y0 / scaling_h;
+        dst_y0 = 0;
+    }
+
+    if (dst_y1 >= dst->clip_rect.h) {
+        src_y1 -= (dst_y1 - dst->clip_rect.h + 1) / scaling_h;
+        dst_y1 = dst->clip_rect.h - 1;
+    }
+
+    /* Translate back to surface coordinates */
+    dst_x0 += dst->clip_rect.x;
+    dst_x1 += dst->clip_rect.x;
+    dst_y0 += dst->clip_rect.y;
+    dst_y1 += dst->clip_rect.y;
+
+    final_src.x = SDL_round(src_x0);
+    final_src.y = SDL_round(src_y0);
+    final_src.w = SDL_round(src_x1 - src_x0 + 1);
+    final_src.h = SDL_round(src_y1 - src_y0 + 1);
+
+    final_dst.x = SDL_round(dst_x0);
+    final_dst.y = SDL_round(dst_y0);
+    final_dst.w = SDL_round(dst_x1 - dst_x0 + 1);
+    final_dst.h = SDL_round(dst_y1 - dst_y0 + 1);
+
+    if (final_dst.w < 0)
+        final_dst.w = 0;
+    if (final_dst.h < 0)
+        final_dst.h = 0;
+
+    if (dstrect)
+        *dstrect = final_dst;
+
+    if (final_dst.w == 0 || final_dst.h == 0 ||
+        final_src.w <= 0 || final_src.h <= 0) {
+        /* No-op. */
+        return 0;
+    }
+
+    return SDL_LowerBlitScaled(src, &final_src, dst, &final_dst);
 }
 
 /**
@@ -721,43 +782,6 @@ SDL_LowerBlitScaled(SDL_Surface * src, SDL_Rect * srcrect,
         SDL_COPY_COLORKEY
     );
 
-    /* Save off the original dst width, height */
-    int dstW = dstrect->w;
-    int dstH = dstrect->h;
-    SDL_Rect full_rect;
-    SDL_Rect final_dst = *dstrect;
-    SDL_Rect final_src = *srcrect;
-
-    /* Clip the dst surface to the dstrect */
-    full_rect.x = 0;
-    full_rect.y = 0;
-    full_rect.w = dst->w;
-    full_rect.h = dst->h;
-    if (!SDL_IntersectRect(&final_dst, &full_rect, &final_dst)) {
-        return 0;
-    }
-
-    /* Did the dst width change? */
-    if ( dstW != final_dst.w ) {
-        /* scale the src width appropriately */
-        final_src.w = final_src.w * dst->clip_rect.w / dstW;
-    }
-
-    /* Did the dst height change? */
-    if ( dstH != final_dst.h ) {
-        /* scale the src width appropriately */
-        final_src.h = final_src.h * dst->clip_rect.h / dstH;
-    }
-
-    /* Clip the src surface to the srcrect */
-    full_rect.x = 0;
-    full_rect.y = 0;
-    full_rect.w = src->w;
-    full_rect.h = src->h;
-    if (!SDL_IntersectRect(&final_src, &full_rect, &final_src)) {
-        return 0;
-    }
-
     if (!(src->map->info.flags & SDL_COPY_NEAREST)) {
         src->map->info.flags |= SDL_COPY_NEAREST;
         SDL_InvalidateMap(src->map);
@@ -766,9 +790,9 @@ SDL_LowerBlitScaled(SDL_Surface * src, SDL_Rect * srcrect,
     if ( !(src->map->info.flags & complex_copy_flags) &&
          src->format->format == dst->format->format &&
          !SDL_ISPIXELFORMAT_INDEXED(src->format->format) ) {
-        return SDL_SoftStretch( src, &final_src, dst, &final_dst );
+        return SDL_SoftStretch( src, srcrect, dst, dstrect );
     } else {
-        return SDL_LowerBlit( src, &final_src, dst, &final_dst );
+        return SDL_LowerBlit( src, srcrect, dst, dstrect );
     }
 }