diff --git a/buildconfig/stubs/pygame/transform.pyi b/buildconfig/stubs/pygame/transform.pyi index f1a5f27918..dc42edf87f 100644 --- a/buildconfig/stubs/pygame/transform.pyi +++ b/buildconfig/stubs/pygame/transform.pyi @@ -70,6 +70,14 @@ def gaussian_blur( repeat_edge_pixels: bool = True, dest_surface: Optional[Surface] = None ) -> Surface: ... +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: ... def hsl( surface: Surface, hue: float = 0, diff --git a/docs/reST/ref/transform.rst b/docs/reST/ref/transform.rst index 14a23b7aaa..bdccb9345b 100644 --- a/docs/reST/ref/transform.rst +++ b/docs/reST/ref/transform.rst @@ -279,6 +279,42 @@ Instead, always begin with the original image and scale to the desired size.) .. ## pygame.transform.gaussian_blur ## +.. function:: bloom + + | :sl:`apply the bloom effect to a surface` + | :sg:`bloom(surface, blur_radius, intensity, luminance_threshold=0.5, blur_type='gaussian', dest_surface=None) -> 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.2 + .. function:: average_surfaces | :sl:`find the average surface from many surfaces.` diff --git a/src_c/doc/transform_doc.h b/src_c/doc/transform_doc.h index d768240a52..dae0cce082 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\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 c584de990c..30376d1d25 100644 --- a/src_c/transform.c +++ b/src_c/transform.c @@ -3564,170 +3564,186 @@ 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 *buf = malloc(dst_pitch * sizeof(float)); \ + float *buf2 = malloc(dst_pitch * sizeof(float)); \ + float *lut = malloc((kernel_radius + 1) * sizeof(float)); \ + float lut_sum = 0.0; \ + \ + 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(buf); \ + free(buf2); \ + free(lut); + +#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 *buf = malloc(dst_pitch * sizeof(Uint32)); \ + Uint32 *sum_v = malloc(dst_pitch * sizeof(Uint32)); \ + Uint32 *sum_h = malloc(nb * sizeof(Uint32)); \ + \ + 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(buf); \ + free(sum_v); \ + free(sum_h); + static void 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; - Uint32 *buf = malloc(dst_pitch * sizeof(Uint32)); - Uint32 *sum_v = malloc(dst_pitch * sizeof(Uint32)); - Uint32 *sum_h = malloc(nb * sizeof(Uint32)); - - 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(buf); - free(sum_v); - free(sum_h); + BOX_BLUR(src, dst, radius, repeat, SDL_TRUE, ); } static void 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 *buf = malloc(dst_pitch * sizeof(float)); - float *buf2 = malloc(dst_pitch * sizeof(float)); - float *lut = malloc((kernel_radius + 1) * sizeof(float)); - float lut_sum = 0.0; - - 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(buf); - free(buf2); - free(lut); + GAUSSIAN_BLUR(src, dst, sigma, repeat, ); } static SDL_Surface * @@ -3873,6 +3889,227 @@ surf_gaussian_blur(PyObject *self, PyObject *args, PyObject *kwargs) return (PyObject *)pgSurface_New(new_surf); } +static void +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 void +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); + + SDL_PixelFormat *fmt = src->format; + SDL_PixelFormat *dfmt = bpfsurf->format; + + 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 >> dfmt->Rloss) << dfmt->Rshift | + (new_g >> dfmt->Gloss) << dfmt->Gshift | + (new_b >> dfmt->Bloss) << 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; + + src = pgSurface_AsSurface(srcobj); + + if (src->format->palette) { + 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) || + src->format->Rmask != retsurf->format->Rmask || + src->format->Gmask != retsurf->format->Gmask || + src->format->Bmask != retsurf->format->Bmask || + src->format->Amask != retsurf->format->Amask) { + 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') { + bloom_gaussian(src, bpfsurf, retsurf, blur_radius); + } + else if (blur_type == 'b') { + bloom_box(src, bpfsurf, retsurf, blur_radius); + } + + SDL_FreeSurface(bpfsurf); + + 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, SDL_Surface *newsurf) { @@ -4014,6 +4251,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 0078e5b3a5..cdb010d16b 100644 --- a/test/transform_test.py +++ b/test/transform_test.py @@ -1900,6 +1900,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.png"))