Skip to content

BLEND_OVERLAY blend mode #2377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ from .constants import (
BLEND_MAX as BLEND_MAX,
BLEND_MIN as BLEND_MIN,
BLEND_MULT as BLEND_MULT,
BLEND_OVERLAY as BLEND_OVERLAY,
BLEND_PREMULTIPLIED as BLEND_PREMULTIPLIED,
BLEND_RGBA_ADD as BLEND_RGBA_ADD,
BLEND_RGBA_MAX as BLEND_RGBA_MAX,
Expand All @@ -126,6 +127,7 @@ from .constants import (
BLEND_RGB_MAX as BLEND_RGB_MAX,
BLEND_RGB_MIN as BLEND_RGB_MIN,
BLEND_RGB_MULT as BLEND_RGB_MULT,
BLEND_RGB_OVERLAY as BLEND_RGB_OVERLAY,
BLEND_RGB_SUB as BLEND_RGB_SUB,
BLEND_SUB as BLEND_SUB,
BUTTON_LEFT as BUTTON_LEFT,
Expand Down
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/constants.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ BLEND_ALPHA_SDL2: int
BLEND_MAX: int
BLEND_MIN: int
BLEND_MULT: int
BLEND_OVERLAY: int
BLEND_PREMULTIPLIED: int
BLEND_RGBA_ADD: int
BLEND_RGBA_MAX: int
Expand All @@ -50,6 +51,7 @@ BLEND_RGB_ADD: int
BLEND_RGB_MAX: int
BLEND_RGB_MIN: int
BLEND_RGB_MULT: int
BLEND_RGB_OVERLAY: int
BLEND_RGB_SUB: int
BLEND_SUB: int
BUTTON_LEFT: int
Expand Down
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/locals.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ BLEND_ALPHA_SDL2: int
BLEND_MAX: int
BLEND_MIN: int
BLEND_MULT: int
BLEND_OVERLAY: int
BLEND_PREMULTIPLIED: int
BLEND_RGBA_ADD: int
BLEND_RGBA_MAX: int
Expand All @@ -50,6 +51,7 @@ BLEND_RGB_ADD: int
BLEND_RGB_MAX: int
BLEND_RGB_MIN: int
BLEND_RGB_MULT: int
BLEND_RGB_OVERLAY: int
BLEND_RGB_SUB: int
BLEND_SUB: int
BUTTON_LEFT: int
Expand Down
17 changes: 17 additions & 0 deletions docs/reST/ref/special_flags_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ Special Flags List
- ``BLEND_MAX`` / ``BLEND_RGB_MAX``
Takes the maximum value of each color channel

.. versionadded:: 2.5.0

- ``BLEND_OVERLAY`` / ``BLEND_RGB_OVERLAY``
Combines the Multiply and Screen blend modes. Where the base layer is light, the top
becomes lighter, and where the base layer is dark, the top becomes darker.

The formula for the Overlay blend mode is:

.. math::

\text{Overlay}(a, b) = \begin{cases}
2ab & \text{if } a < 0.5 \\
1 - 2(1 - a)(1 - b) & \text{if } a \geq 0.5
\end{cases}

Where \( a \) is the base layer value, and \( b \) is the top layer value, both normalized to the range [0, 1].

**Blending with Alpha Channel (RGBA)**

----
Expand Down
232 changes: 232 additions & 0 deletions src_c/alphablit.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ static void
blit_blend_min(SDL_BlitInfo *info);
static void
blit_blend_max(SDL_BlitInfo *info);
static void
blit_blend_overlay(SDL_BlitInfo *info);

static void
blit_blend_rgba_add(SDL_BlitInfo *info);
Expand Down Expand Up @@ -379,6 +381,38 @@ SoftBlitPyGame(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst,
blit_blend_max(&info);
break;
}
case PYGAME_BLEND_OVERLAY: {
#if !defined(__EMSCRIPTEN__)
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
if (PG_SURF_BytesPerPixel(src) == 4 &&
PG_SURF_BytesPerPixel(dst) == 4 &&
src->format->Rmask == dst->format->Rmask &&
src->format->Gmask == dst->format->Gmask &&
src->format->Bmask == dst->format->Bmask &&
!(src->format->Amask != 0 && dst->format->Amask != 0 &&
src->format->Amask != dst->format->Amask) &&
pg_has_avx2() && (src != dst)) {
blit_blend_rgb_overlay_avx2(&info);
break;
}
#if PG_ENABLE_SSE_NEON
if (PG_SURF_BytesPerPixel(src) == 4 &&
PG_SURF_BytesPerPixel(dst) == 4 &&
src->format->Rmask == dst->format->Rmask &&
src->format->Gmask == dst->format->Gmask &&
src->format->Bmask == dst->format->Bmask &&
!(src->format->Amask != 0 && dst->format->Amask != 0 &&
src->format->Amask != dst->format->Amask) &&
pg_HasSSE_NEON() && (src != dst)) {
blit_blend_rgb_overlay_sse2(&info);
break;
}
#endif /* PG_ENABLE_SSE_NEON */
#endif /* SDL_BYTEORDER == SDL_LIL_ENDIAN */
#endif /* __EMSCRIPTEN__ */
blit_blend_overlay(&info);
break;
}

case PYGAME_BLEND_RGBA_ADD: {
#if !defined(__EMSCRIPTEN__)
Expand Down Expand Up @@ -2329,6 +2363,204 @@ blit_blend_max(SDL_BlitInfo *info)
}
}

static void
blit_blend_overlay(SDL_BlitInfo *info)
{
int n;
int width = info->width;
int height = info->height;
Uint8 *src = info->s_pixels;
int srcpxskip = info->s_pxskip;
int srcskip = info->s_skip;
Uint8 *dst = info->d_pixels;
int dstpxskip = info->d_pxskip;
int dstskip = info->d_skip;
SDL_PixelFormat *srcfmt = info->src;
SDL_PixelFormat *dstfmt = info->dst;
int srcbpp = PG_FORMAT_BytesPerPixel(srcfmt);
int dstbpp = PG_FORMAT_BytesPerPixel(dstfmt);
Uint8 dR, dG, dB, dA, sR, sG, sB, sA;
Uint32 pixel;
int srcppa = info->src_blend != SDL_BLENDMODE_NONE && srcfmt->Amask;
int dstppa = info->dst_blend != SDL_BLENDMODE_NONE && dstfmt->Amask;

if (srcbpp >= 3 && dstbpp >= 3 && info->src_blend == SDL_BLENDMODE_NONE) {
size_t srcoffsetR, srcoffsetG, srcoffsetB;
size_t dstoffsetR, dstoffsetG, dstoffsetB;
if (srcbpp == 3) {
SET_OFFSETS_24(srcoffsetR, srcoffsetG, srcoffsetB, srcfmt);
}
else {
SET_OFFSETS_32(srcoffsetR, srcoffsetG, srcoffsetB, srcfmt);
}
if (dstbpp == 3) {
SET_OFFSETS_24(dstoffsetR, dstoffsetG, dstoffsetB, dstfmt);
}
else {
SET_OFFSETS_32(dstoffsetR, dstoffsetG, dstoffsetB, dstfmt);
}
while (height--) {
LOOP_UNROLLED4(
{
if (dst[dstoffsetR] < 127) {
dst[dstoffsetR] =
(dst[dstoffsetR] * src[srcoffsetR]) >> 7;
}
else {
dst[dstoffsetR] = 255 - (((255 - dst[dstoffsetR]) *
(255 - src[srcoffsetR])) >>
7);
}
if (dst[dstoffsetG] < 127) {
dst[dstoffsetG] =
(dst[dstoffsetG] * src[srcoffsetG]) >> 7;
}
else {
dst[dstoffsetG] = 255 - (((255 - dst[dstoffsetG]) *
(255 - src[srcoffsetG])) >>
7);
}
if (dst[dstoffsetB] < 127) {
dst[dstoffsetB] =
(dst[dstoffsetB] * src[srcoffsetB]) >> 7;
}
else {
dst[dstoffsetB] = 255 - (((255 - dst[dstoffsetB]) *
(255 - src[srcoffsetB])) >>
7);
}

src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
return;
}

if (srcbpp == 1) {
if (dstbpp == 1) {
while (height--) {
LOOP_UNROLLED4(
{
GET_PIXELVALS_1(sR, sG, sB, sA, src, srcfmt);
GET_PIXELVALS_1(dR, dG, dB, dA, dst, dstfmt);
BLEND_OVERLAY(sR, sG, sB, sA, dR, dG, dB, dA);
SET_PIXELVAL(dst, dstfmt, dR, dG, dB, dA);
src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
}
else if (dstbpp == 3) {
size_t offsetR, offsetG, offsetB;
SET_OFFSETS_24(offsetR, offsetG, offsetB, dstfmt);
while (height--) {
LOOP_UNROLLED4(
{
GET_PIXELVALS_1(sR, sG, sB, sA, src, srcfmt);
GET_PIXEL(pixel, dstbpp, dst);
GET_PIXELVALS(dR, dG, dB, dA, pixel, dstfmt, dstppa);
BLEND_OVERLAY(sR, sG, sB, sA, dR, dG, dB, dA);
dst[offsetR] = dR;
dst[offsetG] = dG;
dst[offsetB] = dB;
src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
}
else /* even dstbpp */
{
while (height--) {
LOOP_UNROLLED4(
{
GET_PIXELVALS_1(sR, sG, sB, sA, src, srcfmt);
GET_PIXEL(pixel, dstbpp, dst);
GET_PIXELVALS(dR, dG, dB, dA, pixel, dstfmt, dstppa);
BLEND_OVERLAY(sR, sG, sB, sA, dR, dG, dB, dA);
CREATE_PIXEL(dst, dR, dG, dB, dA, dstbpp, dstfmt);
src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
}
}
else /* srcbpp > 1 */
{
if (dstbpp == 1) {
while (height--) {
LOOP_UNROLLED4(
{
GET_PIXEL(pixel, srcbpp, src);
GET_PIXELVALS(sR, sG, sB, sA, pixel, srcfmt, srcppa);
GET_PIXELVALS_1(dR, dG, dB, dA, dst, dstfmt);
BLEND_OVERLAY(sR, sG, sB, sA, dR, dG, dB, dA);
SET_PIXELVAL(dst, dstfmt, dR, dG, dB, dA);
*dst = (Uint8)SDL_MapRGB(dstfmt, dR, dG, dB);
src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
}
else if (dstbpp == 3) {
size_t offsetR, offsetG, offsetB;
SET_OFFSETS_24(offsetR, offsetG, offsetB, dstfmt);
while (height--) {
LOOP_UNROLLED4(
{
GET_PIXEL(pixel, srcbpp, src);
GET_PIXELVALS(sR, sG, sB, sA, pixel, srcfmt, srcppa);
GET_PIXEL(pixel, dstbpp, dst);
GET_PIXELVALS(dR, dG, dB, dA, pixel, dstfmt, dstppa);
BLEND_OVERLAY(sR, sG, sB, sA, dR, dG, dB, dA);
dst[offsetR] = dR;
dst[offsetG] = dG;
dst[offsetB] = dB;
src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
}
else /* even dstbpp */
{
while (height--) {
LOOP_UNROLLED4(
{
GET_PIXEL(pixel, srcbpp, src);
GET_PIXELVALS(sR, sG, sB, sA, pixel, srcfmt, srcppa);
GET_PIXEL(pixel, dstbpp, dst);
GET_PIXELVALS(dR, dG, dB, dA, pixel, dstfmt, dstppa);
BLEND_OVERLAY(sR, sG, sB, sA, dR, dG, dB, dA);
CREATE_PIXEL(dst, dR, dG, dB, dA, dstbpp, dstfmt);
src += srcpxskip;
dst += dstpxskip;
},
n, width);
src += srcskip;
dst += dstskip;
}
}
}
}

/* --------------------------------------------------------- */

static void
Expand Down
4 changes: 4 additions & 0 deletions src_c/constants.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,14 @@ MODINIT_DEFINE(constants)
#define PYGAME_BLEND_RGB_MULT 0x3
#define PYGAME_BLEND_RGB_MIN 0x4
#define PYGAME_BLEND_RGB_MAX 0x5
#define PYGAME_BLEND_RGB_OVERLAY 0x13

#define PYGAME_BLEND_ADD PYGAME_BLEND_RGB_ADD
#define PYGAME_BLEND_SUB PYGAME_BLEND_RGB_SUB
#define PYGAME_BLEND_MULT PYGAME_BLEND_RGB_MULT
#define PYGAME_BLEND_MIN PYGAME_BLEND_RGB_MIN
#define PYGAME_BLEND_MAX PYGAME_BLEND_RGB_MAX
#define PYGAME_BLEND_OVERLAY PYGAME_BLEND_RGB_OVERLAY

#define PYGAME_BLEND_RGBA_ADD 0x6
#define PYGAME_BLEND_RGBA_SUB 0x7
Expand All @@ -215,12 +217,14 @@ MODINIT_DEFINE(constants)
DEC_CONSTS(BLEND_MULT, PYGAME_BLEND_MULT);
DEC_CONSTS(BLEND_MIN, PYGAME_BLEND_MIN);
DEC_CONSTS(BLEND_MAX, PYGAME_BLEND_MAX);
DEC_CONSTS(BLEND_OVERLAY, PYGAME_BLEND_OVERLAY);

DEC_CONSTS(BLEND_RGB_ADD, PYGAME_BLEND_RGB_ADD);
DEC_CONSTS(BLEND_RGB_SUB, PYGAME_BLEND_RGB_SUB);
DEC_CONSTS(BLEND_RGB_MULT, PYGAME_BLEND_RGB_MULT);
DEC_CONSTS(BLEND_RGB_MIN, PYGAME_BLEND_RGB_MIN);
DEC_CONSTS(BLEND_RGB_MAX, PYGAME_BLEND_RGB_MAX);
DEC_CONSTS(BLEND_RGB_OVERLAY, PYGAME_BLEND_RGB_OVERLAY);

DEC_CONSTS(BLEND_RGBA_ADD, PYGAME_BLEND_RGBA_ADD);
DEC_CONSTS(BLEND_RGBA_SUB, PYGAME_BLEND_RGBA_SUB);
Expand Down
4 changes: 4 additions & 0 deletions src_c/simd_blitters.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ void
blit_blend_rgb_min_sse2(SDL_BlitInfo *info);
void
blit_blend_premultiplied_sse2(SDL_BlitInfo *info);
void
blit_blend_rgb_overlay_sse2(SDL_BlitInfo *info);
#endif /* (defined(__SSE2__) || defined(PG_ENABLE_ARM_NEON)) */

/* Deliberately putting these outside of the preprocessor guards as I want to
Expand Down Expand Up @@ -82,4 +84,6 @@ blit_blend_rgb_min_avx2(SDL_BlitInfo *info);
void
blit_blend_premultiplied_avx2(SDL_BlitInfo *info);
void
blit_blend_rgb_overlay_avx2(SDL_BlitInfo *info);
void
premul_surf_color_by_alpha_avx2(SDL_Surface *src, SDL_Surface *dst);
Loading
Loading