diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 6998b12991..32cb75c388 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -798,8 +798,6 @@ extern SDL_DECLSPEC int SDLCALL SDL_ConvertPixelsAndColorspace(int width, int he * * This is safe to use with src == dst, but not for other overlapping areas. * - * This function is currently only implemented for SDL_PIXELFORMAT_ARGB8888. - * * \param width the width of the block to convert, in pixels. * \param height the height of the block to convert, in pixels. * \param src_format an SDL_PixelFormat value of the `src` pixels format. @@ -808,12 +806,27 @@ extern SDL_DECLSPEC int SDLCALL SDL_ConvertPixelsAndColorspace(int width, int he * \param dst_format an SDL_PixelFormat value of the `dst` pixels format. * \param dst a pointer to be filled in with premultiplied pixel data. * \param dst_pitch the pitch of the destination pixels, in bytes. + * \param linear SDL_TRUE to convert from sRGB to linear space for the alpha multiplication, SDL_FALSE to do multiplication in sRGB space. * \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_PremultiplyAlpha(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch); +extern SDL_DECLSPEC int SDLCALL SDL_PremultiplyAlpha(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch, SDL_bool linear); + +/** + * Premultiply the alpha in a surface. + * + * This is safe to use with src == dst, but not for other overlapping areas. + * + * \param surface the surface to modify. + * \param linear SDL_TRUE to convert from sRGB to linear space for the alpha multiplication, SDL_FALSE to do multiplication in sRGB space. + * \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_PremultiplySurfaceAlpha(SDL_Surface *surface, SDL_bool linear); /** * Perform a fast fill of a rectangle with a specific color. diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index d1c53c8725..04675387d3 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -610,6 +610,7 @@ SDL3_0.0.0 { SDL_PollEvent; SDL_PostSemaphore; SDL_PremultiplyAlpha; + SDL_PremultiplySurfaceAlpha; SDL_PumpEvents; SDL_PushEvent; SDL_PutAudioStreamData; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 425bab5f8b..c25827219b 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -635,6 +635,7 @@ #define SDL_PollEvent SDL_PollEvent_REAL #define SDL_PostSemaphore SDL_PostSemaphore_REAL #define SDL_PremultiplyAlpha SDL_PremultiplyAlpha_REAL +#define SDL_PremultiplySurfaceAlpha SDL_PremultiplySurfaceAlpha_REAL #define SDL_PumpEvents SDL_PumpEvents_REAL #define SDL_PushEvent SDL_PushEvent_REAL #define SDL_PutAudioStreamData SDL_PutAudioStreamData_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index fd778f69d4..a25da1aa1d 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -645,7 +645,8 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_PenConnected,(SDL_PenID a),(a),return) SDL_DYNAPI_PROC(int,SDL_PlayHapticRumble,(SDL_Haptic *a, float b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_bool,SDL_PollEvent,(SDL_Event *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PostSemaphore,(SDL_Semaphore *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, SDL_PixelFormat c, const void *d, int e, SDL_PixelFormat f, void *g, int h),(a,b,c,d,e,f,g,h),return) +SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, SDL_PixelFormat c, const void *d, int e, SDL_PixelFormat f, void *g, int h, SDL_bool i),(a,b,c,d,e,f,g,h,i),return) +SDL_DYNAPI_PROC(int,SDL_PremultiplySurfaceAlpha,(SDL_Surface *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_PumpEvents,(void),(),) SDL_DYNAPI_PROC(int,SDL_PushEvent,(SDL_Event *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return) diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 2c38eacd24..e6d5c1f0b6 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -1769,15 +1769,12 @@ int SDL_ConvertPixels(int width, int height, /* * Premultiply the alpha on a block of pixels * - * This is currently only implemented for SDL_PIXELFORMAT_ARGB8888 - * * Here are some ideas for optimization: * https://github.com/Wizermil/premultiply_alpha/tree/master/premultiply_alpha * https://developer.arm.com/documentation/101964/0201/Pre-multiplied-alpha-channel-data */ -int SDL_PremultiplyAlpha(int width, int height, - SDL_PixelFormat src_format, const void *src, int src_pitch, - SDL_PixelFormat dst_format, void *dst, int dst_pitch) + +static void SDL_PremultiplyAlpha_AXYZ8888(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch) { int c; Uint32 srcpixel; @@ -1785,25 +1782,6 @@ int SDL_PremultiplyAlpha(int width, int height, Uint32 dstpixel; Uint32 dstR, dstG, dstB, dstA; - if (!src) { - return SDL_InvalidParamError("src"); - } - if (!src_pitch) { - return SDL_InvalidParamError("src_pitch"); - } - if (!dst) { - return SDL_InvalidParamError("dst"); - } - if (!dst_pitch) { - return SDL_InvalidParamError("dst_pitch"); - } - if (src_format != SDL_PIXELFORMAT_ARGB8888) { - return SDL_InvalidParamError("src_format"); - } - if (dst_format != SDL_PIXELFORMAT_ARGB8888) { - return SDL_InvalidParamError("dst_format"); - } - while (height--) { const Uint32 *src_px = (const Uint32 *)src; Uint32 *dst_px = (Uint32 *)dst; @@ -1825,7 +1803,192 @@ int SDL_PremultiplyAlpha(int width, int height, src = (const Uint8 *)src + src_pitch; dst = (Uint8 *)dst + dst_pitch; } - return 0; +} + +static void SDL_PremultiplyAlpha_XYZA8888(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch) +{ + int c; + Uint32 srcpixel; + Uint32 srcR, srcG, srcB, srcA; + Uint32 dstpixel; + Uint32 dstR, dstG, dstB, dstA; + + while (height--) { + const Uint32 *src_px = (const Uint32 *)src; + Uint32 *dst_px = (Uint32 *)dst; + for (c = width; c; --c) { + /* Component bytes extraction. */ + srcpixel = *src_px++; + RGBA_FROM_RGBA8888(srcpixel, srcR, srcG, srcB, srcA); + + /* Alpha pre-multiplication of each component. */ + dstA = srcA; + dstR = (srcA * srcR) / 255; + dstG = (srcA * srcG) / 255; + dstB = (srcA * srcB) / 255; + + /* RGBA8888 pixel recomposition. */ + RGBA8888_FROM_RGBA(dstpixel, dstR, dstG, dstB, dstA); + *dst_px++ = dstpixel; + } + src = (const Uint8 *)src + src_pitch; + dst = (Uint8 *)dst + dst_pitch; + } +} + +static void SDL_PremultiplyAlpha_AXYZ128(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch) +{ + int c; + float flR, flG, flB, flA; + + while (height--) { + const float *src_px = (const float *)src; + float *dst_px = (float *)dst; + for (c = width; c; --c) { + flA = *src_px++; + flR = *src_px++; + flG = *src_px++; + flB = *src_px++; + + /* Alpha pre-multiplication of each component. */ + flR *= flA; + flG *= flA; + flB *= flA; + + *dst_px++ = flA; + *dst_px++ = flR; + *dst_px++ = flG; + *dst_px++ = flB; + } + src = (const Uint8 *)src + src_pitch; + dst = (Uint8 *)dst + dst_pitch; + } +} + +static int SDL_PremultiplyAlphaPixelsAndColorspace(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch, SDL_bool linear) +{ + SDL_Surface *convert = NULL; + void *final_dst = dst; + int final_dst_pitch = dst_pitch; + SDL_PixelFormat format; + SDL_Colorspace colorspace; + int result = -1; + + if (!src) { + return SDL_InvalidParamError("src"); + } + if (!src_pitch) { + return SDL_InvalidParamError("src_pitch"); + } + if (!dst) { + return SDL_InvalidParamError("dst"); + } + if (!dst_pitch) { + return SDL_InvalidParamError("dst_pitch"); + } + + // Use a high precision format if we're converting to linear colorspace or using high precision pixel formats + if (linear || + SDL_ISPIXELFORMAT_10BIT(src_format) || SDL_BITSPERPIXEL(src_format) > 32 || + SDL_ISPIXELFORMAT_10BIT(dst_format) || SDL_BITSPERPIXEL(dst_format) > 32) { + if (src_format == SDL_PIXELFORMAT_ARGB128_FLOAT || + src_format == SDL_PIXELFORMAT_ABGR128_FLOAT) { + format = src_format; + } else { + format = SDL_PIXELFORMAT_ARGB128_FLOAT; + } + } else { + if (src_format == SDL_PIXELFORMAT_ARGB8888 || + src_format == SDL_PIXELFORMAT_ABGR8888 || + src_format == SDL_PIXELFORMAT_RGBA8888 || + src_format == SDL_PIXELFORMAT_BGRA8888) { + format = src_format; + } else { + format = SDL_PIXELFORMAT_ARGB8888; + } + } + if (linear) { + colorspace = SDL_COLORSPACE_SRGB_LINEAR; + } else { + colorspace = SDL_COLORSPACE_SRGB; + } + + if (src_format != format || src_colorspace != colorspace) { + convert = SDL_CreateSurface(width, height, format); + if (!convert) { + goto done; + } + if (SDL_ConvertPixelsAndColorspace(width, height, src_format, src_colorspace, src_properties, src, src_pitch, format, colorspace, 0, convert->pixels, convert->pitch) < 0) { + goto done; + } + + src = convert->pixels; + src_pitch = convert->pitch; + dst = convert->pixels; + dst_pitch = convert->pitch; + + } else if (dst_format != format || dst_colorspace != colorspace) { + convert = SDL_CreateSurface(width, height, format); + if (!convert) { + goto done; + } + dst = convert->pixels; + dst_pitch = convert->pitch; + } + + switch (format) { + case SDL_PIXELFORMAT_ARGB8888: + case SDL_PIXELFORMAT_ABGR8888: + SDL_PremultiplyAlpha_AXYZ8888(width, height, src, src_pitch, dst, dst_pitch); + break; + case SDL_PIXELFORMAT_RGBA8888: + case SDL_PIXELFORMAT_BGRA8888: + SDL_PremultiplyAlpha_XYZA8888(width, height, src, src_pitch, dst, dst_pitch); + break; + case SDL_PIXELFORMAT_ARGB128_FLOAT: + case SDL_PIXELFORMAT_ABGR128_FLOAT: + SDL_PremultiplyAlpha_AXYZ128(width, height, src, src_pitch, dst, dst_pitch); + break; + default: + SDL_SetError("Unexpected internal pixel format"); + goto done; + } + + if (dst != final_dst) { + if (SDL_ConvertPixelsAndColorspace(width, height, format, colorspace, 0, convert->pixels, convert->pitch, dst_format, dst_colorspace, dst_properties, final_dst, final_dst_pitch) < 0) { + goto done; + } + } + result = 0; + +done: + if (convert) { + SDL_DestroySurface(convert); + } + return result; +} + +int SDL_PremultiplyAlpha(int width, int height, + SDL_PixelFormat src_format, const void *src, int src_pitch, + SDL_PixelFormat dst_format, void *dst, int dst_pitch, SDL_bool linear) +{ + SDL_Colorspace src_colorspace = SDL_GetDefaultColorspaceForFormat(src_format); + SDL_Colorspace dst_colorspace = SDL_GetDefaultColorspaceForFormat(dst_format); + + return SDL_PremultiplyAlphaPixelsAndColorspace(width, height, src_format, src_colorspace, 0, src, src_pitch, dst_format, dst_colorspace, 0, dst, dst_pitch, linear); +} + +int SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, SDL_bool linear) +{ + SDL_Colorspace colorspace; + + if (!SDL_SurfaceValid(surface)) { + return SDL_InvalidParamError("surface"); + } + + colorspace = SDL_GetSurfaceColorspace(surface); + + 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); } Uint32 SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b) diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.c b/src/video/kmsdrm/SDL_kmsdrmmouse.c index f96dd837de..2b9a796dfb 100644 --- a/src/video/kmsdrm/SDL_kmsdrmmouse.c +++ b/src/video/kmsdrm/SDL_kmsdrmmouse.c @@ -267,7 +267,7 @@ static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_ straight-alpha pixels, so we always have to convert. */ SDL_PremultiplyAlpha(surface->w, surface->h, surface->format, surface->pixels, surface->pitch, - SDL_PIXELFORMAT_ARGB8888, curdata->buffer, surface->w * 4); + SDL_PIXELFORMAT_ARGB8888, curdata->buffer, surface->w * 4, SDL_TRUE); cursor->internal = curdata; diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 3bf9376690..6a2640ef1e 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -441,7 +441,7 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot /* Wayland requires premultiplied alpha for its surfaces. */ SDL_PremultiplyAlpha(surface->w, surface->h, surface->format, surface->pixels, surface->pitch, - SDL_PIXELFORMAT_ARGB8888, data->cursor_data.custom.shm_data, surface->w * 4); + SDL_PIXELFORMAT_ARGB8888, data->cursor_data.custom.shm_data, surface->w * 4, SDL_TRUE); data->surface = wl_compositor_create_surface(wd->compositor); wl_surface_set_user_data(data->surface, NULL);