From 02a072a1b7d7bb7aa74455c31e3b58c850e66186 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 17 Jul 2024 16:25:00 -0700 Subject: [PATCH] Added SDL_ClearSurface() and SDL_ReadSurfacePixelFloat() --- include/SDL3/SDL_surface.h | 43 ++++++ src/dynapi/SDL_dynapi.sym | 2 + src/dynapi/SDL_dynapi_overrides.h | 2 + src/dynapi/SDL_dynapi_procs.h | 2 + src/video/SDL_surface.c | 225 ++++++++++++++++++++++++++---- 5 files changed, 246 insertions(+), 28 deletions(-) diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 2ec66c563..a25ba2696 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -828,6 +828,25 @@ extern SDL_DECLSPEC int SDLCALL SDL_PremultiplyAlpha(int width, int height, SDL_ */ extern SDL_DECLSPEC int SDLCALL SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, SDL_bool linear); +/** + * Clear a surface with a specific color, with floating point precision. + * + * This function handles all surface formats, and ignores any clip rectangle. + * + * If the surface is YUV, the color is assumed to be in the sRGB colorspace, otherwise the color is assumed to be in the colorspace of the suface. + * + * \param surface the SDL_Surface to clear. + * \param r the red component of the pixel, normally in the range 0-1 + * \param g the green component of the pixel, normally in the range 0-1 + * \param b the blue component of the pixel, normally in the range 0-1 + * \param a the alpha component of the pixel, normally in the range 0-1 + * \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. + */ +extern SDL_DECLSPEC int SDLCALL SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a); + /** * Perform a fast fill of a rectangle with a specific color. * @@ -1138,6 +1157,30 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint */ extern SDL_DECLSPEC int SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); +/** + * Retrieves a single pixel from a surface. + * + * This function prioritizes correctness over speed: it is suitable for unit + * tests, but is not intended for use in a game engine. + * + * \param surface the surface to read. + * \param x the horizontal coordinate, 0 <= x < width. + * \param y the vertical coordinate, 0 <= y < height. + * \param r a pointer filled in with the red channel, normally in the range 0-1, or NULL to ignore + * this channel. + * \param g a pointer filled in with the green channel, normally in the range 0-1, or NULL to + * ignore this channel. + * \param b a pointer filled in with the blue channel, normally in the range 0-1, or NULL to + * ignore this channel. + * \param a a pointer filled in with the alpha channel, normally in the range 0-1, or NULL to + * ignore this channel. + * \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. + */ +extern SDL_DECLSPEC int SDLCALL SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 04675387d..8e7734bdd 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -35,6 +35,7 @@ SDL3_0.0.0 { SDL_ClearComposition; SDL_ClearError; SDL_ClearProperty; + SDL_ClearSurface; SDL_CloseAudioDevice; SDL_CloseCamera; SDL_CloseGamepad; @@ -627,6 +628,7 @@ SDL3_0.0.0 { SDL_ReadS8; SDL_ReadStorageFile; SDL_ReadSurfacePixel; + SDL_ReadSurfacePixelFloat; SDL_ReadU16BE; SDL_ReadU16LE; SDL_ReadU32BE; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index c25827219..cb95d19ef 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -60,6 +60,7 @@ #define SDL_ClearComposition SDL_ClearComposition_REAL #define SDL_ClearError SDL_ClearError_REAL #define SDL_ClearProperty SDL_ClearProperty_REAL +#define SDL_ClearSurface SDL_ClearSurface_REAL #define SDL_CloseAudioDevice SDL_CloseAudioDevice_REAL #define SDL_CloseCamera SDL_CloseCamera_REAL #define SDL_CloseGamepad SDL_CloseGamepad_REAL @@ -652,6 +653,7 @@ #define SDL_ReadS8 SDL_ReadS8_REAL #define SDL_ReadStorageFile SDL_ReadStorageFile_REAL #define SDL_ReadSurfacePixel SDL_ReadSurfacePixel_REAL +#define SDL_ReadSurfacePixelFloat SDL_ReadSurfacePixelFloat_REAL #define SDL_ReadU16BE SDL_ReadU16BE_REAL #define SDL_ReadU16LE SDL_ReadU16LE_REAL #define SDL_ReadU32BE SDL_ReadU32BE_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index a25da1aa1..f384bd983 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -80,6 +80,7 @@ SDL_DYNAPI_PROC(int,SDL_ClearClipboardData,(void),(),return) SDL_DYNAPI_PROC(int,SDL_ClearComposition,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(int,SDL_ClearError,(void),(),return) SDL_DYNAPI_PROC(int,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_ClearSurface,(SDL_Surface *a, float b, float c, float d, float e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(void,SDL_CloseAudioDevice,(SDL_AudioDeviceID a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_Camera *a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseGamepad,(SDL_Gamepad *a),(a),) @@ -663,6 +664,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_ReadS64LE,(SDL_IOStream *a, Sint64 *b),(a,b),return SDL_DYNAPI_PROC(SDL_bool,SDL_ReadS8,(SDL_IOStream *a, Sint8 *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_ReadStorageFile,(SDL_Storage *a, const char *b, void *c, Uint64 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_ReadSurfacePixel,(SDL_Surface *a, int b, int c, Uint8 *d, Uint8 *e, Uint8 *f, Uint8 *g),(a,b,c,d,e,f,g),return) +SDL_DYNAPI_PROC(int,SDL_ReadSurfacePixelFloat,(SDL_Surface *a, int b, int c, float *d, float *e, float *f, float *g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(SDL_bool,SDL_ReadU16BE,(SDL_IOStream *a, Uint16 *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_ReadU16LE,(SDL_IOStream *a, Uint16 *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_ReadU32BE,(SDL_IOStream *a, Uint32 *b),(a,b),return) diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 50734c06c..bffa87939 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -1991,6 +1991,63 @@ int SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, SDL_bool linear) return SDL_PremultiplyAlphaPixelsAndColorspace(surface->w, surface->h, surface->format, colorspace, surface->internal->props, surface->pixels, surface->pitch, surface->format, colorspace, surface->internal->props, surface->pixels, surface->pitch, linear); } +int SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a) +{ + SDL_Rect clip_rect; + int result = -1; + + if (!SDL_SurfaceValid(surface)) { + return SDL_InvalidParamError("surface"); + } + + SDL_GetSurfaceClipRect(surface, &clip_rect); + SDL_SetSurfaceClipRect(surface, NULL); + + if (!SDL_ISPIXELFORMAT_FOURCC(surface->format) && + SDL_BYTESPERPIXEL(surface->format) <= sizeof(Uint32)) { + Uint32 color; + + color = SDL_MapSurfaceRGBA(surface, + (Uint8)SDL_roundf(SDL_clamp(r, 0.0f, 1.0f) * 255.0f), + (Uint8)SDL_roundf(SDL_clamp(g, 0.0f, 1.0f) * 255.0f), + (Uint8)SDL_roundf(SDL_clamp(b, 0.0f, 1.0f) * 255.0f), + (Uint8)SDL_roundf(SDL_clamp(a, 0.0f, 1.0f) * 255.0f)); + result = SDL_FillSurfaceRect(surface, NULL, color); + } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) { + // We can't directly set an RGB value on a YUV surface + SDL_Surface *tmp = SDL_CreateSurface(surface->w, surface->h, SDL_PIXELFORMAT_ARGB8888); + if (!tmp) { + goto done; + } + + if (SDL_ClearSurface(tmp, r, g, b, a) == 0) { + result = SDL_ConvertPixelsAndColorspace(surface->w, surface->h, tmp->format, SDL_GetSurfaceColorspace(tmp), tmp->internal->props, tmp->pixels, tmp->pitch, surface->format, SDL_GetSurfaceColorspace(surface), surface->internal->props, surface->pixels, surface->pitch); + } + SDL_DestroySurface(tmp); + } else { + // Take advantage of blit color conversion + SDL_Surface *tmp = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA128_FLOAT); + if (!tmp) { + goto done; + } + SDL_SetSurfaceColorspace(tmp, SDL_GetSurfaceColorspace(surface)); + + float *pixels = (float *)tmp->pixels; + pixels[0] = r; + pixels[1] = g; + pixels[2] = b; + pixels[3] = a; + + result = SDL_BlitSurfaceScaled(tmp, NULL, surface, NULL, SDL_SCALEMODE_NEAREST); + SDL_DestroySurface(tmp); + } + +done: + SDL_SetSurfaceClipRect(surface, &clip_rect); + + return result; +} + Uint32 SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b) { return SDL_MapSurfaceRGBA(surface, r, g, b, SDL_ALPHA_OPAQUE); @@ -2014,7 +2071,31 @@ int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *p; int result = -1; - if (!surface || !surface->format || !surface->pixels) { + if (r) { + *r = 0; + } else { + r = &unused; + } + + if (g) { + *g = 0; + } else { + g = &unused; + } + + if (b) { + *b = 0; + } else { + b = &unused; + } + + if (a) { + *a = 0; + } else { + a = &unused; + } + + if (!SDL_SurfaceValid(surface) || !surface->format || !surface->pixels) { return SDL_InvalidParamError("surface"); } @@ -2026,22 +2107,6 @@ int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, return SDL_InvalidParamError("y"); } - if (!r) { - r = &unused; - } - - if (!g) { - g = &unused; - } - - if (!b) { - b = &unused; - } - - if (!a) { - a = &unused; - } - bytes_per_pixel = SDL_BYTESPERPIXEL(surface->format); if (SDL_MUSTLOCK(surface)) { @@ -2052,7 +2117,24 @@ int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, p = (Uint8 *)surface->pixels + y * surface->pitch + x * bytes_per_pixel; - if (bytes_per_pixel > sizeof(pixel)) { + if (bytes_per_pixel <= sizeof(pixel) && !SDL_ISPIXELFORMAT_FOURCC(surface->format)) { + /* Fill the appropriate number of least-significant bytes of pixel, + * leaving the most-significant bytes set to zero */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + SDL_memcpy(((Uint8 *)&pixel) + (sizeof(pixel) - bytes_per_pixel), p, bytes_per_pixel); +#else + SDL_memcpy(&pixel, p, bytes_per_pixel); +#endif + SDL_GetRGBA(pixel, surface->internal->format, surface->internal->palette, r, g, b, a); + result = 0; + } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) { + // FIXME: We need code to extract a single macroblock from a YUV surface + SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); + if (converted) { + result = SDL_ReadSurfacePixel(converted, x, y, r, g, b, a); + SDL_DestroySurface(converted); + } + } else { /* This is really slow, but it gets the job done */ Uint8 rgba[4]; SDL_Colorspace colorspace = SDL_GetSurfaceColorspace(surface); @@ -2064,16 +2146,6 @@ int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, *a = rgba[3]; result = 0; } - } else { - /* Fill the appropriate number of least-significant bytes of pixel, - * leaving the most-significant bytes set to zero */ -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - SDL_memcpy(((Uint8 *)&pixel) + (sizeof(pixel) - bytes_per_pixel), p, bytes_per_pixel); -#else - SDL_memcpy(&pixel, p, bytes_per_pixel); -#endif - SDL_GetRGBA(pixel, surface->internal->format, surface->internal->palette, r, g, b, a); - result = 0; } if (SDL_MUSTLOCK(surface)) { @@ -2082,6 +2154,103 @@ int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, return result; } +int SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a) +{ + float unused; + int result = -1; + + if (r) { + *r = 0.0f; + } else { + r = &unused; + } + + if (g) { + *g = 0.0f; + } else { + g = &unused; + } + + if (b) { + *b = 0.0f; + } else { + b = &unused; + } + + if (a) { + *a = 0.0f; + } else { + a = &unused; + } + + if (!SDL_SurfaceValid(surface) || !surface->format || !surface->pixels) { + return SDL_InvalidParamError("surface"); + } + + if (x < 0 || x >= surface->w) { + return SDL_InvalidParamError("x"); + } + + if (y < 0 || y >= surface->h) { + return SDL_InvalidParamError("y"); + } + + if (SDL_BYTESPERPIXEL(surface->format) <= sizeof(Uint32) && !SDL_ISPIXELFORMAT_FOURCC(surface->format)) { + Uint8 r8, g8, b8, a8; + + if (SDL_ReadSurfacePixel(surface, x, y, &r8, &g8, &b8, &a8) == 0) { + *r = (float)r8 / 255.0f; + *g = (float)g8 / 255.0f; + *b = (float)b8 / 255.0f; + *a = (float)a8 / 255.0f; + result = 0; + } + } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) { + // FIXME: We need code to extract a single macroblock from a YUV surface + SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); + if (converted) { + result = SDL_ReadSurfacePixelFloat(converted, x, y, r, g, b, a); + SDL_DestroySurface(converted); + } + } else { + /* This is really slow, but it gets the job done */ + float rgba[4]; + Uint8 *p; + + if (SDL_MUSTLOCK(surface)) { + if (SDL_LockSurface(surface) < 0) { + return -1; + } + } + + p = (Uint8 *)surface->pixels + y * surface->pitch + x * SDL_BYTESPERPIXEL(surface->format); + + if (surface->format == SDL_PIXELFORMAT_RGBA128_FLOAT) { + SDL_memcpy(rgba, p, sizeof(rgba)); + result = 0; + } else { + SDL_Colorspace src_colorspace = SDL_GetSurfaceColorspace(surface); + SDL_Colorspace dst_colorspace = (src_colorspace == SDL_COLORSPACE_SRGB_LINEAR ? SDL_COLORSPACE_SRGB_LINEAR : SDL_COLORSPACE_SRGB); + + if (SDL_ConvertPixelsAndColorspace(1, 1, surface->format, src_colorspace, surface->internal->props, p, surface->pitch, SDL_PIXELFORMAT_RGBA128_FLOAT, dst_colorspace, 0, rgba, sizeof(rgba)) == 0) { + result = 0; + } + } + + if (result == 0) { + *r = rgba[0]; + *g = rgba[1]; + *b = rgba[2]; + *a = rgba[3]; + } + + if (SDL_MUSTLOCK(surface)) { + SDL_UnlockSurface(surface); + } + } + return result; +} + /* * Free a surface created by the above function. */