Browse Source

Added SDL_BlitSurface9Grid() and SDL_RenderTexture9Grid()

Sam Lantinga 9 months ago
parent
commit
6209c71f54

+ 22 - 0
include/SDL3/SDL_render.h

@@ -1898,6 +1898,28 @@ extern SDL_DECLSPEC int SDLCALL SDL_RenderTextureRotated(SDL_Renderer *renderer,
  */
 extern SDL_DECLSPEC int SDLCALL SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect);
 
+/**
+ * Perform a scaled copy using the 9-grid algorithm to the current rendering target at subpixel precision.
+ *
+ * The pixels in the texture are split into a 3x3 grid, using the corner size for each corner, and the sides and center making up the remaining pixels. The corners are then scaled using `scale` and fit into the corners of the destination rectangle. The sides and center are then stretched into place to cover the remaining destination rectangle.
+ *
+ * \param renderer the renderer which should copy parts of a texture.
+ * \param texture the source texture.
+ * \param srcrect the SDL_Rect structure representing the rectangle to be
+ *                used for the 9-grid, or NULL to use the entire texture.
+ * \param corner_size the size, in pixels, of the corner in `srcrect`.
+ * \param scale the scale used to transform the corner of `srcrect` into the corner of `dstrect`, or 0.0f for an unscaled copy.
+ * \param dstrect a pointer to the destination rectangle, or NULL for the
+ *                entire rendering target.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_RenderTexture
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float corner_size, float scale, const SDL_FRect *dstrect);
+
 /**
  * Render a list of triangles, optionally using a texture and indices into the
  * vertex array Color and alpha modulation is done per vertex

+ 28 - 0
include/SDL3/SDL_surface.h

@@ -1099,6 +1099,34 @@ extern SDL_DECLSPEC int SDLCALL SDL_BlitSurfaceTiled(SDL_Surface *src, const SDL
  */
 extern SDL_DECLSPEC int SDLCALL SDL_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect);
 
+/**
+ * Perform a scaled blit using the 9-grid algorithm to a destination surface, which may be of a different
+ * format.
+ *
+ * The pixels in the source surface are split into a 3x3 grid, using the corner size for each corner, and the sides and center making up the remaining pixels. The corners are then scaled using `scale` and fit into the corners of the destination rectangle. The sides and center are then stretched into place to cover the remaining destination rectangle.
+ *
+ * \param src the SDL_Surface structure to be copied from.
+ * \param srcrect the SDL_Rect structure representing the rectangle to be
+ *                used for the 9-grid, or NULL to use the entire surface.
+ * \param corner_size the size, in pixels, of the corner in `srcrect`.
+ * \param scale the scale used to transform the corner of `srcrect` into the corner of `dstrect`, or 0.0f for an unscaled blit.
+ * \param scaleMode scale algorithm to be used.
+ * \param dst the SDL_Surface structure that is the blit target.
+ * \param dstrect the SDL_Rect structure representing the target rectangle in
+ *                the destination surface, or NULL to fill the entire surface.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \threadsafety The same destination surface should not be used from two
+ *               threads at once. It is safe to use the same source surface
+ *               from multiple threads.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_BlitSurface
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_BlitSurface9Grid(SDL_Surface *src, const SDL_Rect *srcrect, int corner_size, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect);
+
 /**
  * Map an RGB triple to an opaque pixel value for a surface.
  *

+ 2 - 0
src/dynapi/SDL_dynapi.sym

@@ -23,6 +23,7 @@ SDL3_0.0.0 {
     SDL_AudioDevicePaused;
     SDL_BindAudioStream;
     SDL_BindAudioStreams;
+    SDL_BlitSurface9Grid;
     SDL_BlitSurface;
     SDL_BlitSurfaceScaled;
     SDL_BlitSurfaceTiled;
@@ -662,6 +663,7 @@ SDL3_0.0.0 {
     SDL_RenderReadPixels;
     SDL_RenderRect;
     SDL_RenderRects;
+    SDL_RenderTexture9Grid;
     SDL_RenderTexture;
     SDL_RenderTextureRotated;
     SDL_RenderTextureTiled;

+ 2 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -49,6 +49,7 @@
 #define SDL_BindAudioStream SDL_BindAudioStream_REAL
 #define SDL_BindAudioStreams SDL_BindAudioStreams_REAL
 #define SDL_BlitSurface SDL_BlitSurface_REAL
+#define SDL_BlitSurface9Grid SDL_BlitSurface9Grid_REAL
 #define SDL_BlitSurfaceScaled SDL_BlitSurfaceScaled_REAL
 #define SDL_BlitSurfaceTiled SDL_BlitSurfaceTiled_REAL
 #define SDL_BlitSurfaceTiledWithScale SDL_BlitSurfaceTiledWithScale_REAL
@@ -688,6 +689,7 @@
 #define SDL_RenderRect SDL_RenderRect_REAL
 #define SDL_RenderRects SDL_RenderRects_REAL
 #define SDL_RenderTexture SDL_RenderTexture_REAL
+#define SDL_RenderTexture9Grid SDL_RenderTexture9Grid_REAL
 #define SDL_RenderTextureRotated SDL_RenderTextureRotated_REAL
 #define SDL_RenderTextureTiled SDL_RenderTextureTiled_REAL
 #define SDL_RenderViewportSet SDL_RenderViewportSet_REAL

+ 2 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -69,6 +69,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_AudioDevicePaused,(SDL_AudioDeviceID a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_BindAudioStream,(SDL_AudioDeviceID a, SDL_AudioStream *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_BindAudioStreams,(SDL_AudioDeviceID a, SDL_AudioStream **b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_BlitSurface,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(int,SDL_BlitSurface9Grid,(SDL_Surface *a, const SDL_Rect *b, int c, float d, SDL_ScaleMode e, SDL_Surface *f, const SDL_Rect *g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(int,SDL_BlitSurfaceScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d, SDL_ScaleMode e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(int,SDL_BlitSurfaceTiled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_BlitSurfaceTiledWithScale,(SDL_Surface *a, const SDL_Rect *b, float c, SDL_ScaleMode d, SDL_Surface *e, const SDL_Rect *f),(a,b,c,d,e,f),return)
@@ -699,6 +700,7 @@ SDL_DYNAPI_PROC(SDL_Surface*,SDL_RenderReadPixels,(SDL_Renderer *a, const SDL_Re
 SDL_DYNAPI_PROC(int,SDL_RenderRect,(SDL_Renderer *a, const SDL_FRect *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_RenderRects,(SDL_Renderer *a, const SDL_FRect *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_RenderTexture,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, const SDL_FRect *d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(int,SDL_RenderTexture9Grid,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, float d, float e, const SDL_FRect *f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(int,SDL_RenderTextureRotated,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, const SDL_FRect *d, const double e, const SDL_FPoint *f, const SDL_FlipMode g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(int,SDL_RenderTextureTiled,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, float d, const SDL_FRect *e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_RenderViewportSet,(SDL_Renderer *a),(a),return)

+ 117 - 0
src/render/SDL_render.c

@@ -4160,6 +4160,123 @@ int SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const S
     }
 }
 
+int SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float corner_size, float scale, const SDL_FRect *dstrect)
+{
+    SDL_FRect full_src, full_dst;
+    SDL_FRect curr_src, curr_dst;
+    float dst_corner_size;
+
+    CHECK_RENDERER_MAGIC(renderer, -1);
+    CHECK_TEXTURE_MAGIC(texture, -1);
+
+    if (renderer != texture->renderer) {
+        return SDL_SetError("Texture was not created with this renderer");
+    }
+
+    if (!srcrect) {
+        full_src.x = 0;
+        full_src.y = 0;
+        full_src.w = (float)texture->w;
+        full_src.h = (float)texture->h;
+        srcrect = &full_src;
+    }
+
+    if (!dstrect) {
+        GetRenderViewportSize(renderer, &full_dst);
+        dstrect = &full_dst;
+    }
+
+    if (scale <= 0.0f || scale == 1.0f) {
+        dst_corner_size = corner_size;
+    } else {
+        dst_corner_size = (corner_size * scale);
+    }
+
+    // Upper-left corner
+    curr_src.x = srcrect->x;
+    curr_src.y = srcrect->y;
+    curr_src.w = corner_size;
+    curr_src.h = corner_size;
+    curr_dst.x = dstrect->x;
+    curr_dst.y = dstrect->y;
+    curr_dst.w = dst_corner_size;
+    curr_dst.h = dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Upper-right corner
+    curr_src.x = srcrect->x + srcrect->w - corner_size;
+    curr_dst.x = dstrect->x + dstrect->w - dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Lower-right corner
+    curr_src.y = srcrect->y + srcrect->h - corner_size;
+    curr_dst.y = dstrect->y + dstrect->h - dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Lower-left corner
+    curr_src.x = srcrect->x;
+    curr_dst.x = dstrect->x;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Left
+    curr_src.y = srcrect->y + corner_size;
+    curr_src.h = srcrect->h - 2 * corner_size;
+    curr_dst.y = dstrect->y + dst_corner_size;
+    curr_dst.h = dstrect->h - 2 * dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Right
+    curr_src.x = srcrect->x + srcrect->w - corner_size;
+    curr_dst.x = dstrect->x + dstrect->w - dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Top
+    curr_src.x = srcrect->x + corner_size;
+    curr_src.y = srcrect->y;
+    curr_src.w = srcrect->w - 2 * corner_size;
+    curr_src.h = corner_size;
+    curr_dst.x = dstrect->x + dst_corner_size;
+    curr_dst.y = dstrect->y;
+    curr_dst.w = dstrect->w - 2 * dst_corner_size;
+    curr_dst.h = dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Bottom
+    curr_src.y = srcrect->y + srcrect->h - corner_size;
+    curr_dst.y = dstrect->y + dstrect->h - dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    // Center
+    curr_src.x = srcrect->x + corner_size;
+    curr_src.y = srcrect->y + corner_size;
+    curr_src.w = srcrect->w - 2 * corner_size;
+    curr_src.h = srcrect->h - 2 * corner_size;
+    curr_dst.x = dstrect->x + dst_corner_size;
+    curr_dst.y = dstrect->y + dst_corner_size;
+    curr_dst.w = dstrect->w - 2 * dst_corner_size;
+    curr_dst.h = dstrect->h - 2 * dst_corner_size;
+    if (SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
 int SDL_RenderGeometry(SDL_Renderer *renderer,
                        SDL_Texture *texture,
                        const SDL_Vertex *vertices, int num_vertices,

+ 121 - 0
src/video/SDL_surface.c

@@ -1444,6 +1444,127 @@ int SDL_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, flo
     return 0;
 }
 
+int SDL_BlitSurface9Grid(SDL_Surface *src, const SDL_Rect *srcrect, int corner_size, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)
+{
+    SDL_Rect full_src, full_dst;
+    SDL_Rect curr_src, curr_dst;
+    int dst_corner_size;
+
+    /* Make sure the surfaces aren't locked */
+    if (!SDL_SurfaceValid(src)) {
+        return SDL_InvalidParamError("src");
+    } else if (!SDL_SurfaceValid(dst)) {
+        return SDL_InvalidParamError("dst");
+    }
+
+    if (!srcrect) {
+        full_src.x = 0;
+        full_src.y = 0;
+        full_src.w = src->w;
+        full_src.h = src->h;
+        srcrect = &full_src;
+    }
+
+    if (!dstrect) {
+        full_dst.x = 0;
+        full_dst.y = 0;
+        full_dst.w = dst->w;
+        full_dst.h = dst->h;
+        dstrect = &full_dst;
+    }
+
+    if (scale <= 0.0f || scale == 1.0f) {
+        dst_corner_size = corner_size;
+    } else {
+        dst_corner_size = (int)SDL_roundf(corner_size * scale);
+    }
+
+    // Upper-left corner
+    curr_src.x = srcrect->x;
+    curr_src.y = srcrect->y;
+    curr_src.w = corner_size;
+    curr_src.h = corner_size;
+    curr_dst.x = dstrect->x;
+    curr_dst.y = dstrect->y;
+    curr_dst.w = dst_corner_size;
+    curr_dst.h = dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Upper-right corner
+    curr_src.x = srcrect->x + srcrect->w - corner_size;
+    curr_dst.x = dstrect->x + dstrect->w - dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Lower-right corner
+    curr_src.y = srcrect->y + srcrect->h - corner_size;
+    curr_dst.y = dstrect->y + dstrect->h - dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Lower-left corner
+    curr_src.x = srcrect->x;
+    curr_dst.x = dstrect->x;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Left
+    curr_src.y = srcrect->y + corner_size;
+    curr_src.h = srcrect->h - 2 * corner_size;
+    curr_dst.y = dstrect->y + dst_corner_size;
+    curr_dst.h = dstrect->h - 2 * dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Right
+    curr_src.x = srcrect->x + srcrect->w - corner_size;
+    curr_dst.x = dstrect->x + dstrect->w - dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Top
+    curr_src.x = srcrect->x + corner_size;
+    curr_src.y = srcrect->y;
+    curr_src.w = srcrect->w - 2 * corner_size;
+    curr_src.h = corner_size;
+    curr_dst.x = dstrect->x + dst_corner_size;
+    curr_dst.y = dstrect->y;
+    curr_dst.w = dstrect->w - 2 * dst_corner_size;
+    curr_dst.h = dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Bottom
+    curr_src.y = srcrect->y + srcrect->h - corner_size;
+    curr_dst.y = dstrect->y + dstrect->h - dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    // Center
+    curr_src.x = srcrect->x + corner_size;
+    curr_src.y = srcrect->y + corner_size;
+    curr_src.w = srcrect->w - 2 * corner_size;
+    curr_src.h = srcrect->h - 2 * corner_size;
+    curr_dst.x = dstrect->x + dst_corner_size;
+    curr_dst.y = dstrect->y + dst_corner_size;
+    curr_dst.w = dstrect->w - 2 * dst_corner_size;
+    curr_dst.h = dstrect->h - 2 * dst_corner_size;
+    if (SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
 /*
  * Lock a surface to directly access the pixels
  */

+ 160 - 0
test/testautomation_render.c

@@ -365,6 +365,161 @@ static int render_testBlitTiled(void *arg)
     return TEST_COMPLETED;
 }
 
+static const Uint8 COLOR_SEPARATION = 85;
+
+static void Fill9GridReferenceSurface(SDL_Surface *surface, int corner_size)
+{
+    SDL_Rect rect;
+
+    // Upper left
+    rect.x = 0;
+    rect.y = 0;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 1 * COLOR_SEPARATION, 1 * COLOR_SEPARATION, 0));
+
+    // Top
+    rect.x = corner_size;
+    rect.y = 0;
+    rect.w = surface->w - 2 * corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 2 * COLOR_SEPARATION, 1 * COLOR_SEPARATION, 0));
+
+    // Upper right
+    rect.x = surface->w - corner_size;
+    rect.y = 0;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 3 * COLOR_SEPARATION, 1 * COLOR_SEPARATION, 0));
+
+    // Left
+    rect.x = 0;
+    rect.y = corner_size;
+    rect.w = corner_size;
+    rect.h = surface->h - 2 * corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 1 * COLOR_SEPARATION, 2 * COLOR_SEPARATION, 0));
+
+    // Center
+    rect.x = corner_size;
+    rect.y = corner_size;
+    rect.w = surface->w - 2 * corner_size;
+    rect.h = surface->h - 2 * corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 2 * COLOR_SEPARATION, 2 * COLOR_SEPARATION, 0));
+
+    // Right
+    rect.x = surface->w - corner_size;
+    rect.y = corner_size;
+    rect.w = corner_size;
+    rect.h = surface->h - 2 * corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 3 * COLOR_SEPARATION, 2 * COLOR_SEPARATION, 0));
+
+    // Lower left
+    rect.x = 0;
+    rect.y = surface->h - corner_size;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 1 * COLOR_SEPARATION, 3 * COLOR_SEPARATION, 0));
+
+    // Bottom
+    rect.x = corner_size;
+    rect.y = surface->h - corner_size;
+    rect.w = surface->w - 2 * corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 2 * COLOR_SEPARATION, 3 * COLOR_SEPARATION, 0));
+
+    // Lower right
+    rect.x = surface->w - corner_size;
+    rect.y = surface->h - corner_size;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 3 * COLOR_SEPARATION, 3 * COLOR_SEPARATION, 0));
+}
+
+/**
+ *  Tests 9-grid blitting.
+ */
+static int render_testBlit9Grid(void *arg)
+{
+    SDL_Surface *referenceSurface = NULL;
+    SDL_Surface *source = NULL;
+    SDL_Texture *texture;
+    int x, y;
+    SDL_FRect rect;
+    int ret = 0;
+
+    /* Create source surface */
+    source = SDL_CreateSurface(3, 3, SDL_PIXELFORMAT_RGBA32);
+    SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL");
+    for (y = 0; y < 3; ++y) {
+        for (x = 0; x < 3; ++x) {
+            SDL_WriteSurfacePixel(source, x, y, (Uint8)((1 + x) * COLOR_SEPARATION), (Uint8)((1 + y) * COLOR_SEPARATION), 0, 255);
+        }
+    }
+    texture = SDL_CreateTextureFromSurface(renderer, source);
+    SDLTest_AssertCheck(texture != NULL, "Verify source texture is not NULL");
+    ret = SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
+    SDLTest_AssertCheck(ret == 0, "Validate results from call to SDL_SetTextureScaleMode, expected: 0, got: %i", ret);
+
+    /* 9-grid blit - 1.0 scale */
+    {
+        /* Create reference surface */
+        SDL_DestroySurface(referenceSurface);
+        referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32);
+        SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL");
+        Fill9GridReferenceSurface(referenceSurface, 1);
+
+        /* Clear surface. */
+        clearScreen();
+
+        /* Tiled blit. */
+        rect.x = 0.0f;
+        rect.y = 0.0f;
+        rect.w = (float)TESTRENDER_SCREEN_W;
+        rect.h = (float)TESTRENDER_SCREEN_H;
+        ret = SDL_RenderTexture9Grid(renderer, texture, NULL, 1.0f, 1.0f, &rect);
+        SDLTest_AssertCheck(ret == 0, "Validate results from call to SDL_RenderTexture9Grid, expected: 0, got: %i", ret);
+
+        /* See if it's the same */
+        compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE);
+
+        /* Make current */
+        SDL_RenderPresent(renderer);
+    }
+
+    /* 9-grid blit - 2.0 scale */
+    {
+        /* Create reference surface */
+        SDL_DestroySurface(referenceSurface);
+        referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32);
+        SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL");
+        Fill9GridReferenceSurface(referenceSurface, 2);
+
+        /* Clear surface. */
+        clearScreen();
+
+        /* Tiled blit. */
+        rect.x = 0.0f;
+        rect.y = 0.0f;
+        rect.w = (float)TESTRENDER_SCREEN_W;
+        rect.h = (float)TESTRENDER_SCREEN_H;
+        ret = SDL_RenderTexture9Grid(renderer, texture, NULL, 1.0f, 2.0f, &rect);
+        SDLTest_AssertCheck(ret == 0, "Validate results from call to SDL_RenderTexture9Grid, expected: 0, got: %i", ret);
+
+        /* See if it's the same */
+        compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE);
+
+        /* Make current */
+        SDL_RenderPresent(renderer);
+    }
+
+    /* Clean up. */
+    SDL_DestroySurface(referenceSurface);
+    SDL_DestroySurface(source);
+    SDL_DestroyTexture(texture);
+
+    return TEST_COMPLETED;
+}
+
 /**
  * Blits doing color tests.
  *
@@ -1306,6 +1461,10 @@ static const SDLTest_TestCaseReference renderTestBlitTiled = {
     (SDLTest_TestCaseFp)render_testBlitTiled, "render_testBlitTiled", "Tests tiled blitting", TEST_ENABLED
 };
 
+static const SDLTest_TestCaseReference renderTestBlit9Grid = {
+    (SDLTest_TestCaseFp)render_testBlit9Grid, "render_testBlit9Grid", "Tests 9-grid blitting", TEST_ENABLED
+};
+
 static const SDLTest_TestCaseReference renderTestBlitColor = {
     (SDLTest_TestCaseFp)render_testBlitColor, "render_testBlitColor", "Tests blitting with color", TEST_ENABLED
 };
@@ -1337,6 +1496,7 @@ static const SDLTest_TestCaseReference *renderTests[] = {
     &renderTestPrimitivesWithViewport,
     &renderTestBlit,
     &renderTestBlitTiled,
+    &renderTestBlit9Grid,
     &renderTestBlitColor,
     &renderTestBlendModes,
     &renderTestViewport,

+ 129 - 0
test/testautomation_surface.c

@@ -415,6 +415,130 @@ static int surface_testBlitTiled(void *arg)
     return TEST_COMPLETED;
 }
 
+static const Uint8 COLOR_SEPARATION = 85;
+
+static void Fill9GridReferenceSurface(SDL_Surface *surface, int corner_size)
+{
+    SDL_Rect rect;
+
+    // Upper left
+    rect.x = 0;
+    rect.y = 0;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 1 * COLOR_SEPARATION, 1 * COLOR_SEPARATION, 0));
+
+    // Top
+    rect.x = corner_size;
+    rect.y = 0;
+    rect.w = surface->w - 2 * corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 2 * COLOR_SEPARATION, 1 * COLOR_SEPARATION, 0));
+
+    // Upper right
+    rect.x = surface->w - corner_size;
+    rect.y = 0;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 3 * COLOR_SEPARATION, 1 * COLOR_SEPARATION, 0));
+
+    // Left
+    rect.x = 0;
+    rect.y = corner_size;
+    rect.w = corner_size;
+    rect.h = surface->h - 2 * corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 1 * COLOR_SEPARATION, 2 * COLOR_SEPARATION, 0));
+
+    // Center
+    rect.x = corner_size;
+    rect.y = corner_size;
+    rect.w = surface->w - 2 * corner_size;
+    rect.h = surface->h - 2 * corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 2 * COLOR_SEPARATION, 2 * COLOR_SEPARATION, 0));
+
+    // Right
+    rect.x = surface->w - corner_size;
+    rect.y = corner_size;
+    rect.w = corner_size;
+    rect.h = surface->h - 2 * corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 3 * COLOR_SEPARATION, 2 * COLOR_SEPARATION, 0));
+
+    // Lower left
+    rect.x = 0;
+    rect.y = surface->h - corner_size;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 1 * COLOR_SEPARATION, 3 * COLOR_SEPARATION, 0));
+
+    // Bottom
+    rect.x = corner_size;
+    rect.y = surface->h - corner_size;
+    rect.w = surface->w - 2 * corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 2 * COLOR_SEPARATION, 3 * COLOR_SEPARATION, 0));
+
+    // Lower right
+    rect.x = surface->w - corner_size;
+    rect.y = surface->h - corner_size;
+    rect.w = corner_size;
+    rect.h = corner_size;
+    SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 3 * COLOR_SEPARATION, 3 * COLOR_SEPARATION, 0));
+}
+
+/**
+ *  Tests 9-grid blitting.
+ */
+static int surface_testBlit9Grid(void *arg)
+{
+    SDL_Surface *source = NULL;
+    int x, y;
+    int ret = 0;
+
+    /* Create source surface */
+    source = SDL_CreateSurface(3, 3, SDL_PIXELFORMAT_RGBA32);
+    SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL");
+    for (y = 0; y < 3; ++y) {
+        for (x = 0; x < 3; ++x) {
+            SDL_WriteSurfacePixel(source, x, y, (Uint8)((1 + x) * COLOR_SEPARATION), (Uint8)((1 + y) * COLOR_SEPARATION), 0, 255);
+        }
+    }
+
+    /* 9-grid blit - 1.0 scale */
+    {
+        /* Create reference surface */
+        SDL_DestroySurface(referenceSurface);
+        referenceSurface = SDL_CreateSurface(testSurface->w, testSurface->h, testSurface->format);
+        SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL");
+        Fill9GridReferenceSurface(referenceSurface, 1);
+
+        ret = SDL_BlitSurface9Grid(source, NULL, 1, 0.0f, SDL_SCALEMODE_NEAREST, testSurface, NULL);
+        SDLTest_AssertCheck(ret == 0, "Validate result from SDL_BlitSurface9Grid, expected: 0, got: %i", ret);
+
+        ret = SDLTest_CompareSurfaces(testSurface, referenceSurface, 0);
+        SDLTest_AssertCheck(ret == 0, "Validate result from SDLTest_CompareSurfaces, expected: 0, got: %i", ret);
+    }
+
+    /* 9-grid blit - 2.0 scale */
+    {
+        /* Create reference surface */
+        SDL_DestroySurface(referenceSurface);
+        referenceSurface = SDL_CreateSurface(testSurface->w, testSurface->h, testSurface->format);
+        SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL");
+        Fill9GridReferenceSurface(referenceSurface, 2);
+
+        ret = SDL_BlitSurface9Grid(source, NULL, 1, 2.0f, SDL_SCALEMODE_NEAREST, testSurface, NULL);
+        SDLTest_AssertCheck(ret == 0, "Validate result from SDL_BlitSurface9Grid, expected: 0, got: %i", ret);
+
+        ret = SDLTest_CompareSurfaces(testSurface, referenceSurface, 0);
+        SDLTest_AssertCheck(ret == 0, "Validate result from SDLTest_CompareSurfaces, expected: 0, got: %i", ret);
+    }
+
+    /* Clean up. */
+    SDL_DestroySurface(source);
+
+    return TEST_COMPLETED;
+}
+
 /**
  *  Tests surface conversion.
  */
@@ -1099,6 +1223,10 @@ static const SDLTest_TestCaseReference surfaceTestBlitTiled = {
     (SDLTest_TestCaseFp)surface_testBlitTiled, "surface_testBlitTiled", "Tests tiled blitting.", TEST_ENABLED
 };
 
+static const SDLTest_TestCaseReference surfaceTestBlit9Grid = {
+    (SDLTest_TestCaseFp)surface_testBlit9Grid, "surface_testBlit9Grid", "Tests 9-grid blitting.", TEST_ENABLED
+};
+
 static const SDLTest_TestCaseReference surfaceTestLoadFailure = {
     (SDLTest_TestCaseFp)surface_testLoadFailure, "surface_testLoadFailure", "Tests sprite loading. A failure case.", TEST_ENABLED
 };
@@ -1168,6 +1296,7 @@ static const SDLTest_TestCaseReference *surfaceTests[] = {
     &surfaceTestSaveLoadBitmap,
     &surfaceTestBlit,
     &surfaceTestBlitTiled,
+    &surfaceTestBlit9Grid,
     &surfaceTestLoadFailure,
     &surfaceTestSurfaceConversion,
     &surfaceTestCompleteSurfaceConversion,