diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index 70dfa9da62760c..b20104c6d01dca 100644 --- a/include/SDL3/SDL_render.h +++ b/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 diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index df035a8798adde..064ab26a9e299b 100644 --- a/include/SDL3/SDL_surface.h +++ b/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. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 98c9e98c2f3f7e..513016f21bd1b3 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/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; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 629db5f943def8..e4c3e9e355240b 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 55c8a0b32afd31..4b5657d5c557b1 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/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) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 1f97255877eb25..764713b2a1586a 100644 --- a/src/render/SDL_render.c +++ b/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, diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 9105a4e1f98079..21394174df5a29 100644 --- a/src/video/SDL_surface.c +++ b/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 */