summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornobytesgiven <[email protected]>2022-10-25 18:56:06 +0300
committerGitHub <[email protected]>2022-10-25 17:56:06 +0200
commitdbecb950242e51f5a55da916590b0d187515189c (patch)
tree3342ab6ad6d6e2c26105eeeebb6e94956d107628
parent072e92615aad45777e667e7b29137095b815734a (diff)
downloadraylib-dbecb950242e51f5a55da916590b0d187515189c.tar.gz
raylib-dbecb950242e51f5a55da916590b0d187515189c.zip
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 <[email protected]>
-rw-r--r--src/raylib.h1
-rw-r--r--src/rtextures.c157
2 files changed, 158 insertions, 0 deletions
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