Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tiled 9-grid texture rendering function #12076

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions include/SDL3/SDL_render.h
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,42 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureTiled(SDL_Renderer *renderer,
*/
extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, 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 different
* corner sizes 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
* tiled 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 left_width the width, in pixels, of the left corners in `srcrect`.
* \param right_width the width, in pixels, of the right corners in `srcrect`.
* \param top_height the height, in pixels, of the top corners in `srcrect`.
* \param bottom_height the height, in pixels, of the bottom corners 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.
* \param tileScale the scale used to transform the borders and center of `srcrect` into the
* borders and middle of `dstrect`, or 1.0f for an unscaled copy.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*
* \threadsafety This function should only be called on the main thread.
*
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_RenderTexture
*/
extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale);

/**
* Render a list of triangles, optionally using a texture and indices into the
* vertex array Color and alpha modulation is done per vertex
Expand Down
137 changes: 137 additions & 0 deletions src/render/SDL_render.c
Original file line number Diff line number Diff line change
Expand Up @@ -4483,6 +4483,143 @@ bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const
return true;
}

bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale)
{
SDL_FRect full_src, full_dst;
SDL_FRect curr_src, curr_dst;
float dst_left_width;
float dst_right_width;
float dst_top_height;
float dst_bottom_height;

CHECK_RENDERER_MAGIC(renderer, false);
CHECK_TEXTURE_MAGIC(texture, false);

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_left_width = SDL_ceilf(left_width);
dst_right_width = SDL_ceilf(right_width);
dst_top_height = SDL_ceilf(top_height);
dst_bottom_height = SDL_ceilf(bottom_height);
} else {
dst_left_width = SDL_ceilf(left_width * scale);
dst_right_width = SDL_ceilf(right_width * scale);
dst_top_height = SDL_ceilf(top_height * scale);
dst_bottom_height = SDL_ceilf(bottom_height * scale);
}

// Center
curr_src.x = srcrect->x + left_width;
curr_src.y = srcrect->y + top_height;
curr_src.w = srcrect->w - left_width - right_width;
curr_src.h = srcrect->h - top_height - bottom_height;
curr_dst.x = dstrect->x + dst_left_width;
curr_dst.y = dstrect->y + dst_top_height;
curr_dst.w = dstrect->w - dst_left_width - dst_right_width;
curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height;
if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) {
return false;
}

// Upper-left corner
curr_src.x = srcrect->x;
curr_src.y = srcrect->y;
curr_src.w = left_width;
curr_src.h = top_height;
curr_dst.x = dstrect->x;
curr_dst.y = dstrect->y;
curr_dst.w = dst_left_width;
curr_dst.h = dst_top_height;
if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
return false;
}

// Upper-right corner
curr_src.x = srcrect->x + srcrect->w - right_width;
curr_src.w = right_width;
curr_dst.x = dstrect->x + dstrect->w - dst_right_width;
curr_dst.w = dst_right_width;
if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
return false;
}

// Lower-right corner
curr_src.y = srcrect->y + srcrect->h - bottom_height;
curr_src.h = bottom_height;
curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height;
curr_dst.h = dst_bottom_height;
if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
return false;
}

// Lower-left corner
curr_src.x = srcrect->x;
curr_src.w = left_width;
curr_dst.x = dstrect->x;
curr_dst.w = dst_left_width;
if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
return false;
}

// Left
curr_src.y = srcrect->y + top_height;
curr_src.h = srcrect->h - top_height - bottom_height;
curr_dst.y = dstrect->y + dst_top_height;
curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height;
if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) {
return false;
}

// Right
curr_src.x = srcrect->x + srcrect->w - right_width;
curr_src.w = right_width;
curr_dst.x = dstrect->x + dstrect->w - dst_right_width;
curr_dst.w = dst_right_width;
if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) {
return false;
}

// Top
curr_src.x = srcrect->x + left_width;
curr_src.y = srcrect->y;
curr_src.w = srcrect->w - left_width - right_width;
curr_src.h = top_height;
curr_dst.x = dstrect->x + dst_left_width;
curr_dst.y = dstrect->y;
curr_dst.w = dstrect->w - dst_left_width - dst_right_width;
curr_dst.h = dst_top_height;
if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) {
return false;
}

// Bottom
curr_src.y = srcrect->y + srcrect->h - bottom_height;
curr_src.h = bottom_height;
curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height;
curr_dst.h = dst_bottom_height;
if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) {
return false;
}

return true;
}

bool SDL_RenderGeometry(SDL_Renderer *renderer,
SDL_Texture *texture,
const SDL_Vertex *vertices, int num_vertices,
Expand Down
188 changes: 188 additions & 0 deletions test/testautomation_render.c
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,189 @@ static int SDLCALL render_testBlit9Grid(void *arg)
return TEST_COMPLETED;
}

/**
* Tests tiled 9-grid blitting.
*/
static int SDLCALL render_testBlit9GridTiled(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 == true, "Validate results from call to SDL_SetTextureScaleMode, expected: true, got: %i", ret);

/* Tiled 9-grid blit - 1.0 scale */
{
SDLTest_Log("tiled 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, 1, 1, 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_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, &rect, 1.0f);
SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret);

/* See if it's the same */
compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE);

/* Make current */
SDL_RenderPresent(renderer);
}

/* Tiled 9-grid blit - 2.0 scale */
{
SDLTest_Log("tiled 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, 2, 2, 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_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, &rect, 2.0f);
SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret);

/* See if it's the same */
compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE);

/* Make current */
SDL_RenderPresent(renderer);
}

/* Clean up. */
SDL_DestroySurface(source);
SDL_DestroyTexture(texture);

/* Create complex source surface */
source = SDL_CreateSurface(5, 5, SDL_PIXELFORMAT_RGBA32);
SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL");
SDL_WriteSurfacePixel(source, 0, 0, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 1, 0, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 2, 0, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 3, 0, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 4, 0, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255);

SDL_WriteSurfacePixel(source, 0, 1, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 1, 1, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 2, 1, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 3, 1, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 4, 1, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);

SDL_WriteSurfacePixel(source, 0, 2, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 1, 2, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 2, 2, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 3, 2, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 4, 2, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255);

SDL_WriteSurfacePixel(source, 0, 3, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 1, 3, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 2, 3, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 3, 3, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 4, 3, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);

SDL_WriteSurfacePixel(source, 0, 4, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 1, 4, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 2, 4, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 3, 4, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255);
SDL_WriteSurfacePixel(source, 4, 4, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * 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 == true, "Validate results from call to SDL_SetTextureScaleMode, expected: true, got: %i", ret);

/* complex tiled 9-grid blit - 1.0 scale */
{
SDLTest_Log("complex tiled 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, 2, 1, 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_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, &rect, 1.0f);
SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret);

/* See if it's the same */
compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE);

/* Make current */
SDL_RenderPresent(renderer);
}

/* complex tiled 9-grid blit - 2.0 scale */
{
SDLTest_Log("complex tiled 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, 4, 2, 4);

/* 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_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 2.0f, 1.0f, 2.0f, 2.0f, &rect, 2.0f);
SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, 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.
*
Expand Down Expand Up @@ -1539,6 +1722,10 @@ static const SDLTest_TestCaseReference renderTestBlit9Grid = {
render_testBlit9Grid, "render_testBlit9Grid", "Tests 9-grid blitting", TEST_ENABLED
};

static const SDLTest_TestCaseReference renderTestBlit9GridTiled = {
render_testBlit9GridTiled, "render_testBlit9GridTiled", "Tests tiled 9-grid blitting", TEST_ENABLED
};

static const SDLTest_TestCaseReference renderTestBlitColor = {
render_testBlitColor, "render_testBlitColor", "Tests blitting with color", TEST_ENABLED
};
Expand Down Expand Up @@ -1571,6 +1758,7 @@ static const SDLTest_TestCaseReference *renderTests[] = {
&renderTestBlit,
&renderTestBlitTiled,
&renderTestBlit9Grid,
&renderTestBlit9GridTiled,
&renderTestBlitColor,
&renderTestBlendModes,
&renderTestViewport,
Expand Down
Loading