From d28b92744ee0d4a19a5587585998e5c351f6d300 Mon Sep 17 00:00:00 2001
From: Merov Linden <merov@lindenlab.com>
Date: Thu, 2 Jan 2014 16:14:38 -0800
Subject: [PATCH] ACME-1236 : WIP : added all the color correction filters:
 colorize, linarize, equalize, contrast, brightness

---
 .../llimage_libtest/CMakeLists.txt            |   3 +
 .../llimage_libtest/llimage_libtest.cpp       |  44 ++++-
 indra/llimage/llimage.cpp                     | 155 ++++++++++++++++++
 indra/llimage/llimage.h                       |  16 +-
 4 files changed, 209 insertions(+), 9 deletions(-)

diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt
index 36a7d38bb71..8a83ac498fe 100755
--- a/indra/integration_tests/llimage_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt
@@ -7,6 +7,7 @@ project (llimage_libtest)
 include(00-Common)
 include(LLCommon)
 include(LLImage)
+include(LLMath)
 include(LLImageJ2COJ) 
 include(LLKDU)
 include(LLVFS)
@@ -15,6 +16,7 @@ include_directories(
     ${LLCOMMON_INCLUDE_DIRS}
     ${LLVFS_INCLUDE_DIRS}
     ${LLIMAGE_INCLUDE_DIRS}
+    ${LLMATH_INCLUDE_DIRS}
     )
 include_directories(SYSTEM
     ${LLCOMMON_SYSTEM_INCLUDE_DIRS}
@@ -64,6 +66,7 @@ endif (DARWIN)
 target_link_libraries(llimage_libtest
     ${LLCOMMON_LIBRARIES}
     ${LLVFS_LIBRARIES}
+    ${LLMATH_LIBRARIES}
     ${LLIMAGE_LIBRARIES}
     ${LLKDU_LIBRARIES}
     ${KDU_LIBRARY}
diff --git a/indra/integration_tests/llimage_libtest/llimage_libtest.cpp b/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
index afd5e2ce98e..4d32282a0d9 100755
--- a/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
+++ b/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
@@ -39,6 +39,7 @@
 #include "llimagej2c.h"
 #include "lldir.h"
 #include "lldiriterator.h"
+#include "v4coloru.h"
 
 // system libraries
 #include <iostream>
@@ -84,10 +85,18 @@ static const char USAGE[] = "\n"
 "        Set the compression to be lossless (reversible in j2c parlance).\n"
 "        Only valid for output j2c images.\n"
 " -f, --filter <name> [<param>]\n"
-"        Apply the filter <name> to the input images using the optional param (float) value:\n"
-"        - 'grayscale' and 'sepia' just do that (no param).\n"
-"        - 'saturate' changes color saturation according to param: param < 1.0 will desaturate, param > 1.0 will saturate.\n"
-"        - 'rotate' rotates the color hue according to param (in degree, positive value only).\n"
+"        Apply the filter <name> to the input images using the optional <param> value. Admissible names:\n"
+"        - 'grayscale' converts to grayscale (no param).\n"
+"        - 'sepia' converts to sepia (no param).\n"
+"        - 'saturate' changes color saturation according to <param>: < 1.0 will desaturate, > 1.0 will saturate.\n"
+"        - 'rotate' rotates the color hue according to <param> (in degree, positive value only).\n"
+"        - 'gamma' applies gamma curve <param> to all channels: > 1.0 will darken, < 1.0 will lighten.\n"
+"        - 'colorize' applies a red tint to the image using <param> as an alpha (transparency between 0.0 and 1.0) value.\n"
+"        - 'contrast' modifies the contrast according to <param> : > 1.0 will enhance the contrast, <1.0 will flatten it.\n"
+"        - 'brighten' adds <param> light to the image (<param> between 0 and 255).\n"
+"        - 'darken' substracts <param> light to the image (<param> between 0 and 255).\n"
+"        - 'linearize' optimizes the contrast using the brightness histogram. <param> is the fraction (between 0.0 and 1.0) of discarded tail of the histogram.\n"
+"        - 'posterize' redistributes the colors between <param> classes per channel (<param> between 2 and 255).\n"
 " -log, --logmetrics <metric>\n"
 "        Log performance data for <metric>. Results in <metric>.slp\n"
 "        Note: so far, only ImageCompressionTester has been tested.\n"
@@ -626,6 +635,33 @@ int main(int argc, char** argv)
         {
             raw_image->filterGamma((float)(filter_param));
         }
+        else if (filter_name == "colorize")
+        {
+            // For testing, we just colorize in red, modulate by the alpha passed as a parameter
+            LLColor4U color = LLColor4U::red;
+            color.setAlpha((U8)(filter_param * 255.0));
+            raw_image->filterColorize(color);
+        }
+        else if (filter_name == "contrast")
+        {
+            raw_image->filterContrast((float)(filter_param));
+        }
+        else if (filter_name == "brighten")
+        {
+            raw_image->filterBrightness((S32)(filter_param));
+        }
+        else if (filter_name == "darken")
+        {
+            raw_image->filterBrightness((S32)(-filter_param));
+        }
+        else if (filter_name == "linearize")
+        {
+            raw_image->filterLinearize((float)(filter_param));
+        }
+        else if (filter_name == "posterize")
+        {
+            raw_image->filterEqualize((S32)(filter_param));
+        }
 	
 		// Save file
 		if (out_file != out_end)
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index d406995f3a2..5dc9c24f6ce 100755
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -1042,6 +1042,152 @@ void LLImageRaw::filterGamma(F32 gamma)
     colorCorrect(gamma_lut,gamma_lut,gamma_lut);
 }
 
+void LLImageRaw::filterLinearize(F32 tail)
+{
+    // Get the histogram
+    U32* histo = getBrightnessHistogram();
+    
+    // Compute cumulated histogram
+    U32 cumulated_histo[256];
+    cumulated_histo[0] = histo[0];
+    for (S32 i = 1; i < 256; i++)
+    {
+        cumulated_histo[i] = cumulated_histo[i-1] + histo[i];
+    }
+    
+    // Compute min and max counts minus tail
+    tail = llclampf(tail);
+    S32 total = cumulated_histo[255];
+    S32 min_c = (S32)((F32)(total) * tail);
+    S32 max_c = (S32)((F32)(total) * (1.0 - tail));
+    
+    // Find min and max values
+    S32 min_v = 0;
+    while (cumulated_histo[min_v] < min_c)
+    {
+        min_v++;
+    }
+    S32 max_v = 255;
+    while (cumulated_histo[max_v] > max_c)
+    {
+        max_v--;
+    }
+    
+    // Compute linear lookup table
+    U8 linear_lut[256];
+    if (max_v == min_v)
+    {
+        // Degenerated binary split case
+        for (S32 i = 0; i < 256; i++)
+        {
+            linear_lut[i] = (i < min_v ? 0 : 255);
+        }
+    }
+    else
+    {
+        // Linearize between min and max
+        F32 slope = 255.0 / (F32)(max_v - min_v);
+        F32 translate = -min_v * slope;
+        for (S32 i = 0; i < 256; i++)
+        {
+            linear_lut[i] = (U8)(llclampb((S32)(slope*i + translate)));
+        }
+    }
+    
+    // Apply lookup table
+    colorCorrect(linear_lut,linear_lut,linear_lut);    
+}
+
+void LLImageRaw::filterEqualize(S32 nb_classes)
+{
+    // Regularize the parameter: must be between 2 and 255
+    nb_classes = llmax(nb_classes,2);
+    nb_classes = llclampb(nb_classes);
+    
+    // Get the histogram
+    U32* histo = getBrightnessHistogram();
+    
+    // Compute cumulated histogram
+    U32 cumulated_histo[256];
+    cumulated_histo[0] = histo[0];
+    for (S32 i = 1; i < 256; i++)
+    {
+        cumulated_histo[i] = cumulated_histo[i-1] + histo[i];
+    }
+    
+    // Compute deltas
+    S32 total = cumulated_histo[255];
+    S32 delta_count = total / nb_classes;
+    S32 current_count = delta_count;
+    S32 delta_value = 256 / (nb_classes - 1);
+    S32 current_value = 0;
+    
+    // Compute equalized lookup table
+    U8 equalize_lut[256];
+    for (S32 i = 0; i < 256; i++)
+    {
+        equalize_lut[i] = (U8)(current_value);
+        if (cumulated_histo[i] >= current_count)
+        {
+            current_count += delta_count;
+            current_value += delta_value;
+            current_value = llclampb(current_value);
+        }
+    }
+
+    // Apply lookup table
+    colorCorrect(equalize_lut,equalize_lut,equalize_lut);
+}
+
+void LLImageRaw::filterColorize(const LLColor4U& color)
+{
+    U8 red_lut[256];
+    U8 green_lut[256];
+    U8 blue_lut[256];
+    
+    F32 alpha = (F32)(color.mV[3])/255.0;
+    F32 inv_alpha = 1.0 - alpha;
+    
+    F32 red_composite   =  alpha * (F32)(color.mV[0]);
+    F32 green_composite =  alpha * (F32)(color.mV[1]);
+    F32 blue_composite  =  alpha * (F32)(color.mV[2]);
+    
+    for (S32 i = 0; i < 256; i++)
+    {
+        red_lut[i]   = (U8)(llclampb((S32)(inv_alpha*(F32)(i) + red_composite)));
+        green_lut[i] = (U8)(llclampb((S32)(inv_alpha*(F32)(i) + green_composite)));
+        blue_lut[i]  = (U8)(llclampb((S32)(inv_alpha*(F32)(i) + blue_composite)));
+    }
+    
+    colorCorrect(red_lut,green_lut,blue_lut);
+}
+
+void LLImageRaw::filterContrast(F32 slope)
+{
+    U8 contrast_lut[256];
+    
+    F32 translate = 128.0 * (1.0 - slope);
+    
+    for (S32 i = 0; i < 256; i++)
+    {
+        contrast_lut[i] = (U8)(llclampb((S32)(slope*i + translate)));
+    }
+    
+    colorCorrect(contrast_lut,contrast_lut,contrast_lut);
+}
+
+void LLImageRaw::filterBrightness(S32 add)
+{
+    U8 brightness_lut[256];
+    
+    for (S32 i = 0; i < 256; i++)
+    {
+        brightness_lut[i] = (U8)(llclampb((S32)((S32)(i) + add)));
+    }
+    
+    colorCorrect(brightness_lut,brightness_lut,brightness_lut);
+}
+
 // Filter Primitives
 void LLImageRaw::colorTransform(const LLMatrix3 &transform)
 {
@@ -1078,6 +1224,15 @@ void LLImageRaw::colorCorrect(const U8* lut_red, const U8* lut_green, const U8*
 	}
 }
 
+U32* LLImageRaw::getBrightnessHistogram()
+{
+    if (!mHistoBrightness)
+    {
+        computeHistograms();
+    }
+    return mHistoBrightness;
+}
+
 void LLImageRaw::computeHistograms()
 {
  	const S32 components = getComponents();
diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h
index 89734693ccf..b62deadee02 100755
--- a/indra/llimage/llimage.h
+++ b/indra/llimage/llimage.h
@@ -264,15 +264,21 @@ class LLImageRaw : public LLImageBase
 	void compositeUnscaled4onto3( LLImageRaw* src );
     
     // Filter Operations
-    void filterGrayScale();
-    void filterSepia();
-    void filterSaturate(F32 saturation);    // < 1.0 desaturates, > 1.0 saturates
-    void filterRotate(F32 alpha);           // rotates hue, alpha in degrees
-    void filterGamma(F32 gamma);            // Apply a gamma lookup to all channels
+    void filterGrayScale();                         // Convert to grayscale
+    void filterSepia();                             // Convert to sepia
+    void filterSaturate(F32 saturation);            // < 1.0 desaturates, > 1.0 saturates
+    void filterRotate(F32 alpha);                   // Rotates hue according to alpha, alpha in degrees
+    void filterGamma(F32 gamma);                    // Apply gamma to all channels
+    void filterLinearize(F32 tail);                 // Use histogram to linearize constrast between min and max values minus tail
+    void filterEqualize(S32 nb_classes);            // Use histogram to equalize constrast throughout the image
+    void filterColorize(const LLColor4U& color);    // Colorize with color. Alpha will be taken into account for colorization intensity.
+    void filterContrast(F32 slope);                 // Change contrast according to slope: > 1.0 more contrast, < 1.0 less contrast
+    void filterBrightness(S32 add);                 // Change brightness according to add: > 0 brighter, < 0 darker
     
     // Filter Primitives
     void colorTransform(const LLMatrix3 &transform);
     void colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue);
+    U32* getBrightnessHistogram();
 
 protected:
 	// Create an image from a local file (generally used in tools)
-- 
GitLab