From 9e55ee93917741ce7309a1948692f69e8e9db7c8 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 20 Jul 2024 14:37:49 -0700 Subject: [PATCH] Added SDL_BlitSurfaceTiledWithScale() --- include/SDL3/SDL_surface.h | 27 +++++++ src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/video/SDL_surface.c | 113 ++++++++++++++++++++++++++++++ test/testautomation_surface.c | 39 +++++++++-- 6 files changed, 175 insertions(+), 7 deletions(-) diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index a3d64c0c9..c86bffdb6 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -1097,6 +1097,33 @@ extern SDL_DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, */ extern SDL_DECLSPEC int SDLCALL SDL_BlitSurfaceTiled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect); +/** + * Perform a scaled and tiled blit to a destination surface, which may be of a different + * format. + * + * The pixels in `srcrect` will be scaled and repeated as many times as needed to completely fill `dstrect`. + * + * \param src the SDL_Surface structure to be copied from. + * \param srcrect the SDL_Rect structure representing the rectangle to be + * copied, or NULL to copy the entire surface. + * \param scale the scale used to transform srcrect into the destination rectangle, e.g. a 32x32 texture with a scale of 2 would fill 64x64 tiles. + * \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_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, 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 c7552b0a8..ec7744343 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -26,6 +26,7 @@ SDL3_0.0.0 { SDL_BlitSurface; SDL_BlitSurfaceScaled; SDL_BlitSurfaceTiled; + SDL_BlitSurfaceTiledWithScale; SDL_BlitSurfaceUnchecked; SDL_BlitSurfaceUncheckedScaled; SDL_BroadcastCondition; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 2cf76e762..11f0cf49d 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -51,6 +51,7 @@ #define SDL_BlitSurface SDL_BlitSurface_REAL #define SDL_BlitSurfaceScaled SDL_BlitSurfaceScaled_REAL #define SDL_BlitSurfaceTiled SDL_BlitSurfaceTiled_REAL +#define SDL_BlitSurfaceTiledWithScale SDL_BlitSurfaceTiledWithScale_REAL #define SDL_BlitSurfaceUnchecked SDL_BlitSurfaceUnchecked_REAL #define SDL_BlitSurfaceUncheckedScaled SDL_BlitSurfaceUncheckedScaled_REAL #define SDL_BroadcastCondition SDL_BroadcastCondition_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 363593e7a..c5a26add7 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -71,6 +71,7 @@ SDL_DYNAPI_PROC(int,SDL_BindAudioStreams,(SDL_AudioDeviceID a, SDL_AudioStream * SDL_DYNAPI_PROC(int,SDL_BlitSurface,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, SDL_Rect *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_BlitSurfaceScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, 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) SDL_DYNAPI_PROC(int,SDL_BlitSurfaceUnchecked,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_BlitSurfaceUncheckedScaled,(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_BroadcastCondition,(SDL_Condition *a),(a),return) diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index fc87780cb..ae274dabc 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -1341,6 +1341,119 @@ int SDL_BlitSurfaceTiled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface return 0; } +int SDL_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect) +{ + SDL_Rect r_src, r_dst; + + /* Make sure the surfaces aren't locked */ + if (!SDL_SurfaceValid(src)) { + return SDL_InvalidParamError("src"); + } else if (!SDL_SurfaceValid(dst)) { + return SDL_InvalidParamError("dst"); + } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) { + return SDL_SetError("Surfaces must not be locked during blit"); + } + + if (scale <= 0.0f) { + return SDL_InvalidParamError("scale"); + } + + /* Full src surface */ + r_src.x = 0; + r_src.y = 0; + r_src.w = src->w; + r_src.h = src->h; + + if (dstrect) { + r_dst.x = dstrect->x; + r_dst.y = dstrect->y; + r_dst.w = dstrect->w; + r_dst.h = dstrect->h; + } else { + r_dst.x = 0; + r_dst.y = 0; + r_dst.w = dst->w; + r_dst.h = dst->h; + } + + /* clip the source rectangle to the source surface */ + if (srcrect) { + if (SDL_GetRectIntersection(srcrect, &r_src, &r_src) == SDL_FALSE) { + return 0; + } + + /* For tiling we don't adjust the destination rectangle */ + } + + /* clip the destination rectangle against the clip rectangle */ + { + if (SDL_GetRectIntersection(&r_dst, &dst->internal->clip_rect, &r_dst) == SDL_FALSE) { + return 0; + } + + /* For tiling we don't adjust the source rectangle */ + } + + /* Switch back to a fast blit if we were previously stretching */ + if (src->internal->map.info.flags & SDL_COPY_NEAREST) { + src->internal->map.info.flags &= ~SDL_COPY_NEAREST; + SDL_InvalidateMap(&src->internal->map); + } + + int tile_width = (int)(r_src.w * scale); + int tile_height = (int)(r_src.h * scale); + int rows = r_dst.h / tile_height; + int cols = r_dst.w / tile_width; + int remaining_dst_w = (r_dst.w - cols * tile_width); + int remaining_dst_h = (r_dst.h - rows * tile_height); + int remaining_src_w = (int)(remaining_dst_w / scale); + int remaining_src_h = (int)(remaining_dst_h / scale); + SDL_Rect curr_src, curr_dst; + + SDL_copyp(&curr_src, &r_src); + curr_dst.y = r_dst.y; + curr_dst.w = tile_width; + curr_dst.h = tile_height; + for (int y = 0; y < rows; ++y) { + curr_dst.x = r_dst.x; + for (int x = 0; x < cols; ++x) { + if (SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) { + return -1; + } + curr_dst.x += curr_dst.w; + } + if (remaining_dst_w > 0) { + curr_src.w = remaining_src_w; + curr_dst.w = remaining_dst_w; + if (SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) { + return -1; + } + curr_src.w = r_src.w; + curr_dst.w = tile_width; + } + curr_dst.y += curr_dst.h; + } + if (remaining_dst_h > 0) { + curr_src.h = remaining_src_h; + curr_dst.h = remaining_dst_h; + curr_dst.x = r_dst.x; + for (int x = 0; x < cols; ++x) { + if (SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) { + return -1; + } + curr_dst.x += curr_dst.w; + } + if (remaining_dst_w > 0) { + curr_src.w = remaining_src_w; + curr_dst.w = remaining_dst_w; + if (SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode) < 0) { + return -1; + } + } + } + return 0; +} + /* * Lock a surface to directly access the pixels */ diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c index ebebdfee5..446913b4f 100644 --- a/test/testautomation_surface.c +++ b/test/testautomation_surface.c @@ -366,6 +366,8 @@ static int surface_testSaveLoadBitmap(void *arg) static int surface_testBlitTiled(void *arg) { SDL_Surface *face = NULL; + SDL_Surface *testSurface2x = NULL; + SDL_Surface *referenceSurface2x = NULL; int ret = 0; /* Create sample surface */ @@ -375,17 +377,40 @@ static int surface_testBlitTiled(void *arg) return TEST_ABORTED; } - ret = SDL_BlitSurfaceTiled(face, NULL, testSurface, NULL); - SDLTest_AssertCheck(ret == 0, "Verify result from SDL_BlitSurfaceTiled expected: 0, got: %i", ret); + /* Tiled blit - 1.0 scale */ + { + ret = SDL_BlitSurfaceTiled(face, NULL, testSurface, NULL); + SDLTest_AssertCheck(ret == 0, "Verify result from SDL_BlitSurfaceTiled expected: 0, got: %i", ret); - /* See if it's the same */ - SDL_DestroySurface(referenceSurface); - referenceSurface = SDLTest_ImageBlitTiled(); - ret = SDLTest_CompareSurfaces(testSurface, referenceSurface, 0); - SDLTest_AssertCheck(ret == 0, "Validate result from SDLTest_CompareSurfaces, expected: 0, got: %i", ret); + /* See if it's the same */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDLTest_ImageBlitTiled(); + ret = SDLTest_CompareSurfaces(testSurface, referenceSurface, 0); + SDLTest_AssertCheck(ret == 0, "Validate result from SDLTest_CompareSurfaces, expected: 0, got: %i", ret); + } + + /* Tiled blit - 2.0 scale */ + { + testSurface2x = SDL_CreateSurface(testSurface->w * 2, testSurface->h * 2, testSurface->format); + SDLTest_AssertCheck(testSurface != NULL, "Check that testSurface2x is not NULL"); + ret = SDL_FillSurfaceRect(testSurface2x, NULL, SDL_MapSurfaceRGBA(testSurface2x, 0, 0, 0, 255)); + SDLTest_AssertCheck(ret == 0, "Validate result from SDL_FillSurfaceRect, expected: 0, got: %i", ret); + + ret = SDL_BlitSurfaceTiledWithScale(face, NULL, 2.0f, SDL_SCALEMODE_NEAREST, testSurface2x, NULL); + SDLTest_AssertCheck(ret == 0, "Validate results from call to SDL_RenderTextureTiled, expected: 0, got: %i", ret); + + /* See if it's the same */ + referenceSurface2x = SDL_CreateSurface(referenceSurface->w * 2, referenceSurface->h * 2, referenceSurface->format); + SDL_BlitSurfaceScaled(referenceSurface, NULL, referenceSurface2x, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(ret == 0, "Validate results from call to SDL_BlitSurfaceScaled, expected: 0, got: %i", ret); + ret = SDLTest_CompareSurfaces(testSurface2x, referenceSurface2x, 0); + SDLTest_AssertCheck(ret == 0, "Validate result from SDLTest_CompareSurfaces, expected: 0, got: %i", ret); + } /* Clean up. */ SDL_DestroySurface(face); + SDL_DestroySurface(testSurface2x); + SDL_DestroySurface(referenceSurface2x); return TEST_COMPLETED; }