Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image convolution function ImageKernelConvolution #3528

Merged
merged 5 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ TEXTURES = \
textures/textures_gif_player \
textures/textures_image_drawing \
textures/textures_image_generation \
textures/textures_image_kernel \
textures/textures_image_loading \
textures/textures_image_processing \
textures/textures_image_rotate \
Expand Down
127 changes: 127 additions & 0 deletions examples/textures/textures_image_kernel.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*******************************************************************************************
*
* raylib [textures] example - Image loading and texture creation
*
* NOTE: Images are loaded in CPU memory (RAM); textures are loaded in GPU memory (VRAM)
*
* Example originally created with raylib 1.3, last time updated with raylib 1.3
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2015-2023 Karim Salem (@kimo-s)
*
********************************************************************************************/

#include "raylib.h"

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
void normalizeKernel(float *kernel, int size){
float sum = 0.0f;
for(int i = 0; i < size; i++)
{
sum += kernel[i];
}

if(sum != 0.0f)
{
for(int i = 0; i < size; i++)
{
kernel[i] /= sum;
}
}
}

int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------

Image image = LoadImage("resources/cat.png"); // Loaded in CPU memory (RAM)

const int screenWidth = 800;
const int screenHeight = 450;

InitWindow(screenWidth, screenHeight, "raylib [textures] example - image convolution");

float gaussiankernel[] = {1.0, 2.0, 1.0,
2.0, 4.0, 2.0,
1.0, 2.0, 1.0};

float sobelkernel[] = {1.0, 0.0, -1.0,
2.0, 0.0, -2.0,
1.0, 0.0, -1.0};

float sharpenkernel[] = {0.0, -1.0, 0.0,
-1.0, 5.0, -1.0,
0.0, -1.0, 0.0};

normalizeKernel(gaussiankernel, 9);
normalizeKernel(sharpenkernel, 9);
normalizeKernel(sobelkernel, 9);

Image catSharpend = ImageCopy(image);
ImageKernelConvolution(&catSharpend, sharpenkernel, 9);

Image catSobel = ImageCopy(image);
ImageKernelConvolution(&catSobel, sobelkernel, 9);

Image catGaussian = ImageCopy(image);
for(int i = 0; i < 6; i++)
{
ImageKernelConvolution(&catGaussian, gaussiankernel, 9);
}

ImageCrop(&image, (Rectangle){ 0, 0, (float)200, (float)450 });
ImageCrop(&catGaussian, (Rectangle){ 0, 0, (float)200, (float)450 });
ImageCrop(&catSobel, (Rectangle){ 0, 0, (float)200, (float)450 });
ImageCrop(&catSharpend, (Rectangle){ 0, 0, (float)200, (float)450 });
Texture2D texture = LoadTextureFromImage(image); // Image converted to texture, GPU memory (VRAM)
Texture2D catSharpendTexture = LoadTextureFromImage(catSharpend);
Texture2D catSobelTexture = LoadTextureFromImage(catSobel);
Texture2D catGaussianTexture = LoadTextureFromImage(catGaussian);
UnloadImage(image); // Once image has been converted to texture and uploaded to VRAM, it can be unloaded from RAM
UnloadImage(catGaussian);
UnloadImage(catSobel);
UnloadImage(catSharpend);

SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//---------------------------------------------------------------------------------------

// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
// TODO: Update your variables here
//----------------------------------------------------------------------------------

// Draw
//----------------------------------------------------------------------------------
BeginDrawing();

ClearBackground(RAYWHITE);

DrawTexture(catSharpendTexture, 0, 0, WHITE);
DrawTexture(catSobelTexture, 200, 0, WHITE);
DrawTexture(catGaussianTexture, 400, 0, WHITE);
DrawTexture(texture, 600, 0, WHITE);

EndDrawing();
//----------------------------------------------------------------------------------
}

// De-Initialization
//--------------------------------------------------------------------------------------
UnloadTexture(texture); // Texture unloading
UnloadTexture(catGaussianTexture);
UnloadTexture(catSobelTexture);
UnloadTexture(catSharpendTexture);

CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------

return 0;
}
1 change: 1 addition & 0 deletions projects/Notepad++/raylib_npp_parser/raylib_to_parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ RLAPI void ImageAlphaClear(Image *image, Color color, float threshold);
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 ImageKernelConvolution(Image *image, float* kernel, int kernelSize); // Apply Custom Square image convolution kernel
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
Expand Down
1 change: 1 addition & 0 deletions src/raylib.h
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,7 @@ RLAPI void ImageAlphaClear(Image *image, Color color, float threshold);
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 ImageKernelConvolution(Image *image, float* kernel, int kernelSize); // Apply Custom Square image convolution kernel
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
Expand Down
142 changes: 142 additions & 0 deletions src/rtextures.c
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,148 @@ void ImageBlurGaussian(Image *image, int blurSize) {
ImageFormat(image, format);
}

// The kernel matrix is assumed to be square. Only supply the width of the kernel.
void ImageKernelConvolution(Image *image, float* kernel, int kernelSize){

if ((image->data == NULL) || (image->width == 0) || (image->height == 0) || kernel == NULL) return;

int kernelWidth = (int)sqrtf((float)kernelSize);
if (kernelWidth*kernelWidth != kernelSize)
{
TRACELOG(LOG_WARNING, "IMAGE: Convolution kernel must be square to be applied");
return;
}

Color *pixels = LoadImageColors(*image);

Vector4 *imageCopy2 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4));
Vector4 *temp = RL_MALLOC(kernelSize*sizeof(Vector4));


for(int i = 0; i < kernelSize; i++){
temp[i].x = 0.0f;
temp[i].y = 0.0f;
temp[i].z = 0.0f;
temp[i].w = 0.0f;
}

float rRes = 0.0f;
float gRes = 0.0f;
float bRes = 0.0f;
float aRes = 0.0f;


int startRange, endRange;
if(kernelWidth % 2 == 0)
{
startRange = -kernelWidth/2;
endRange = kernelWidth/2;
} else
{
startRange = -kernelWidth/2;
endRange = kernelWidth/2+1;
}
for(int x = 0; x < image->height; x++)
{
for(int y = 0; y < image->width; y++)
{

for(int xk = startRange; xk < endRange; xk++)
{
for(int yk = startRange; yk < endRange; yk++)
{
int xkabs = xk + kernelWidth/2;
int ykabs = yk + kernelWidth/2;
size_t imgindex = image->width * (x+xk) + (y+yk);
if(imgindex < 0 || imgindex >= image->width * image->height){
temp[kernelWidth * xkabs + ykabs].x = 0.0f;
temp[kernelWidth * xkabs + ykabs].y = 0.0f;
temp[kernelWidth * xkabs + ykabs].z = 0.0f;
temp[kernelWidth * xkabs + ykabs].w = 0.0f;
} else {
temp[kernelWidth * xkabs + ykabs].x = ((float)pixels[imgindex].r)/255.0f * kernel[kernelWidth * xkabs + ykabs];
temp[kernelWidth * xkabs + ykabs].y = ((float)pixels[imgindex].g)/255.0f * kernel[kernelWidth * xkabs + ykabs];
temp[kernelWidth * xkabs + ykabs].z = ((float)pixels[imgindex].b)/255.0f * kernel[kernelWidth * xkabs + ykabs];
temp[kernelWidth * xkabs + ykabs].w = ((float)pixels[imgindex].a)/255.0f * kernel[kernelWidth * xkabs + ykabs];
}
}
}

for(int i = 0; i < kernelSize; i++)
{
rRes += temp[i].x;
gRes += temp[i].y;
bRes += temp[i].z;
aRes += temp[i].w;
}

if(rRes < 0.0f)
{
rRes = 0.0f;
}
if(gRes < 0.0f)
{
gRes = 0.0f;
}
if(bRes < 0.0f)
{
bRes = 0.0f;
}

if(rRes > 1.0f)
{
rRes = 1.0f;
}
if(gRes > 1.0f)
{
gRes = 1.0f;
}
if(bRes > 1.0f)
{
bRes = 1.0f;
}

imageCopy2[image->width * (x) + (y)].x = rRes;
imageCopy2[image->width * (x) + (y)].y = gRes;
imageCopy2[image->width * (x) + (y)].z = bRes;
imageCopy2[image->width * (x) + (y)].w = aRes;

rRes = 0.0f;
gRes = 0.0f;
bRes = 0.0f;
aRes = 0.0f;

for(int i = 0; i < kernelSize; i++)
{
temp[i].x = 0.0f;
temp[i].y = 0.0f;
temp[i].z = 0.0f;
temp[i].w = 0.0f;
}
}
}

for (int i = 0; i < (image->width) * (image->height); i++)
{
float alpha = (float)imageCopy2[i].w;
pixels[i].r = (unsigned char)((imageCopy2[i].x)*255.0f);
pixels[i].g = (unsigned char)((imageCopy2[i].y)*255.0f);
pixels[i].b = (unsigned char)((imageCopy2[i].z)*255.0f);
pixels[i].a = (unsigned char)((alpha)*255.0f);
// printf("pixels[%d] = %d", i, pixels[i].r);
}


int format = image->format;
RL_FREE(image->data);
RL_FREE(imageCopy2);
RL_FREE(temp);

image->data = pixels;
image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
ImageFormat(image, format);
raysan5 marked this conversation as resolved.
Show resolved Hide resolved
}

// Generate all mipmap levels for a provided image
// NOTE 1: Supports POT and NPOT images
// NOTE 2: image.data is scaled to include mipmap levels
Expand Down