diff --git a/buildconfig/stubs/pygame/transform.pyi b/buildconfig/stubs/pygame/transform.pyi index e5df5844a8..308ad6d817 100644 --- a/buildconfig/stubs/pygame/transform.pyi +++ b/buildconfig/stubs/pygame/transform.pyi @@ -261,6 +261,48 @@ def gaussian_blur( A surface with either width or height equal to 0 won't raise a ``ValueError`` """ +def bloom( + surface: Surface, + blur_radius: int, + intensity: float, + luminance_threshold: float = 0.5, + blur_type: Literal["gaussian", "box"] = "gaussian", + dest_surface: Optional[Surface] = None, +) -> Surface: + """Apply the bloom effect to a Surface + + Returns a surface where the bright pixels are blurred and added to the original + surface resulting in a bloom effect. The alpha of the pixels is preserved. + + :param Surface surface: The input surface. Does not work for indexed surfaces + and for surfaces with less than 24 bits. A ``ValueError`` will be throw in that case. + + :param int blur_radius: The radius (in pixels) that the selected blur will use. + Cannot be less than ``0`` and a value of ``0`` won't modify the surface. + + :param float intensity: Acts as a brightness multiplier for the luminance filter. + Values less or equal to zero won't modify the surface. Values greater or equal + to ``255`` will produce the same result, making the bright parts completely white. + + :param float luminance_threshold: Luminance is a property of each pixel in the range 0-1 + (regardless of alpha). The luminance threshold selects the minimum luminance + required for a pixel to be considered bright. A value of ``0`` will allow every + pixel while a value of ``1`` will discard every pixel and won't modify the surface. + + :param str blur_type: Specifies the blur to use. Allowed values are ``"box"`` and ``"gaussian"``. + + :param Surface dest_surface: An optional destination surface which is faster than creating + a new Surface. This destination surface must have the same dimensions (width, height) and + depth and format as the source Surface. + + .. note:: A higher blur radius will be smoother but will be slower. The default + gaussian blur will be more precise but box blur is many times faster, which + is preferred for real-time effects. Using real-time bloom on very big surfaces + is generally not advised, caching is preferred. + + .. versionadded:: 2.5.5 + """ + def average_surfaces( surfaces: SequenceLike[Surface], dest_surface: Optional[Surface] = None, diff --git a/src_c/doc/transform_doc.h b/src_c/doc/transform_doc.h index 3ab15a7ec4..2a0dbb2159 100644 --- a/src_c/doc/transform_doc.h +++ b/src_c/doc/transform_doc.h @@ -14,6 +14,7 @@ #define DOC_TRANSFORM_LAPLACIAN "laplacian(surface, dest_surface=None) -> Surface\nFind edges in a surface." #define DOC_TRANSFORM_BOXBLUR "box_blur(surface, radius, repeat_edge_pixels=True, dest_surface=None) -> Surface\nBlur a surface using box blur." #define DOC_TRANSFORM_GAUSSIANBLUR "gaussian_blur(surface, radius, repeat_edge_pixels=True, dest_surface=None) -> Surface\nBlur a surface using gaussian blur." +#define DOC_TRANSFORM_BLOOM "bloom(surface, blur_radius, intensity, luminance_threshold=0.5, blur_type='gaussian', dest_surface=None) -> Surface\nApply the bloom effect to a Surface" #define DOC_TRANSFORM_AVERAGESURFACES "average_surfaces(surfaces, dest_surface=None, palette_colors=1) -> Surface\nFind the average surface from many surfaces." #define DOC_TRANSFORM_AVERAGECOLOR "average_color(surface, rect=None, consider_alpha=False) -> tuple[int, int, int, int]\nFinds the average color of a surface." #define DOC_TRANSFORM_INVERT "invert(surface, dest_surface=None) -> Surface\nInverts the RGB elements of a surface." diff --git a/src_c/transform.c b/src_c/transform.c index 8869607b33..f86c9ff24c 100644 --- a/src_c/transform.c +++ b/src_c/transform.c @@ -3778,183 +3778,192 @@ surf_average_color(PyObject *self, PyObject *args, PyObject *kwargs) return Py_BuildValue("(bbbb)", r, g, b, a); } +#define GAUSSIAN_BLUR(src, dst, sigma, repeat, assign_code) \ + Uint8 *srcpx = (Uint8 *)src->pixels; \ + Uint8 *dstpx = (Uint8 *)dst->pixels; \ + Uint8 nb = PG_SURF_BytesPerPixel(src); \ + int w = dst->w, h = dst->h; \ + int dst_pitch = dst->pitch; \ + int src_pitch = src->pitch; \ + int i, j, x, y, color; \ + int kernel_radius = sigma * 2; \ + float lut_sum = 0.0; \ + float *overall_buf = \ + malloc(sizeof(float) * (dst_pitch * 2 + kernel_radius + 1)); \ + float *buf = overall_buf; \ + float *buf2 = overall_buf + dst_pitch; \ + float *lut = overall_buf + dst_pitch * 2; \ + if (overall_buf == NULL) { \ + return -1; \ + } \ + \ + for (i = 0; i <= kernel_radius; i++) { /* init gaussian lut*/ \ + /* Gaussian function */ \ + lut[i] = \ + expf(-powf((float)i, 2.0f) / (2.0f * powf((float)sigma, 2.0f))); \ + lut_sum += lut[i] * 2; \ + } \ + lut_sum -= lut[0]; \ + for (i = 0; i <= kernel_radius; i++) { \ + lut[i] /= lut_sum; \ + } \ + \ + for (i = 0; i < dst_pitch; i++) { \ + buf[i] = 0.0; \ + buf2[i] = 0.0; \ + } \ + \ + for (y = 0; y < h; y++) { \ + for (j = -kernel_radius; j <= kernel_radius; j++) { \ + for (i = 0; i < dst_pitch; i++) { \ + if (y + j >= 0 && y + j < h) { \ + buf[i] += \ + (float)srcpx[src_pitch * (y + j) + i] * lut[abs(j)]; \ + } \ + else if (repeat) { \ + if (y + j < 0) { \ + buf[i] += (float)srcpx[i] * lut[abs(j)]; \ + } \ + else { \ + buf[i] += (float)srcpx[src_pitch * (h - 1) + i] * \ + lut[abs(j)]; \ + } \ + } \ + } \ + } \ + \ + for (x = 0; x < w; x++) { \ + for (j = -kernel_radius; j <= kernel_radius; j++) { \ + for (color = 0; color < nb; color++) { \ + if (x + j >= 0 && x + j < w) { \ + buf2[nb * x + color] += \ + buf[nb * (x + j) + color] * lut[abs(j)]; \ + } \ + else if (repeat) { \ + if (x + j < 0) { \ + buf2[nb * x + color] += buf[color] * lut[abs(j)]; \ + } \ + else { \ + buf2[nb * x + color] += \ + buf[nb * (w - 1) + color] * lut[abs(j)]; \ + } \ + } \ + } \ + } \ + } \ + for (i = 0; i < dst_pitch; i++) { \ + Uint8 blur_color = (Uint8)buf2[i]; \ + dstpx[dst_pitch * y + i] = blur_color; \ + assign_code buf[i] = 0.0; \ + buf2[i] = 0.0; \ + } \ + } \ + \ + free(overall_buf); \ + return 0; + +#define BOX_BLUR(src, dst, radius, repeat, do_assign, assign_code) \ + \ + /* Reference : \ + * https://blog.csdn.net/blogshinelee/article/details/80997324 */ \ + \ + Uint8 *srcpx = (Uint8 *)src->pixels; \ + Uint8 *dstpx = (Uint8 *)dst->pixels; \ + Uint8 nb = PG_SURF_BytesPerPixel(src); \ + int w = dst->w, h = dst->h; \ + int dst_pitch = dst->pitch; \ + int src_pitch = src->pitch; \ + int i, x, y, color; \ + Uint32 *overall_buf = malloc(sizeof(Uint32) * (dst_pitch * 2 + nb)); \ + Uint32 *buf = overall_buf; \ + Uint32 *sum_v = overall_buf + dst_pitch; \ + Uint32 *sum_h = overall_buf + dst_pitch * 2; \ + if (overall_buf == NULL) { \ + return -1; \ + } \ + memset(sum_v, 0, dst_pitch * sizeof(Uint32)); \ + for (y = 0; y <= radius; y++) { /* y-pre */ \ + for (i = 0; i < dst_pitch; i++) { \ + sum_v[i] += srcpx[src_pitch * y + i]; \ + } \ + } \ + if (repeat) { \ + for (i = 0; i < dst_pitch; i++) { \ + sum_v[i] += srcpx[i] * radius; \ + } \ + } \ + for (y = 0; y < h; y++) { /* y */ \ + for (i = 0; i < dst_pitch; i++) { \ + buf[i] = sum_v[i] / (radius * 2 + 1); \ + \ + /* update vertical sum */ \ + if (y - radius >= 0) { \ + sum_v[i] -= srcpx[src_pitch * (y - radius) + i]; \ + } \ + else if (repeat) { \ + sum_v[i] -= srcpx[i]; \ + } \ + if (y + radius + 1 < h) { \ + sum_v[i] += srcpx[src_pitch * (y + radius + 1) + i]; \ + } \ + else if (repeat) { \ + sum_v[i] += srcpx[src_pitch * (h - 1) + i]; \ + } \ + } \ + \ + memset(sum_h, 0, nb * sizeof(Uint32)); \ + for (x = 0; x <= radius; x++) { /* x-pre */ \ + for (color = 0; color < nb; color++) { \ + sum_h[color] += buf[x * nb + color]; \ + } \ + } \ + if (repeat) { \ + for (color = 0; color < nb; color++) { \ + sum_h[color] += buf[color] * radius; \ + } \ + } \ + for (x = 0; x < w; x++) { /* x */ \ + for (color = 0; color < nb; color++) { \ + Uint8 blur_color = sum_h[color] / (radius * 2 + 1); \ + /* Will always be SDL_TRUE on regular blur */ \ + /* Without it bloom will break */ \ + if (do_assign) { \ + dstpx[dst_pitch * y + nb * x + color] = blur_color; \ + } \ + assign_code \ + \ + /* update horizontal sum */ \ + if (x - radius >= 0) \ + { \ + sum_h[color] -= buf[(x - radius) * nb + color]; \ + } \ + else if (repeat) \ + { \ + sum_h[color] -= buf[color]; \ + } \ + if (x + radius + 1 < w) { \ + sum_h[color] += buf[(x + radius + 1) * nb + color]; \ + } \ + else if (repeat) { \ + sum_h[color] += buf[(w - 1) * nb + color]; \ + } \ + } \ + } \ + } \ + \ + free(overall_buf); \ + return 0; + static int box_blur(SDL_Surface *src, SDL_Surface *dst, int radius, SDL_bool repeat) { - // Reference : https://blog.csdn.net/blogshinelee/article/details/80997324 - - Uint8 *srcpx = (Uint8 *)src->pixels; - Uint8 *dstpx = (Uint8 *)dst->pixels; - Uint8 nb = PG_SURF_BytesPerPixel(src); - int w = dst->w, h = dst->h; - int dst_pitch = dst->pitch; - int src_pitch = src->pitch; - int i, x, y, color; - - // Allocate bytes for buf, sum_v, and sum_h at once to reduce allocations. - Uint32 *overall_buf = malloc(sizeof(Uint32) * (dst_pitch * 2 + nb)); - Uint32 *buf = overall_buf; - Uint32 *sum_v = overall_buf + dst_pitch; - Uint32 *sum_h = overall_buf + dst_pitch * 2; - - if (overall_buf == NULL) { - return -1; - } - - memset(sum_v, 0, dst_pitch * sizeof(Uint32)); - for (y = 0; y <= radius; y++) { // y-pre - for (i = 0; i < dst_pitch; i++) { - sum_v[i] += srcpx[src_pitch * y + i]; - } - } - if (repeat) { - for (i = 0; i < dst_pitch; i++) { - sum_v[i] += srcpx[i] * radius; - } - } - for (y = 0; y < h; y++) { // y - for (i = 0; i < dst_pitch; i++) { - buf[i] = sum_v[i] / (radius * 2 + 1); - - // update vertical sum - if (y - radius >= 0) { - sum_v[i] -= srcpx[src_pitch * (y - radius) + i]; - } - else if (repeat) { - sum_v[i] -= srcpx[i]; - } - if (y + radius + 1 < h) { - sum_v[i] += srcpx[src_pitch * (y + radius + 1) + i]; - } - else if (repeat) { - sum_v[i] += srcpx[src_pitch * (h - 1) + i]; - } - } - - memset(sum_h, 0, nb * sizeof(Uint32)); - for (x = 0; x <= radius; x++) { // x-pre - for (color = 0; color < nb; color++) { - sum_h[color] += buf[x * nb + color]; - } - } - if (repeat) { - for (color = 0; color < nb; color++) { - sum_h[color] += buf[color] * radius; - } - } - for (x = 0; x < w; x++) { // x - for (color = 0; color < nb; color++) { - dstpx[dst_pitch * y + nb * x + color] = - sum_h[color] / (radius * 2 + 1); - - // update horizontal sum - if (x - radius >= 0) { - sum_h[color] -= buf[(x - radius) * nb + color]; - } - else if (repeat) { - sum_h[color] -= buf[color]; - } - if (x + radius + 1 < w) { - sum_h[color] += buf[(x + radius + 1) * nb + color]; - } - else if (repeat) { - sum_h[color] += buf[(w - 1) * nb + color]; - } - } - } - } - - free(overall_buf); - return 0; + BOX_BLUR(src, dst, radius, repeat, SDL_TRUE, ); } static int gaussian_blur(SDL_Surface *src, SDL_Surface *dst, int sigma, SDL_bool repeat) { - Uint8 *srcpx = (Uint8 *)src->pixels; - Uint8 *dstpx = (Uint8 *)dst->pixels; - Uint8 nb = PG_SURF_BytesPerPixel(src); - int w = dst->w, h = dst->h; - int dst_pitch = dst->pitch; - int src_pitch = src->pitch; - int i, j, x, y, color; - int kernel_radius = sigma * 2; - float lut_sum = 0.0; - - // Allocate bytes for buf, buf2, and lut at once to reduce allocations. - float *overall_buf = - malloc(sizeof(float) * (dst_pitch * 2 + kernel_radius + 1)); - float *buf = overall_buf; - float *buf2 = overall_buf + dst_pitch; - float *lut = overall_buf + dst_pitch * 2; - - if (overall_buf == NULL) { - return -1; - } - - for (i = 0; i <= kernel_radius; i++) { // init gaussian lut - // Gaussian function - lut[i] = - expf(-powf((float)i, 2.0f) / (2.0f * powf((float)sigma, 2.0f))); - lut_sum += lut[i] * 2; - } - lut_sum -= lut[0]; - for (i = 0; i <= kernel_radius; i++) { - lut[i] /= lut_sum; - } - - for (i = 0; i < dst_pitch; i++) { - buf[i] = 0.0; - buf2[i] = 0.0; - } - - for (y = 0; y < h; y++) { - for (j = -kernel_radius; j <= kernel_radius; j++) { - for (i = 0; i < dst_pitch; i++) { - if (y + j >= 0 && y + j < h) { - buf[i] += - (float)srcpx[src_pitch * (y + j) + i] * lut[abs(j)]; - } - else if (repeat) { - if (y + j < 0) { - buf[i] += (float)srcpx[i] * lut[abs(j)]; - } - else { - buf[i] += (float)srcpx[src_pitch * (h - 1) + i] * - lut[abs(j)]; - } - } - } - } - - for (x = 0; x < w; x++) { - for (j = -kernel_radius; j <= kernel_radius; j++) { - for (color = 0; color < nb; color++) { - if (x + j >= 0 && x + j < w) { - buf2[nb * x + color] += - buf[nb * (x + j) + color] * lut[abs(j)]; - } - else if (repeat) { - if (x + j < 0) { - buf2[nb * x + color] += buf[color] * lut[abs(j)]; - } - else { - buf2[nb * x + color] += - buf[nb * (w - 1) + color] * lut[abs(j)]; - } - } - } - } - } - for (i = 0; i < dst_pitch; i++) { - dstpx[dst_pitch * y + i] = (Uint8)buf2[i]; - buf[i] = 0.0; - buf2[i] = 0.0; - } - } - - free(overall_buf); - return 0; + GAUSSIAN_BLUR(src, dst, sigma, repeat, ); } static SDL_Surface * @@ -4111,6 +4120,237 @@ surf_gaussian_blur(PyObject *self, PyObject *args, PyObject *kwargs) return (PyObject *)pgSurface_New(new_surf); } +static int +bloom_gaussian(SDL_Surface *bloom_src, SDL_Surface *bpfsurf, + SDL_Surface *retsurf, int sigma) +{ + Uint8 *src_pixels = (Uint8 *)bloom_src->pixels; + Uint8 *ret_pixels = (Uint8 *)retsurf->pixels; + int bloom_src_pitch = bloom_src->pitch; + int ret_pitch = retsurf->pitch; + + GAUSSIAN_BLUR(bpfsurf, bpfsurf, sigma, SDL_FALSE, + Uint8 src_color = src_pixels[bloom_src_pitch * y + i]; + int new_color = (src_color + blur_color); + ret_pixels[ret_pitch * y + i] = + (Uint8)(new_color > 255 ? 255 : new_color);) +} + +static int +bloom_box(SDL_Surface *bloom_src, SDL_Surface *bpfsurf, SDL_Surface *retsurf, + int radius) +{ + Uint8 *src_pixels = (Uint8 *)bloom_src->pixels; + Uint8 *ret_pixels = (Uint8 *)retsurf->pixels; + int bloom_src_pitch = bloom_src->pitch; + int ret_pitch = retsurf->pitch; + + BOX_BLUR( + bpfsurf, bpfsurf, radius, SDL_FALSE, SDL_FALSE, + Uint8 src_color = src_pixels[bloom_src_pitch * y + nb * x + color]; + int new_color = (src_color + blur_color); + ret_pixels[ret_pitch * y + nb * x + color] = + (Uint8)(new_color > 255 ? 255 : new_color);) +} + +// Return a new bright-pass-filter surface. PyExc_MemoryError is set +// automatically. The caller is responsible for freeing the returned surface +SDL_Surface * +luminance_filter(SDL_Surface *src, float intensity, float threshold) +{ + SDL_Surface *bpfsurf = newsurf_fromsurf(src, src->w, src->h); + + PG_PixelFormat *fmt = PG_GetSurfaceFormat(src); + PG_PixelFormat *dfmt = PG_GetSurfaceFormat(bpfsurf); + if (fmt == NULL || dfmt == NULL) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + + Uint8 src_r, src_g, src_b; + const Uint32 amask = fmt->Amask; + + Uint32 *srcp = (Uint32 *)src->pixels; + Uint32 *dstp = (Uint32 *)bpfsurf->pixels; + + const int src_skip = src->pitch / 4 - src->w; + const int dst_skip = bpfsurf->pitch / 4 - bpfsurf->w; + + int x, y; + float c_mul = 255.0f * intensity; + for (y = 0; y < src->h; y++) { + for (x = 0; x < src->w; x++) { + Uint32 pxl = *srcp; + + src_r = (Uint8)(pxl >> fmt->Rshift) & 0xFF; + src_g = (Uint8)(pxl >> fmt->Gshift) & 0xFF; + src_b = (Uint8)(pxl >> fmt->Bshift) & 0xFF; + + float r = (float)src_r / 255.0f, g = (float)src_g / 255.0f, + b = (float)src_b / 255.0f; + float luminance = r * 0.299f + g * 0.587f + b * 0.114f; + + if (luminance > threshold && luminance != 0) { + float c = ((luminance - threshold) / luminance) * c_mul; + + float rc = r * c; + float gc = g * c; + float bc = b * c; + + rc = MIN(255, MAX(0, rc)); + gc = MIN(255, MAX(0, gc)); + bc = MIN(255, MAX(0, bc)); + + Uint8 new_r = (Uint8)rc; + Uint8 new_g = (Uint8)gc; + Uint8 new_b = (Uint8)bc; + + Uint32 new_pixel = + (new_r >> PG_FORMAT_R_LOSS(dfmt)) << dfmt->Rshift | + (new_g >> PG_FORMAT_G_LOSS(dfmt)) << dfmt->Gshift | + (new_b >> PG_FORMAT_B_LOSS(dfmt)) << dfmt->Bshift | + (pxl & amask); + + *dstp = new_pixel; + } + + srcp++; + dstp++; + } + srcp += src_skip; + dstp += dst_skip; + } + + return bpfsurf; +} + +SDL_Surface * +bloom(pgSurfaceObject *srcobj, pgSurfaceObject *dstobj, float intensity, + float threshold, int blur_radius, char blur_type) +{ + // Reference: https://github.com/yoyoberenguer/BloomEffect + + SDL_Surface *src = NULL; + SDL_Surface *retsurf = NULL; + int result = 0; + + src = pgSurface_AsSurface(srcobj); + + if (PG_GetSurfacePalette(src) != NULL) { + return RAISE(PyExc_ValueError, "Indexed surfaces cannot be bloomed."); + } + + if (!dstobj) { + retsurf = newsurf_fromsurf(src, src->w, src->h); + if (!retsurf) { + return NULL; + } + } + else { + retsurf = pgSurface_AsSurface(dstobj); + } + + if ((retsurf->w) != (src->w) || (retsurf->h) != (src->h)) { + return RAISE(PyExc_ValueError, + "Destination surface not the same size."); + } + + if (PG_SURF_BytesPerPixel(src) < 3 || PG_SURF_BytesPerPixel(retsurf) < 3) { + return RAISE(PyExc_ValueError, + "Bloom is only allowed for 24 or 32 bit surfaces."); + } + + if (PG_SURF_BytesPerPixel(src) != PG_SURF_BytesPerPixel(retsurf) || + PG_SURF_FORMATENUM(src) != PG_SURF_FORMATENUM(retsurf)) { + return RAISE(PyExc_ValueError, + "Source and destination surfaces need the same format."); + } + + if (retsurf->w == 0 || retsurf->h == 0) { + return retsurf; + } + + SDL_Surface *bpfsurf = luminance_filter(src, intensity, threshold); + if (bpfsurf == NULL) { + return NULL; + } + + if (blur_type == 'g') { + result = bloom_gaussian(src, bpfsurf, retsurf, blur_radius); + } + else if (blur_type == 'b') { + result = bloom_box(src, bpfsurf, retsurf, blur_radius); + } + + SDL_FreeSurface(bpfsurf); + + // Routines only set error flag if memory allocation failed + // Setting Python exception here outside of Py_ THREADS block to be safe + if (result) { + PyErr_NoMemory(); + return NULL; + } + + return retsurf; +} + +static PyObject * +surf_bloom(PyObject *self, PyObject *args, PyObject *kwargs) +{ + pgSurfaceObject *dst_surf_obj = NULL; + pgSurfaceObject *src_surf_obj; + SDL_Surface *new_surf = NULL; + const char *blur_type_str = "gaussian"; + + int blur_radius; + float intensity; + float threshold = 0.5f; + char blur_type = 'g'; + + static char *kwlist[] = {"surface", + "blur_radius", + "intensity", + "luminance_threshold", + "blur_type", + "dest_surface", + 0}; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "O!if|fsO!", kwlist, &pgSurface_Type, &src_surf_obj, + &blur_radius, &intensity, &threshold, &blur_type_str, + &pgSurface_Type, &dst_surf_obj)) { + return NULL; + } + + if (strcmp(blur_type_str, "gaussian") == 0) { + blur_type = 'g'; + } + else if (strcmp(blur_type_str, "box") == 0) { + blur_type = 'b'; + } + else { + return RAISE(PyExc_ValueError, + "Bloom blur type must be either 'gaussian' or 'box'."); + } + + if (blur_radius < 0) { + return RAISE(PyExc_ValueError, + "The blur radius should not be less than zero."); + } + + new_surf = bloom(src_surf_obj, dst_surf_obj, intensity, threshold, + blur_radius, blur_type); + if (!new_surf) { + return NULL; + } + + if (dst_surf_obj) { + Py_INCREF(dst_surf_obj); + return (PyObject *)dst_surf_obj; + } + + return (PyObject *)pgSurface_New(new_surf); +} + void invert_non_simd(SDL_Surface *src, PG_PixelFormat *src_format, SDL_Surface *newsurf, PG_PixelFormat *newsurf_format) @@ -4264,6 +4504,8 @@ static PyMethodDef _transform_methods[] = { DOC_TRANSFORM_INVERT}, {"grayscale", (PyCFunction)surf_grayscale, METH_VARARGS | METH_KEYWORDS, DOC_TRANSFORM_GRAYSCALE}, + {"bloom", (PyCFunction)surf_bloom, METH_VARARGS | METH_KEYWORDS, + DOC_TRANSFORM_BLOOM}, {"solid_overlay", (PyCFunction)surf_solid_overlay, METH_VARARGS | METH_KEYWORDS, DOC_TRANSFORM_SOLIDOVERLAY}, {"hsl", (PyCFunction)surf_hsl, METH_VARARGS | METH_KEYWORDS, diff --git a/test/transform_test.py b/test/transform_test.py index 580bfdc66e..36fba85de3 100644 --- a/test/transform_test.py +++ b/test/transform_test.py @@ -1906,6 +1906,55 @@ def test_blur_zero_size_surface(self): self.assertEqual(pygame.transform.box_blur(surface, 3).get_size(), (0, 20)) self.assertEqual(pygame.transform.gaussian_blur(surface, 3).get_size(), (0, 20)) + def test_bloom_args(self): + surface = pygame.Surface((20, 20), pygame.SRCALPHA) + surface.fill(0) + pygame.draw.circle(surface, "white", (10, 10), 5) + dest_surface = pygame.Surface((20, 20), pygame.SRCALPHA) + dest_surface.fill(0) + + # Check the function runs for a variety of arguments + for blur_radius in range(5, 10, 15): + for intensity in [0.5, 1, 2, 4]: + for threshold in [-1, 0.1, 0.2, 0.5, 0.8, 2]: + for blur_type in ["gaussian", "box"]: + for kwargs in [{}, {"dest_surface": dest_surface}]: + pygame.transform.bloom( + surface, + blur_radius, + intensity, + threshold, + blur_type, + **kwargs, + ) + + def test_bloom_result(self): + surface = pygame.Surface((20, 20), pygame.SRCALPHA) + surface.fill(0) + pygame.draw.circle(surface, (200, 200, 200, 255), (10, 10), 5) + bloom_surf = pygame.transform.bloom(surface, 5, 2, 0.5, "box") + + # Corners should not change + for pix_pos in [(0, 0), (19, 0), (0, 19), (19, 19)]: + self.assertEqual(bloom_surf.get_at(pix_pos), (0, 0, 0, 0)) + + # The circle should get brighter + for pix_pos in [(9, 9), (11, 9), (9, 11), (11, 11)]: + pixel = bloom_surf.get_at(pix_pos) + self.assertEqual(pixel.a, 255) + for i in range(3): + self.assertGreater(pixel[i], 200) + + # Near the circle there should be blooming pixels + for pix_pos in [(6, 6), (13, 6), (6, 13), (13, 13)]: + pixel = bloom_surf.get_at(pix_pos) + value = pixel.r + self.assertGreater(value, 0) + for i in range(3): + self.assertEqual(pixel[i], value) + self.assertGreater(pixel.a, 0) + self.assertLess(pixel.a, 255) + def test_flip(self): """honors the set_color key on the returned surface from flip.""" image_loaded = pygame.image.load(example_path("data/chimp.webp"))