diff --git a/buildconfig/stubs/pygame/surface.pyi b/buildconfig/stubs/pygame/surface.pyi index 4afbd1b5b1..2962f6abb9 100644 --- a/buildconfig/stubs/pygame/surface.pyi +++ b/buildconfig/stubs/pygame/surface.pyi @@ -162,5 +162,6 @@ class Surface: def get_buffer(self) -> BufferProxy: ... def get_blendmode(self) -> int: ... def premul_alpha(self) -> Surface: ... + def premul_alpha_ip(self) -> Surface: ... SurfaceType = Surface diff --git a/docs/reST/ref/surface.rst b/docs/reST/ref/surface.rst index 87c14469f5..6b0130a20d 100644 --- a/docs/reST/ref/surface.rst +++ b/docs/reST/ref/surface.rst @@ -1041,6 +1041,23 @@ .. ## Surface.premul_alpha ## + .. method:: premul_alpha_ip + + | :sl:`multiplies the RGB channels by the surface alpha channel.` + | :sg:`premul_alpha_ip() -> Surface` + + Multiplies the RGB channels of the surface by the alpha channel in place and returns the surface. + + Surfaces without an alpha channel cannot use this method and will return an error if you use + it on them. It is best used on 32 bit surfaces (the default on most platforms) as the blitting + on these surfaces can be accelerated by SIMD versions of the pre-multiplied blitter. + + Refer to the :meth:`premul_alpha` method for more information. + + .. versionadded:: 2.5.1 + + .. ## Surface.premul_alpha_ip ## + .. attribute:: width | :sl:`Surface width in pixels (read-only)` diff --git a/src_c/doc/surface_doc.h b/src_c/doc/surface_doc.h index 004440e488..38a6fb7120 100644 --- a/src_c/doc/surface_doc.h +++ b/src_c/doc/surface_doc.h @@ -52,6 +52,7 @@ #define DOC_SURFACE_GETBUFFER "get_buffer() -> BufferProxy\nacquires a buffer object for the pixels of the Surface." #define DOC_SURFACE_PIXELSADDRESS "_pixels_address -> int\npixel buffer address" #define DOC_SURFACE_PREMULALPHA "premul_alpha() -> Surface\nreturns a copy of the surface with the RGB channels pre-multiplied by the alpha channel." +#define DOC_SURFACE_PREMULALPHAIP "premul_alpha_ip() -> Surface\nmultiplies the RGB channels by the surface alpha channel." #define DOC_SURFACE_WIDTH "width -> int\nSurface width in pixels (read-only)" #define DOC_SURFACE_HEIGHT "height -> int\nSurface height in pixels (read-only)" #define DOC_SURFACE_SIZE "height -> tuple[int, int]\nSurface size in pixels (read-only)" diff --git a/src_c/surface.c b/src_c/surface.c index 3c6a9e84f9..ce697ac9b3 100644 --- a/src_c/surface.c +++ b/src_c/surface.c @@ -194,6 +194,8 @@ static PyObject * surf_get_pixels_address(PyObject *self, PyObject *closure); static PyObject * surf_premul_alpha(pgSurfaceObject *self, PyObject *args); +static PyObject * +surf_premul_alpha_ip(pgSurfaceObject *self, PyObject *args); static int _view_kind(PyObject *obj, void *view_kind_vptr); static int @@ -318,6 +320,8 @@ static struct PyMethodDef surface_methods[] = { {"get_buffer", surf_get_buffer, METH_NOARGS, DOC_SURFACE_GETBUFFER}, {"premul_alpha", (PyCFunction)surf_premul_alpha, METH_NOARGS, DOC_SURFACE_PREMULALPHA}, + {"premul_alpha_ip", (PyCFunction)surf_premul_alpha_ip, METH_NOARGS, + DOC_SURFACE_PREMULALPHAIP}, {NULL, NULL, 0, NULL}}; @@ -3124,6 +3128,29 @@ surf_premul_alpha(pgSurfaceObject *self, PyObject *_null) return final; } +static PyObject * +surf_premul_alpha_ip(pgSurfaceObject *self, PyObject *_null) +{ + SDL_Surface *surf = pgSurface_AsSurface(self); + SURF_INIT_CHECK(surf) + + if (!surf->w || !surf->h) + Py_RETURN_NONE; + + pgSurface_Prep(self); + + if (premul_surf_color_by_alpha(surf, surf) != 0) { + return RAISE(PyExc_ValueError, + "source surface to be alpha pre-multiplied must have " + "alpha channel"); + } + + pgSurface_Unprep(self); + + Py_INCREF(self); + return (PyObject *)self; +} + static int _get_buffer_0D(PyObject *obj, Py_buffer *view_p, int flags) { diff --git a/test/surface_test.py b/test/surface_test.py index 373769868c..4c0ff79b9c 100644 --- a/test/surface_test.py +++ b/test/surface_test.py @@ -4038,6 +4038,76 @@ def test_surface_premul_alpha(self): ), ) + def test_surface_premul_alpha_ip(self): + """Ensure that .premul_alpha_ip() works correctly""" + + # basic functionality at valid bit depths - 32, 16 & 8 + s1 = pygame.Surface((100, 100), pygame.SRCALPHA, 32) + s1.fill(pygame.Color(255, 255, 255, 100)) + s1.premul_alpha_ip() + s1_alpha = s1 + self.assertEqual(s1_alpha.get_at((50, 50)), pygame.Color(100, 100, 100, 100)) + + # 16-bit color has less precision + s2 = pygame.Surface((100, 100), pygame.SRCALPHA, 16) + s2.fill( + pygame.Color( + int(15 / 15 * 255), + int(15 / 15 * 255), + int(15 / 15 * 255), + int(10 / 15 * 255), + ) + ) + s2.premul_alpha_ip() + s2_alpha = s2 + self.assertEqual( + s2_alpha.get_at((50, 50)), + pygame.Color( + int(10 / 15 * 255), + int(10 / 15 * 255), + int(10 / 15 * 255), + int(10 / 15 * 255), + ), + ) + + # invalid surface - we need alpha to pre-multiply + invalid_surf = pygame.Surface((100, 100), 0, 32) + invalid_surf.fill(pygame.Color(255, 255, 255, 100)) + with self.assertRaises(ValueError): + invalid_surf.premul_alpha_ip() + + # churn a bunch of values + test_colors = [ + (200, 30, 74), + (76, 83, 24), + (184, 21, 6), + (74, 4, 74), + (76, 83, 24), + (184, 21, 234), + (160, 30, 74), + (96, 147, 204), + (198, 201, 60), + (132, 89, 74), + (245, 9, 224), + (184, 112, 6), + ] + + for r, g, b in test_colors: + for a in range(255): + with self.subTest(r=r, g=g, b=b, a=a): + surf = pygame.Surface((10, 10), pygame.SRCALPHA, 32) + surf.fill(pygame.Color(r, g, b, a)) + surf.premul_alpha_ip() + self.assertEqual( + surf.get_at((5, 5)), + Color( + ((r + 1) * a) >> 8, + ((g + 1) * a) >> 8, + ((b + 1) * a) >> 8, + a, + ), + ) + class SurfaceSelfBlitTest(unittest.TestCase): """Blit to self tests.