Enhancements for SDL_PremultiplyAlpha()

The function can now convert between pixels of different formats, and takes a parameter to control whether the premultiplication is done in sRGB or linear space.

Also added SDL_PremultiplySurfaceAlpha(), which can premultiply the pixels of a surface in-place.
This commit is contained in:
Sam Lantinga 2024-07-17 12:50:56 -07:00
parent b99ea1ff75
commit 334962b056
7 changed files with 210 additions and 31 deletions

View file

@ -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.

View file

@ -610,6 +610,7 @@ SDL3_0.0.0 {
SDL_PollEvent;
SDL_PostSemaphore;
SDL_PremultiplyAlpha;
SDL_PremultiplySurfaceAlpha;
SDL_PumpEvents;
SDL_PushEvent;
SDL_PutAudioStreamData;

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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;

View file

@ -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);