From dbecb950242e51f5a55da916590b0d187515189c Mon Sep 17 00:00:00 2001 From: nobytesgiven <80068279+nobytesgiven@users.noreply.github.com> Date: Tue, 25 Oct 2022 18:56:06 +0300 Subject: [PATCH] Added Box and Gaussian blurring (#2770) * Added Box and Gaussian blurring * Removed dependence of gaussian blur to box blur & Fixed precision errors Co-authored-by: nobytesgiven --- src/raylib.h | 1 + src/rtextures.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/src/raylib.h b/src/raylib.h index 4c353ac5..dc027591 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1258,6 +1258,7 @@ RLAPI void ImageAlphaCrop(Image *image, float threshold); RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color diff --git a/src/rtextures.c b/src/rtextures.c index 569832ac..5e6f344d 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -194,6 +194,10 @@ #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 #endif +#ifndef GAUSSIAN_BLUR_ITERATIONS + #define GAUSSIAN_BLUR_ITERATIONS 4 // Number of box blur iterations to approximate gaussian blur +#endif + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -1494,6 +1498,159 @@ void ImageAlphaPremultiply(Image *image) ImageFormat(image, format); } +// Apply box blur +void ImageBlurGaussian(Image *image, int blurSize) { + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + ImageAlphaPremultiply(image); + + Color *pixels = LoadImageColors(*image); + Color *pixelsCopy = LoadImageColors(*image); + + // Loop switches between pixelsCopy1 and pixelsCopy2 + Vector4 *pixelsCopy1 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + Vector4 *pixelsCopy2 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + + for (int i = 0; i < (image->height)*(image->width); i++) { + pixelsCopy1[i].x = pixels[i].r; + pixelsCopy1[i].y = pixels[i].g; + pixelsCopy1[i].z = pixels[i].b; + pixelsCopy1[i].w = pixels[i].a; + } + + // Repeated convolution of rectangular window signal by itself converges to a gaussian distribution + for (int j = 0; j < GAUSSIAN_BLUR_ITERATIONS; j++) { + // Horizontal motion blur + for (int row = 0; row < image->height; row++) + { + float avgR = 0.0f; + float avgG = 0.0f; + float avgB = 0.0f; + float avgAlpha = 0.0f; + int convolutionSize = blurSize+1; + + for (int i = 0; i < blurSize+1; i++) + { + avgR += pixelsCopy1[row*image->width + i].x; + avgG += pixelsCopy1[row*image->width + i].y; + avgB += pixelsCopy1[row*image->width + i].z; + avgAlpha += pixelsCopy1[row*image->width + i].w; + } + + pixelsCopy2[row*image->width].x = avgR/convolutionSize; + pixelsCopy2[row*image->width].y = avgG/convolutionSize; + pixelsCopy2[row*image->width].z = avgB/convolutionSize; + pixelsCopy2[row*image->width].w = avgAlpha/convolutionSize; + + for (int x = 1; x < image->width; x++) + { + if (x-blurSize >= 0) + { + avgR -= pixelsCopy1[row*image->width + x-blurSize].x; + avgG -= pixelsCopy1[row*image->width + x-blurSize].y; + avgB -= pixelsCopy1[row*image->width + x-blurSize].z; + avgAlpha -= pixelsCopy1[row*image->width + x-blurSize].w; + convolutionSize--; + } + + if (x+blurSize < image->width) + { + avgR += pixelsCopy1[row*image->width + x+blurSize].x; + avgG += pixelsCopy1[row*image->width + x+blurSize].y; + avgB += pixelsCopy1[row*image->width + x+blurSize].z; + avgAlpha += pixelsCopy1[row*image->width + x+blurSize].w; + convolutionSize++; + } + + pixelsCopy2[row*image->width + x].x = avgR/convolutionSize; + pixelsCopy2[row*image->width + x].y = avgG/convolutionSize; + pixelsCopy2[row*image->width + x].z = avgB/convolutionSize; + pixelsCopy2[row*image->width + x].w = avgAlpha/convolutionSize; + } + } + + // Vertical motion blur + for (int col = 0; col < image->width; col++) + { + float avgR = 0.0f; + float avgG = 0.0f; + float avgB = 0.0f; + float avgAlpha = 0.0f; + int convolutionSize = blurSize+1; + + for (int i = 0; i < blurSize+1; i++) + { + avgR += pixelsCopy2[i*image->width + col].x; + avgG += pixelsCopy2[i*image->width + col].y; + avgB += pixelsCopy2[i*image->width + col].z; + avgAlpha += pixelsCopy2[i*image->width + col].w; + } + + pixelsCopy1[col].x = (unsigned char) (avgR/convolutionSize); + pixelsCopy1[col].y = (unsigned char) (avgG/convolutionSize); + pixelsCopy1[col].z = (unsigned char) (avgB/convolutionSize); + pixelsCopy1[col].w = (unsigned char) (avgAlpha/convolutionSize); + + for (int y = 1; y < image->height; y++) + { + if (y-blurSize >= 0) + { + avgR -= pixelsCopy2[(y-blurSize)*image->width + col].x; + avgG -= pixelsCopy2[(y-blurSize)*image->width + col].y; + avgB -= pixelsCopy2[(y-blurSize)*image->width + col].z; + avgAlpha -= pixelsCopy2[(y-blurSize)*image->width + col].w; + convolutionSize--; + } + if (y+blurSize < image->height) + { + avgR += pixelsCopy2[(y+blurSize)*image->width + col].x; + avgG += pixelsCopy2[(y+blurSize)*image->width + col].y; + avgB += pixelsCopy2[(y+blurSize)*image->width + col].z; + avgAlpha += pixelsCopy2[(y+blurSize)*image->width + col].w; + convolutionSize++; + } + + pixelsCopy1[y*image->width + col].x = (unsigned char) (avgR/convolutionSize); + pixelsCopy1[y*image->width + col].y = (unsigned char) (avgG/convolutionSize); + pixelsCopy1[y*image->width + col].z = (unsigned char) (avgB/convolutionSize); + pixelsCopy1[y*image->width + col].w = (unsigned char) (avgAlpha/convolutionSize); + } + } + } + + + // Reverse premultiply + for (int i = 0; i < (image->width)*(image->height); i++) + { + if (pixelsCopy1[i].w == 0) + { + pixels[i].r = 0; + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 0; + } + else if (pixelsCopy1[i].w < 255.0f) + { + float alpha = (float)pixelsCopy1[i].w/255.0f; + pixels[i].r = (unsigned char)((float)pixelsCopy1[i].x/alpha); + pixels[i].g = (unsigned char)((float)pixelsCopy1[i].y/alpha); + pixels[i].b = (unsigned char)((float)pixelsCopy1[i].z/alpha); + pixels[i].a = (unsigned char) pixelsCopy1[i].w; + } + } + + int format = image->format; + RL_FREE(image->data); + RL_FREE(pixelsCopy1); + RL_FREE(pixelsCopy2); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + // Resize and image to new size // NOTE: Uses stb default scaling filters (both bicubic): // STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM