From 6c630b73a825befb6eeef66d7ed0063b1b891df7 Mon Sep 17 00:00:00 2001
From: Merov Linden <merov@lindenlab.com>
Date: Tue, 21 Jan 2014 13:05:54 -0800
Subject: [PATCH] ACME-1240 : Implement convolve filter for 3x3 kernels.
 Implements sharpen, blur and edge detection as examples and tests.

---
 .../llimage_libtest/blur.xml                  |   7 +
 .../llimage_libtest/edges.xml                 |  24 +++
 .../llimage_libtest/sharpen.xml               |   7 +
 indra/llimage/llimagefilter.cpp               | 164 ++++++++++++++++++
 indra/llimage/llimagefilter.h                 |   1 +
 5 files changed, 203 insertions(+)
 create mode 100644 indra/integration_tests/llimage_libtest/blur.xml
 create mode 100644 indra/integration_tests/llimage_libtest/edges.xml
 create mode 100644 indra/integration_tests/llimage_libtest/sharpen.xml

diff --git a/indra/integration_tests/llimage_libtest/blur.xml b/indra/integration_tests/llimage_libtest/blur.xml
new file mode 100644
index 00000000000..addd0568552
--- /dev/null
+++ b/indra/integration_tests/llimage_libtest/blur.xml
@@ -0,0 +1,7 @@
+<llsd>
+    <array>
+        <array>
+            <string>blur</string>
+        </array>
+    </array>
+</llsd>
diff --git a/indra/integration_tests/llimage_libtest/edges.xml b/indra/integration_tests/llimage_libtest/edges.xml
new file mode 100644
index 00000000000..a66b81d01ef
--- /dev/null
+++ b/indra/integration_tests/llimage_libtest/edges.xml
@@ -0,0 +1,24 @@
+<llsd>
+    <array>
+        <array>
+            <string>gradient</string>
+        </array>
+        <array>
+            <string>blur</string>
+        </array>
+        <array>
+            <string>linearize</string>
+            <real>0.0</real>
+            <real>1.0</real>
+            <real>1.0</real>
+            <real>1.0</real>
+        </array>
+        <array>
+            <string>contrast</string>
+            <real>2.0</real>
+            <real>1.0</real>
+            <real>1.0</real>
+            <real>1.0</real>
+        </array>
+    </array>
+</llsd>
diff --git a/indra/integration_tests/llimage_libtest/sharpen.xml b/indra/integration_tests/llimage_libtest/sharpen.xml
new file mode 100644
index 00000000000..6d3f9ae1a20
--- /dev/null
+++ b/indra/integration_tests/llimage_libtest/sharpen.xml
@@ -0,0 +1,7 @@
+<llsd>
+    <array>
+        <array>
+            <string>sharpen</string>
+        </array>
+    </array>
+</llsd>
diff --git a/indra/llimage/llimagefilter.cpp b/indra/llimage/llimagefilter.cpp
index 0f11b1037f3..75661a6d4bd 100755
--- a/indra/llimage/llimagefilter.cpp
+++ b/indra/llimage/llimagefilter.cpp
@@ -34,6 +34,7 @@
 #include "m3math.h"
 #include "v3math.h"
 #include "llsdserialize.h"
+#include "llstring.h"
 
 //---------------------------------------------------------------------------
 // LLImageFilter
@@ -281,6 +282,32 @@ void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image)
             }
             filterScreen(mode,(S32)(mFilterData[i][2].asReal()),(F32)(mFilterData[i][3].asReal()));
         }
+        else if (filter_name == "blur")
+        {
+            LLMatrix3 kernel;
+            for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++)
+                for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
+                    kernel.mMatrix[i][j] = 1.0;
+            convolve(kernel,true,false);
+        }
+        else if (filter_name == "sharpen")
+        {
+            LLMatrix3 kernel;
+            for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++)
+                for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
+                    kernel.mMatrix[i][j] = -1.0;
+            kernel.mMatrix[1][1] = 9.0;
+            convolve(kernel,false,false);
+        }
+        else if (filter_name == "gradient")
+        {
+            LLMatrix3 kernel;
+            for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++)
+                for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
+                    kernel.mMatrix[i][j] = -1.0;
+            kernel.mMatrix[1][1] = 8.0;
+            convolve(kernel,false,true);
+        }
     }
 }
 
@@ -365,6 +392,143 @@ void LLImageFilter::colorTransform(const LLMatrix3 &transform)
 	}
 }
 
+void LLImageFilter::convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value)
+{
+	const S32 components = mImage->getComponents();
+	llassert( components >= 1 && components <= 4 );
+    
+    // Compute normalization factors
+    F32 kernel_min = 0.0;
+    F32 kernel_max = 0.0;
+    for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++)
+    {
+        for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
+        {
+            if (kernel.mMatrix[i][j] >= 0.0)
+                kernel_max += kernel.mMatrix[i][j];
+            else
+                kernel_min += kernel.mMatrix[i][j];
+        }
+    }
+    if (abs_value)
+    {
+        kernel_max = llabs(kernel_max);
+        kernel_min = llabs(kernel_min);
+        kernel_max = llmax(kernel_max,kernel_min);
+        kernel_min = 0.0;
+    }
+    F32 kernel_range = kernel_max - kernel_min;
+    
+    // Allocate temporary buffers and initialize algorithm's data
+	S32 width  = mImage->getWidth();
+    S32 height = mImage->getHeight();
+    
+	U8* dst_data = mImage->getData();
+
+	S32 buffer_size = width * components;
+	llassert_always(buffer_size > 0);
+	std::vector<U8> even_buffer(buffer_size);
+	std::vector<U8> odd_buffer(buffer_size);
+	
+    U8* south_data = dst_data + buffer_size;
+    U8* east_west_data;
+    U8* north_data;
+    
+    // Line 0 : we set the line to 0 (debatable)
+    memcpy( &even_buffer[0], dst_data, buffer_size );	/* Flawfinder: ignore */
+    for (S32 i = 0; i < width; i++)
+    {
+        blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0);
+        dst_data += components;
+    }
+    south_data += buffer_size;
+    
+    // All other lines
+    for (S32 j = 1; j < (height-1); j++)
+	{
+        // We need to buffer 2 lines. We flip north and current to avoid moving too much memory around
+        if (j % 2)
+        {
+            memcpy( &odd_buffer[0], dst_data, buffer_size );	/* Flawfinder: ignore */
+            east_west_data = &odd_buffer[0];
+            north_data = &even_buffer[0];
+        }
+        else
+        {
+            memcpy( &even_buffer[0], dst_data, buffer_size );	/* Flawfinder: ignore */
+            east_west_data = &even_buffer[0];
+            north_data = &odd_buffer[0];
+        }
+        // First pixel : set to 0
+        blendStencil(getStencilAlpha(0,j), dst_data, 0, 0, 0);
+        // Set pointers to kernel
+        U8* NW = north_data;
+        U8* N = NW+components;
+        U8* NE = N+components;
+        U8* W = east_west_data;
+        U8* C = W+components;
+        U8* E = C+components;
+        U8* SW = south_data;
+        U8* S = SW+components;
+        U8* SE = S+components;
+        dst_data += components;
+        // All other pixels
+        for (S32 i = 1; i < (width-1); i++)
+        {
+            // Compute convolution
+            LLVector3 dst;
+            dst.mV[VRED] = (kernel.mMatrix[0][0]*NW[VRED] + kernel.mMatrix[0][1]*N[VRED] + kernel.mMatrix[0][2]*NE[VRED] +
+                            kernel.mMatrix[1][0]*W[VRED]  + kernel.mMatrix[1][1]*C[VRED] + kernel.mMatrix[1][2]*E[VRED] +
+                            kernel.mMatrix[2][0]*SW[VRED] + kernel.mMatrix[2][1]*S[VRED] + kernel.mMatrix[2][2]*SE[VRED]);
+            dst.mV[VGREEN] = (kernel.mMatrix[0][0]*NW[VGREEN] + kernel.mMatrix[0][1]*N[VGREEN] + kernel.mMatrix[0][2]*NE[VGREEN] +
+                              kernel.mMatrix[1][0]*W[VGREEN]  + kernel.mMatrix[1][1]*C[VGREEN] + kernel.mMatrix[1][2]*E[VGREEN] +
+                              kernel.mMatrix[2][0]*SW[VGREEN] + kernel.mMatrix[2][1]*S[VGREEN] + kernel.mMatrix[2][2]*SE[VGREEN]);
+            dst.mV[VBLUE] = (kernel.mMatrix[0][0]*NW[VBLUE] + kernel.mMatrix[0][1]*N[VBLUE] + kernel.mMatrix[0][2]*NE[VBLUE] +
+                             kernel.mMatrix[1][0]*W[VBLUE]  + kernel.mMatrix[1][1]*C[VBLUE] + kernel.mMatrix[1][2]*E[VBLUE] +
+                             kernel.mMatrix[2][0]*SW[VBLUE] + kernel.mMatrix[2][1]*S[VBLUE] + kernel.mMatrix[2][2]*SE[VBLUE]);
+            if (abs_value)
+            {
+                dst.mV[VRED]   = llabs(dst.mV[VRED]);
+                dst.mV[VGREEN] = llabs(dst.mV[VGREEN]);
+                dst.mV[VBLUE]  = llabs(dst.mV[VBLUE]);
+            }
+            if (normalize)
+            {
+                dst.mV[VRED]   = (dst.mV[VRED] - kernel_min)/kernel_range;
+                dst.mV[VGREEN] = (dst.mV[VGREEN] - kernel_min)/kernel_range;
+                dst.mV[VBLUE]  = (dst.mV[VBLUE] - kernel_min)/kernel_range;
+            }
+            dst.clamp(0.0f,255.0f);
+            
+            // Blend result
+            blendStencil(getStencilAlpha(i,j), dst_data, dst.mV[VRED], dst.mV[VGREEN], dst.mV[VBLUE]);
+            
+            // Next pixel
+            dst_data += components;
+            NW += components;
+            N += components;
+            NE += components;
+            W += components;
+            C += components;
+            E += components;
+            SW += components;
+            S += components;
+            SE += components;
+        }
+        // Last pixel : set to 0
+        blendStencil(getStencilAlpha(width-1,j), dst_data, 0, 0, 0);
+        dst_data += components;
+        south_data += buffer_size;
+	}
+    
+    // Last line
+    for (S32 i = 0; i < width; i++)
+    {
+        blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0);
+        dst_data += components;
+    }
+}
+
 void LLImageFilter::filterScreen(EScreenMode mode, const S32 wave_length, const F32 angle)
 {
 	const S32 components = mImage->getComponents();
diff --git a/indra/llimage/llimagefilter.h b/indra/llimage/llimagefilter.h
index 19ac7e81d6f..738c6936861 100755
--- a/indra/llimage/llimagefilter.h
+++ b/indra/llimage/llimagefilter.h
@@ -91,6 +91,7 @@ class LLImageFilter
     void colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue);
     void filterScreen(EScreenMode mode, const S32 wave_length, const F32 angle);
     void blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue);
+    void convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value);
 
     // Procedural Stencils
     void setStencil(EStencilBlendMode mode, EStencilShape type, F32 gamma, F32 min, F32 max);
-- 
GitLab