From 94d9229ce2158c96d0ba1c46feacaf3327ebbddd Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 31 Jul 2024 21:39:37 -0700 Subject: [PATCH] Added SDL_AddSurfaceAlternateImage(), SDL_SurfaceHasAlternateImages(), SDL_GetSurfaceImages() and SDL_RemoveSurfaceAlternateImages() These functions allow you to create surfaces with alternate high DPI content, and will be used for high DPI icon and cursor support. --- include/SDL3/SDL_surface.h | 76 ++++++++++++++++++ src/dynapi/SDL_dynapi.sym | 4 + src/dynapi/SDL_dynapi_overrides.h | 4 + src/dynapi/SDL_dynapi_procs.h | 4 + src/video/SDL_surface.c | 129 ++++++++++++++++++++++++++++++ src/video/SDL_surface_c.h | 5 ++ 6 files changed, 222 insertions(+) diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 6bde40178..d7342a4f7 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -306,6 +306,76 @@ extern SDL_DECLSPEC int SDLCALL SDL_SetSurfacePalette(SDL_Surface *surface, SDL_ */ extern SDL_DECLSPEC SDL_Palette * SDLCALL SDL_GetSurfacePalette(SDL_Surface *surface); +/** + * Add an alternate version of a surface. + * + * This function adds an alternate version of this surface, usually used for content with high DPI representations like cursors or icons. The size, format, and content do not need to match the original surface, and these alternate versions will not be updated when the original surface changes. + * + * This function adds a reference to the alternate version, so you should call SDL_DestroySurface() on the image after this call. + * + * \param surface the SDL_Surface structure to update. + * \param image a pointer to an alternate SDL_Surface to associate with this surface. + * \returns SDL_TRUE if alternate versions are available or SDL_TRUE otherwise. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_RemoveSurfaceAlternateImages + * \sa SDL_GetSurfaceImages + * \sa SDL_SurfaceHasAlternateImages + */ +extern SDL_DECLSPEC int SDLCALL SDL_AddSurfaceAlternateImage(SDL_Surface *surface, SDL_Surface *image); + +/** + * Return whether a surface has alternate versions available. + * + * \param surface the SDL_Surface structure to query. + * \returns SDL_TRUE if alternate versions are available or SDL_TRUE otherwise. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AddSurfaceAlternateImage + * \sa SDL_RemoveSurfaceAlternateImages + * \sa SDL_GetSurfaceImages + */ +extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SurfaceHasAlternateImages(SDL_Surface *surface); + +/** + * Get an array including all versions of a surface. + * + * This returns all versions of a surface, with the surface being queried as the first element in the returned array. + * + * Freeing the array of surfaces does not affect the surfaces in the array. They are still referenced by the surface being queried and will be cleaned up normally. + * + * \param surface the SDL_Surface structure to query. + * \param count a pointer filled in with the number of surface pointers returned, may + * be NULL. + * \returns a NULL terminated array of SDL_Surface pointers or NULL on failure; + * call SDL_GetError() for more information. This should be freed + * with SDL_free() when it is no longer needed. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AddSurfaceAlternateImage + * \sa SDL_RemoveSurfaceAlternateImages + * \sa SDL_SurfaceHasAlternateImages + */ +extern SDL_DECLSPEC SDL_Surface ** SDLCALL SDL_GetSurfaceImages(SDL_Surface *surface, int *count); + +/** + * Remove all alternate versions of a surface. + * + * This function removes a reference from all the alternative versions, destroying them if this is the last reference to them. + * + * \param surface the SDL_Surface structure to update. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AddSurfaceAlternateImage + * \sa SDL_GetSurfaceImages + * \sa SDL_SurfaceHasAlternateImages + */ +extern SDL_DECLSPEC void SDLCALL SDL_RemoveSurfaceAlternateImages(SDL_Surface *surface); + /** * Set up a surface for directly accessing the pixels. * @@ -679,6 +749,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMo /** * Creates a new surface identical to the existing surface. * + * If the original surface has alternate images, the new surface will have a reference to them as well. + * * The returned surface should be freed with SDL_DestroySurface(). * * \param surface the surface to duplicate. @@ -720,6 +792,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ScaleSurface(SDL_Surface *surface, * If you are converting to an indexed surface and want to map colors to a * palette, you can use SDL_ConvertSurfaceAndColorspace() instead. * + * If the original surface has alternate images, the new surface will have a reference to them as well. + * * \param surface the existing SDL_Surface structure to convert. * \param format the new pixel format. * \returns the new SDL_Surface structure that is created or NULL on failure; @@ -740,6 +814,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_ConvertSurface(SDL_Surface *surfac * and returns the new surface. This will perform any pixel format and * colorspace conversion needed. * + * If the original surface has alternate images, the new surface will have a reference to them as well. + * * \param surface the existing SDL_Surface structure to convert. * \param format the new pixel format. * \param palette an optional palette to use for indexed formats, may be NULL. diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 49a9f2584..bf7a2276b 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -8,6 +8,7 @@ SDL3_0.0.0 { SDL_AddGamepadMappingsFromFile; SDL_AddGamepadMappingsFromIO; SDL_AddHintCallback; + SDL_AddSurfaceAlternateImage; SDL_AddTimer; SDL_AddTimerNS; SDL_AddVulkanRenderSemaphores; @@ -438,6 +439,7 @@ SDL3_0.0.0 { SDL_GetSurfaceColorKey; SDL_GetSurfaceColorMod; SDL_GetSurfaceColorspace; + SDL_GetSurfaceImages; SDL_GetSurfacePalette; SDL_GetSurfaceProperties; SDL_GetSystemRAM; @@ -644,6 +646,7 @@ SDL3_0.0.0 { SDL_ReloadGamepadMappings; SDL_RemovePath; SDL_RemoveStoragePath; + SDL_RemoveSurfaceAlternateImages; SDL_RemoveTimer; SDL_RenamePath; SDL_RenameStoragePath; @@ -820,6 +823,7 @@ SDL3_0.0.0 { SDL_StopTextInput; SDL_StorageReady; SDL_StringToGUID; + SDL_SurfaceHasAlternateImages; SDL_SurfaceHasColorKey; SDL_SurfaceHasRLE; SDL_SyncWindow; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index b1e0dce8e..96d40eaa3 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -33,6 +33,7 @@ #define SDL_AddGamepadMappingsFromFile SDL_AddGamepadMappingsFromFile_REAL #define SDL_AddGamepadMappingsFromIO SDL_AddGamepadMappingsFromIO_REAL #define SDL_AddHintCallback SDL_AddHintCallback_REAL +#define SDL_AddSurfaceAlternateImage SDL_AddSurfaceAlternateImage_REAL #define SDL_AddTimer SDL_AddTimer_REAL #define SDL_AddTimerNS SDL_AddTimerNS_REAL #define SDL_AddVulkanRenderSemaphores SDL_AddVulkanRenderSemaphores_REAL @@ -463,6 +464,7 @@ #define SDL_GetSurfaceColorKey SDL_GetSurfaceColorKey_REAL #define SDL_GetSurfaceColorMod SDL_GetSurfaceColorMod_REAL #define SDL_GetSurfaceColorspace SDL_GetSurfaceColorspace_REAL +#define SDL_GetSurfaceImages SDL_GetSurfaceImages_REAL #define SDL_GetSurfacePalette SDL_GetSurfacePalette_REAL #define SDL_GetSurfaceProperties SDL_GetSurfaceProperties_REAL #define SDL_GetSystemRAM SDL_GetSystemRAM_REAL @@ -669,6 +671,7 @@ #define SDL_ReloadGamepadMappings SDL_ReloadGamepadMappings_REAL #define SDL_RemovePath SDL_RemovePath_REAL #define SDL_RemoveStoragePath SDL_RemoveStoragePath_REAL +#define SDL_RemoveSurfaceAlternateImages SDL_RemoveSurfaceAlternateImages_REAL #define SDL_RemoveTimer SDL_RemoveTimer_REAL #define SDL_RenamePath SDL_RenamePath_REAL #define SDL_RenameStoragePath SDL_RenameStoragePath_REAL @@ -845,6 +848,7 @@ #define SDL_StopTextInput SDL_StopTextInput_REAL #define SDL_StorageReady SDL_StorageReady_REAL #define SDL_StringToGUID SDL_StringToGUID_REAL +#define SDL_SurfaceHasAlternateImages SDL_SurfaceHasAlternateImages_REAL #define SDL_SurfaceHasColorKey SDL_SurfaceHasColorKey_REAL #define SDL_SurfaceHasRLE SDL_SurfaceHasRLE_REAL #define SDL_SyncWindow SDL_SyncWindow_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index be11aa8eb..749af3ce2 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -53,6 +53,7 @@ SDL_DYNAPI_PROC(int,SDL_AddGamepadMapping,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_AddGamepadMappingsFromFile,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_AddGamepadMappingsFromIO,(SDL_IOStream *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_AddHintCallback,(const char *a, SDL_HintCallback b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_AddSurfaceAlternateImage,(SDL_Surface *a, SDL_Surface *b),(a,b),return) SDL_DYNAPI_PROC(SDL_TimerID,SDL_AddTimer,(Uint32 a, SDL_TimerCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_TimerID,SDL_AddTimerNS,(Uint64 a, SDL_NSTimerCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_AddVulkanRenderSemaphores,(SDL_Renderer *a, Uint32 b, Sint64 c, Sint64 d),(a,b,c,d),return) @@ -483,6 +484,7 @@ SDL_DYNAPI_PROC(int,SDL_GetSurfaceClipRect,(SDL_Surface *a, SDL_Rect *b),(a,b),r SDL_DYNAPI_PROC(int,SDL_GetSurfaceColorKey,(SDL_Surface *a, Uint32 *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetSurfaceColorMod,(SDL_Surface *a, Uint8 *b, Uint8 *c, Uint8 *d),(a,b,c,d),return) SDL_DYNAPI_PROC(SDL_Colorspace,SDL_GetSurfaceColorspace,(SDL_Surface *a),(a),return) +SDL_DYNAPI_PROC(SDL_Surface**,SDL_GetSurfaceImages,(SDL_Surface *a, int *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Palette*,SDL_GetSurfacePalette,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSurfaceProperties,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetSystemRAM,(void),(),return) @@ -680,6 +682,7 @@ SDL_DYNAPI_PROC(int,SDL_ReleaseCameraFrame,(SDL_Camera *a, SDL_Surface *b),(a,b) SDL_DYNAPI_PROC(int,SDL_ReloadGamepadMappings,(void),(),return) SDL_DYNAPI_PROC(int,SDL_RemovePath,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_RemoveStoragePath,(SDL_Storage *a, const char *b),(a,b),return) +SDL_DYNAPI_PROC(void,SDL_RemoveSurfaceAlternateImages,(SDL_Surface *a),(a),) SDL_DYNAPI_PROC(int,SDL_RemoveTimer,(SDL_TimerID a),(a),return) SDL_DYNAPI_PROC(int,SDL_RenamePath,(const char *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_RenameStoragePath,(SDL_Storage *a, const char *b, const char *c),(a,b,c),return) @@ -855,6 +858,7 @@ SDL_DYNAPI_PROC(int,SDL_StopHapticRumble,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(int,SDL_StopTextInput,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_StorageReady,(SDL_Storage *a),(a),return) SDL_DYNAPI_PROC(SDL_GUID,SDL_StringToGUID,(const char *a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_SurfaceHasAlternateImages,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SurfaceHasColorKey,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SurfaceHasRLE,(SDL_Surface *a),(a),return) SDL_DYNAPI_PROC(int,SDL_SyncWindow,(SDL_Window *a),(a),return) diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 7f83fd6fa..5aeead462 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -436,6 +436,126 @@ SDL_Palette *SDL_GetSurfacePalette(SDL_Surface *surface) return surface->internal->palette; } +int SDL_AddSurfaceAlternateImage(SDL_Surface *surface, SDL_Surface *image) +{ + if (!SDL_SurfaceValid(surface)) { + return SDL_InvalidParamError("surface"); + } + + if (!SDL_SurfaceValid(image)) { + return SDL_InvalidParamError("image"); + } + + SDL_Surface **images = (SDL_Surface **)SDL_realloc(surface->internal->images, (surface->internal->num_images + 1) * sizeof(*images)); + if (!images) { + return -1; + } + images[surface->internal->num_images] = image; + surface->internal->images = images; + ++surface->internal->num_images; + ++image->refcount; + return 0; +} + +SDL_bool SDL_SurfaceHasAlternateImages(SDL_Surface *surface) +{ + if (!SDL_SurfaceValid(surface)) { + return SDL_FALSE; + } + + return (surface->internal->num_images > 0); +} + +SDL_Surface **SDL_GetSurfaceImages(SDL_Surface *surface, int *count) +{ + if (count) { + *count = 0; + } + + if (!SDL_SurfaceValid(surface)) { + SDL_InvalidParamError("surface"); + return NULL; + } + + int num_images = 1 + surface->internal->num_images; + SDL_Surface **images = (SDL_Surface **)SDL_malloc((num_images + 1) * sizeof(*images)); + if (!images) { + return NULL; + } + images[0] = surface; + if (surface->internal->num_images > 0) { + SDL_memcpy(&images[1], surface->internal->images, (surface->internal->num_images * sizeof(images[1]))); + } + images[num_images] = NULL; + + if (count) { + *count = num_images; + } + return images; +} + +SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale) +{ + if (!SDL_SurfaceValid(surface)) { + SDL_InvalidParamError("surface"); + return NULL; + } + + if (!SDL_SurfaceHasAlternateImages(surface)) { + ++surface->refcount; + return surface; + } + + // This surface has high DPI images, pick the best one available, or scale one to the correct size + SDL_Surface **images = SDL_GetSurfaceImages(surface, NULL); + if (!images) { + // Failure, fall back to the existing surface + ++surface->refcount; + return surface; + } + + SDL_Surface *closest = NULL; + int desired_w = (int)SDL_round(surface->w * display_scale); + int desired_h = (int)SDL_round(surface->h * display_scale); + int closest_distance = -1; + for (int i = 0; images[i]; ++i) { + SDL_Surface *candidate = images[i]; + int delta_w = (candidate->w - desired_w); + int delta_h = (candidate->h - desired_h); + int distance = (delta_w * delta_w) + (delta_h * delta_h); + if (closest_distance < 0 || distance < closest_distance) { + closest = candidate; + closest_distance = distance; + } + } + SDL_free(images); + SDL_assert(closest != NULL); // We should always have at least one surface + + if (closest->w == desired_w && closest->h == desired_h) { + ++closest->refcount; + return closest; + } + + // We need to scale an image to the correct size + return SDL_ScaleSurface(closest, desired_w, desired_h, SDL_SCALEMODE_LINEAR); +} + +void SDL_RemoveSurfaceAlternateImages(SDL_Surface *surface) +{ + if (!SDL_SurfaceValid(surface)) { + return; + } + + if (surface->internal->num_images > 0) { + for (int i = 0; i < surface->internal->num_images; ++i) { + SDL_DestroySurface(surface->internal->images[i]); + } + SDL_free(surface->internal->images); + surface->internal->images = NULL; + surface->internal->num_images = 0; + } +} + int SDL_SetSurfaceRLE(SDL_Surface *surface, SDL_bool enabled) { int flags; @@ -1945,6 +2065,13 @@ end: SDL_SetSurfaceRLE(convert, SDL_TRUE); } + /* Copy alternate images */ + for (int i = 0; i < surface->internal->num_images; ++i) { + if (SDL_AddSurfaceAlternateImage(convert, surface->internal->images[i]) < 0) { + goto error; + } + } + /* We're ready to go! */ return convert; @@ -2788,6 +2915,8 @@ void SDL_DestroySurface(SDL_Surface *surface) return; } + SDL_RemoveSurfaceAlternateImages(surface); + SDL_DestroyProperties(surface->internal->props); SDL_InvalidateMap(&surface->internal->map); diff --git a/src/video/SDL_surface_c.h b/src/video/SDL_surface_c.h index 7aa23703a..db3c8060a 100644 --- a/src/video/SDL_surface_c.h +++ b/src/video/SDL_surface_c.h @@ -52,6 +52,10 @@ struct SDL_SurfaceData /** palette for indexed surfaces */ SDL_Palette *palette; + /** Alternate representation of images */ + int num_images; + SDL_Surface **images; + /** information needed for surfaces requiring locks */ int locked; @@ -76,6 +80,7 @@ extern float SDL_GetDefaultSDRWhitePoint(SDL_Colorspace colorspace); extern float SDL_GetSurfaceSDRWhitePoint(SDL_Surface *surface, SDL_Colorspace colorspace); extern float SDL_GetDefaultHDRHeadroom(SDL_Colorspace colorspace); extern float SDL_GetSurfaceHDRHeadroom(SDL_Surface *surface, SDL_Colorspace colorspace); +extern SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale); extern int SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode); #endif /* SDL_surface_c_h_ */