/**
* @file llimagefilter.cpp
* @brief Simple Image Filtering.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llimagefilter.h"
#include "llmath.h"
#include "v3color.h"
#include "v4coloru.h"
#include "m3math.h"
#include "v3math.h"
#include "llsdserialize.h"
//---------------------------------------------------------------------------
// LLImageFilter
//---------------------------------------------------------------------------
LLImageFilter::LLImageFilter() :
mFilterData(LLSD::emptyArray()),
mImage(NULL),
mHistoRed(NULL),
mHistoGreen(NULL),
mHistoBlue(NULL),
mHistoBrightness(NULL),
mVignetteMode(VIGNETTE_MODE_NONE),
mVignetteGamma(1.0),
mVignetteMin(0.0)
{
}
LLImageFilter::~LLImageFilter()
{
mImage = NULL;
ll_aligned_free_16(mHistoRed);
ll_aligned_free_16(mHistoGreen);
ll_aligned_free_16(mHistoBlue);
ll_aligned_free_16(mHistoBrightness);
}
/*
*TODO
* Rename vignette to stencil
* Separate shape from mode
* Add shapes : uniform and gradients
* Add modes
* Add stencil (min,max) range
* Suppress alpha from colorcorrect and use uniform alpha instead
* Refactor stencil composition in the filter primitives
stencil
shape
blend_mode
min
max
param1
param2
param3
param4
vignette : center_x, center_y, width, feather
sine : wavelength, angle
flat
gradient : start_x, start_y, end_x, end_y
* Document all the admissible names in the wiki
" Apply the filter to the input images using the optional value. Admissible names:\n"
" - 'grayscale' converts to grayscale (no param).\n"
" - 'sepia' converts to sepia (no param).\n"
" - 'saturate' changes color saturation according to : < 1.0 will desaturate, > 1.0 will saturate.\n"
" - 'rotate' rotates the color hue according to (in degree, positive value only).\n"
" - 'gamma' applies gamma curve to all channels: > 1.0 will darken, < 1.0 will lighten.\n"
" - 'colorize' applies a red tint to the image using as an alpha (transparency between 0.0 and 1.0) value.\n"
" - 'contrast' modifies the contrast according to : > 1.0 will enhance the contrast, <1.0 will flatten it.\n"
" - 'brighten' adds light to the image ( between 0 and 255).\n"
" - 'darken' substracts light to the image ( between 0 and 255).\n"
" - 'linearize' optimizes the contrast using the brightness histogram. is the fraction (between 0.0 and 1.0) of discarded tail of the histogram.\n"
" - 'posterize' redistributes the colors between classes per channel ( between 2 and 255).\n"
" - 'newsscreen' applies a 2D sine screening to the red channel and output to black and white.\n"
" - 'horizontalscreen' applies a horizontal screening to the red channel and output to black and white.\n"
" - 'verticalscreen' applies a vertical screening to the red channel and output to black and white.\n"
" - 'slantedscreen' applies a 45 degrees slanted screening to the red channel and output to black and white.\n"
" - Any other value will be interpreted as a file name describing a sequence of filters and parameters to be applied to the input images.\n"
" Apply a circular central vignette to the filter using the optional and values. Admissible names:\n"
" - 'blend' : the filter is applied with full intensity in the center and blends with the image to the periphery.\n"
" - 'fade' : the filter is applied with full intensity in the center and fades to black to the periphery.\n"
*/
//============================================================================
// Load filter from file
//============================================================================
void LLImageFilter::loadFromFile(const std::string& file_path)
{
//std::cout << "Loading filter settings from : " << file_path << std::endl;
llifstream filter_xml(file_path);
if (filter_xml.is_open())
{
// Load and parse the file
LLPointer parser = new LLSDXMLParser();
parser->parse(filter_xml, mFilterData, LLSDSerialize::SIZE_UNLIMITED);
filter_xml.close();
}
else
{
// File couldn't be open, reset the filter data
mFilterData = LLSD();
}
}
//============================================================================
// Apply the filter data to the image passed as parameter
//============================================================================
void LLImageFilter::executeFilter(LLPointer raw_image)
{
mImage = raw_image;
//std::cout << "Filter : size = " << mFilterData.size() << std::endl;
for (S32 i = 0; i < mFilterData.size(); ++i)
{
std::string filter_name = mFilterData[i][0].asString();
// Dump out the filter values (for debug)
//std::cout << "Filter : name = " << mFilterData[i][0].asString() << ", params = ";
//for (S32 j = 1; j < mFilterData[i].size(); ++j)
//{
// std::cout << mFilterData[i][j].asString() << ", ";
//}
//std::cout << std::endl;
// Execute the filter described on this line
if (filter_name == "blend")
{
setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_CENTER,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
}
else if (filter_name == "fade")
{
setVignette(VIGNETTE_MODE_FADE,VIGNETTE_TYPE_CENTER,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
}
else if (filter_name == "lines")
{
setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_LINES,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
}
else if (filter_name == "sepia")
{
filterSepia();
}
else if (filter_name == "grayscale")
{
filterGrayScale();
}
else if (filter_name == "saturate")
{
filterSaturate((float)(mFilterData[i][1].asReal()));
}
else if (filter_name == "rotate")
{
filterRotate((float)(mFilterData[i][1].asReal()));
}
else if (filter_name == "gamma")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterGamma((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "colorize")
{
LLColor3 color((float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()));
LLColor3 alpha((F32)(mFilterData[i][4].asReal()),(float)(mFilterData[i][5].asReal()),(float)(mFilterData[i][6].asReal()));
filterColorize(color,alpha);
}
else if (filter_name == "contrast")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterContrast((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "brighten")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterBrightness((S32)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "darken")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterBrightness((S32)(-mFilterData[i][1].asReal()),color);
}
else if (filter_name == "linearize")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterLinearize((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "posterize")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterEqualize((S32)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "screen")
{
std::string screen_name = mFilterData[i][1].asString();
EScreenMode mode = SCREEN_MODE_2DSINE;
if (screen_name == "2Dsine")
{
mode = SCREEN_MODE_2DSINE;
}
else if (screen_name == "line")
{
mode = SCREEN_MODE_LINE;
}
filterScreen(mode,(S32)(mFilterData[i][2].asReal()),(F32)(mFilterData[i][3].asReal()));
}
}
}
//============================================================================
// Filter Primitives
//============================================================================
void LLImageFilter::colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
U8* dst_data = mImage->getData();
for (S32 j = 0; j < height; j++)
{
for (S32 i = 0; i < width; i++)
{
if (mVignetteMode == VIGNETTE_MODE_NONE)
{
dst_data[VRED] = lut_red[dst_data[VRED]];
dst_data[VGREEN] = lut_green[dst_data[VGREEN]];
dst_data[VBLUE] = lut_blue[dst_data[VBLUE]];
}
else
{
F32 alpha = getVignetteAlpha(i,j);
if (mVignetteMode == VIGNETTE_MODE_BLEND)
{
// Blends with the source image on the edges
F32 inv_alpha = 1.0 - alpha;
dst_data[VRED] = inv_alpha * dst_data[VRED] + alpha * lut_red[dst_data[VRED]];
dst_data[VGREEN] = inv_alpha * dst_data[VGREEN] + alpha * lut_green[dst_data[VGREEN]];
dst_data[VBLUE] = inv_alpha * dst_data[VBLUE] + alpha * lut_blue[dst_data[VBLUE]];
}
else // VIGNETTE_MODE_FADE
{
// Fade to black on the edges
dst_data[VRED] = alpha * lut_red[dst_data[VRED]];
dst_data[VGREEN] = alpha * lut_green[dst_data[VGREEN]];
dst_data[VBLUE] = alpha * lut_blue[dst_data[VBLUE]];
}
}
dst_data += components;
}
}
}
void LLImageFilter::colorTransform(const LLMatrix3 &transform)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
U8* dst_data = mImage->getData();
for (S32 j = 0; j < height; j++)
{
for (S32 i = 0; i < width; i++)
{
LLVector3 src((F32)(dst_data[VRED]),(F32)(dst_data[VGREEN]),(F32)(dst_data[VBLUE]));
LLVector3 dst = src * transform;
dst.clamp(0.0f,255.0f);
if (mVignetteMode == VIGNETTE_MODE_NONE)
{
dst_data[VRED] = dst.mV[VRED];
dst_data[VGREEN] = dst.mV[VGREEN];
dst_data[VBLUE] = dst.mV[VBLUE];
}
else
{
F32 alpha = getVignetteAlpha(i,j);
if (mVignetteMode == VIGNETTE_MODE_BLEND)
{
// Blends with the source image on the edges
F32 inv_alpha = 1.0 - alpha;
dst_data[VRED] = inv_alpha * src.mV[VRED] + alpha * dst.mV[VRED];
dst_data[VGREEN] = inv_alpha * src.mV[VGREEN] + alpha * dst.mV[VGREEN];
dst_data[VBLUE] = inv_alpha * src.mV[VBLUE] + alpha * dst.mV[VBLUE];
}
else // VIGNETTE_MODE_FADE
{
// Fade to black on the edges
dst_data[VRED] = alpha * dst.mV[VRED];
dst_data[VGREEN] = alpha * dst.mV[VGREEN];
dst_data[VBLUE] = alpha * dst.mV[VBLUE];
}
}
dst_data += components;
}
}
}
void LLImageFilter::filterScreen(EScreenMode mode, const S32 wave_length, const F32 angle)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
F32 sin = sinf(angle*DEG_TO_RAD);
F32 cos = cosf(angle*DEG_TO_RAD);
U8* dst_data = mImage->getData();
for (S32 j = 0; j < height; j++)
{
for (S32 i = 0; i < width; i++)
{
F32 value = 0.0;
F32 d = 0.0;
switch (mode)
{
case SCREEN_MODE_2DSINE:
value = (sinf(2*F_PI*i/wave_length)*sinf(2*F_PI*j/wave_length)+1.0)*255.0/2.0;
break;
case SCREEN_MODE_LINE:
d = sin*i - cos*j;
value = (sinf(2*F_PI*d/wave_length)+1.0)*255.0/2.0;
break;
}
U8 dst_value = (dst_data[VRED] >= (U8)(value) ? 255 : 0);
if (mVignetteMode == VIGNETTE_MODE_NONE)
{
dst_data[VRED] = dst_value;
dst_data[VGREEN] = dst_value;
dst_data[VBLUE] = dst_value;
}
else
{
F32 alpha = getVignetteAlpha(i,j);
if (mVignetteMode == VIGNETTE_MODE_BLEND)
{
// Blends with the source image on the edges
F32 inv_alpha = 1.0 - alpha;
dst_data[VRED] = inv_alpha * dst_data[VRED] + alpha * dst_value;
dst_data[VGREEN] = inv_alpha * dst_data[VGREEN] + alpha * dst_value;
dst_data[VBLUE] = inv_alpha * dst_data[VBLUE] + alpha * dst_value;
}
else // VIGNETTE_MODE_FADE
{
// Fade to black on the edges
dst_data[VRED] = alpha * dst_value;
dst_data[VGREEN] = alpha * dst_value;
dst_data[VBLUE] = alpha * dst_value;
}
}
dst_data += components;
}
}
}
//============================================================================
// Procedural Stencils
//============================================================================
void LLImageFilter::setVignette(EVignetteMode mode, EVignetteType type, F32 gamma, F32 min)
{
mVignetteMode = mode;
mVignetteType = type;
mVignetteGamma = gamma;
mVignetteMin = llclampf(min);
// We always center the vignette on the image and fits it in the image smallest dimension
mVignetteCenterX = mImage->getWidth()/2;
mVignetteCenterY = mImage->getHeight()/2;
mVignetteWidth = llmin(mImage->getWidth()/2,mImage->getHeight()/2);
}
F32 LLImageFilter::getVignetteAlpha(S32 i, S32 j)
{
F32 alpha = 1.0;
if (mVignetteType == VIGNETTE_TYPE_CENTER)
{
// alpha is a modified gaussian value, with a center and fading in a circular pattern toward the edges
// The gamma parameter controls the intensity of the drop down from alpha 1.0 (center) to 0.0
F32 d_center_square = (i - mVignetteCenterX)*(i - mVignetteCenterX) + (j - mVignetteCenterY)*(j - mVignetteCenterY);
alpha = powf(F_E, -(powf((d_center_square/(mVignetteWidth*mVignetteWidth)),mVignetteGamma)/2.0f));
}
else if (mVignetteType == VIGNETTE_TYPE_LINES)
{
// alpha varies according to a squared sine function vertically.
// gamma is interpreted as the wavelength (in pixels) of the sine in that case.
alpha = (sinf(2*F_PI*j/mVignetteGamma) > 0.0 ? 1.0 : 0.0);
}
// We rescale alpha between min and 1.0 so to avoid complete fading if so desired.
return (mVignetteMin + alpha * (1.0 - mVignetteMin));
}
//============================================================================
// Histograms
//============================================================================
U32* LLImageFilter::getBrightnessHistogram()
{
if (!mHistoBrightness)
{
computeHistograms();
}
return mHistoBrightness;
}
void LLImageFilter::computeHistograms()
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
// Allocate memory for the histograms
if (!mHistoRed)
{
mHistoRed = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
if (!mHistoGreen)
{
mHistoGreen = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
if (!mHistoBlue)
{
mHistoBlue = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
if (!mHistoBrightness)
{
mHistoBrightness = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
// Initialize them
for (S32 i = 0; i < 256; i++)
{
mHistoRed[i] = 0;
mHistoGreen[i] = 0;
mHistoBlue[i] = 0;
mHistoBrightness[i] = 0;
}
// Compute them
S32 pixels = mImage->getWidth() * mImage->getHeight();
U8* dst_data = mImage->getData();
for (S32 i = 0; i < pixels; i++)
{
mHistoRed[dst_data[VRED]]++;
mHistoGreen[dst_data[VGREEN]]++;
mHistoBlue[dst_data[VBLUE]]++;
// Note: this is a very simple shorthand for brightness but it's OK for our use
S32 brightness = ((S32)(dst_data[VRED]) + (S32)(dst_data[VGREEN]) + (S32)(dst_data[VBLUE])) / 3;
mHistoBrightness[brightness]++;
// next pixel...
dst_data += components;
}
}
//============================================================================
// Secondary Filters
//============================================================================
void LLImageFilter::filterGrayScale()
{
LLMatrix3 gray_scale;
LLVector3 luminosity(0.2125, 0.7154, 0.0721);
gray_scale.setRows(luminosity, luminosity, luminosity);
gray_scale.transpose();
colorTransform(gray_scale);
}
void LLImageFilter::filterSepia()
{
LLMatrix3 sepia;
sepia.setRows(LLVector3(0.3588, 0.7044, 0.1368),
LLVector3(0.2990, 0.5870, 0.1140),
LLVector3(0.2392, 0.4696, 0.0912));
sepia.transpose();
colorTransform(sepia);
}
void LLImageFilter::filterSaturate(F32 saturation)
{
// Matrix to Lij
LLMatrix3 r_a;
LLMatrix3 r_b;
// 45 degre rotation around z
r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0),
LLVector3(-OO_SQRT2, OO_SQRT2, 0.0),
LLVector3( 0.0, 0.0, 1.0));
// 54.73 degre rotation around y
float oo_sqrt3 = 1.0f / F_SQRT3;
float sin_54 = F_SQRT2 * oo_sqrt3;
r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54),
LLVector3(0.0, 1.0, 0.0),
LLVector3(sin_54, 0.0, oo_sqrt3));
// Coordinate conversion
LLMatrix3 Lij = r_b * r_a;
LLMatrix3 Lij_inv = Lij;
Lij_inv.transpose();
// Local saturation transform
LLMatrix3 s;
s.setRows(LLVector3(saturation, 0.0, 0.0),
LLVector3(0.0, saturation, 0.0),
LLVector3(0.0, 0.0, 1.0));
// Global saturation transform
LLMatrix3 transfo = Lij_inv * s * Lij;
colorTransform(transfo);
}
void LLImageFilter::filterRotate(F32 angle)
{
// Matrix to Lij
LLMatrix3 r_a;
LLMatrix3 r_b;
// 45 degre rotation around z
r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0),
LLVector3(-OO_SQRT2, OO_SQRT2, 0.0),
LLVector3( 0.0, 0.0, 1.0));
// 54.73 degre rotation around y
float oo_sqrt3 = 1.0f / F_SQRT3;
float sin_54 = F_SQRT2 * oo_sqrt3;
r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54),
LLVector3(0.0, 1.0, 0.0),
LLVector3(sin_54, 0.0, oo_sqrt3));
// Coordinate conversion
LLMatrix3 Lij = r_b * r_a;
LLMatrix3 Lij_inv = Lij;
Lij_inv.transpose();
// Local color rotation transform
LLMatrix3 r;
angle *= DEG_TO_RAD;
r.setRows(LLVector3( cosf(angle), sinf(angle), 0.0),
LLVector3(-sinf(angle), cosf(angle), 0.0),
LLVector3( 0.0, 0.0, 1.0));
// Global color rotation transform
LLMatrix3 transfo = Lij_inv * r * Lij;
colorTransform(transfo);
}
void LLImageFilter::filterGamma(F32 gamma, const LLColor3& alpha)
{
U8 gamma_red_lut[256];
U8 gamma_green_lut[256];
U8 gamma_blue_lut[256];
for (S32 i = 0; i < 256; i++)
{
F32 gamma_i = llclampf((float)(powf((float)(i)/255.0,gamma)));
// Blend in with alpha values
gamma_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * 255.0 * gamma_i);
gamma_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * 255.0 * gamma_i);
gamma_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * 255.0 * gamma_i);
}
colorCorrect(gamma_red_lut,gamma_green_lut,gamma_blue_lut);
}
void LLImageFilter::filterLinearize(F32 tail, const LLColor3& alpha)
{
// 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_red_lut[256];
U8 linear_green_lut[256];
U8 linear_blue_lut[256];
if (max_v == min_v)
{
// Degenerated binary split case
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (i < min_v ? 0 : 255);
// Blend in with alpha values
linear_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
linear_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
linear_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
}
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++)
{
U8 value_i = (U8)(llclampb((S32)(slope*i + translate)));
// Blend in with alpha values
linear_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
linear_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
linear_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
}
// Apply lookup table
colorCorrect(linear_red_lut,linear_green_lut,linear_blue_lut);
}
void LLImageFilter::filterEqualize(S32 nb_classes, const LLColor3& alpha)
{
// 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_red_lut[256];
U8 equalize_green_lut[256];
U8 equalize_blue_lut[256];
for (S32 i = 0; i < 256; i++)
{
// Blend in current_value with alpha values
equalize_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * current_value);
equalize_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * current_value);
equalize_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * 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_red_lut,equalize_green_lut,equalize_blue_lut);
}
void LLImageFilter::filterColorize(const LLColor3& color, const LLColor3& alpha)
{
U8 red_lut[256];
U8 green_lut[256];
U8 blue_lut[256];
F32 red_composite = 255.0 * alpha.mV[0] * color.mV[0];
F32 green_composite = 255.0 * alpha.mV[1] * color.mV[1];
F32 blue_composite = 255.0 * alpha.mV[2] * color.mV[2];
for (S32 i = 0; i < 256; i++)
{
red_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[0]) * (F32)(i) + red_composite)));
green_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[1]) * (F32)(i) + green_composite)));
blue_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[2]) * (F32)(i) + blue_composite)));
}
colorCorrect(red_lut,green_lut,blue_lut);
}
void LLImageFilter::filterContrast(F32 slope, const LLColor3& alpha)
{
U8 contrast_red_lut[256];
U8 contrast_green_lut[256];
U8 contrast_blue_lut[256];
F32 translate = 128.0 * (1.0 - slope);
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (U8)(llclampb((S32)(slope*i + translate)));
// Blend in with alpha values
contrast_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
contrast_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
contrast_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
colorCorrect(contrast_red_lut,contrast_green_lut,contrast_blue_lut);
}
void LLImageFilter::filterBrightness(S32 add, const LLColor3& alpha)
{
U8 brightness_red_lut[256];
U8 brightness_green_lut[256];
U8 brightness_blue_lut[256];
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (U8)(llclampb((S32)((S32)(i) + add)));
// Blend in with alpha values
brightness_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
brightness_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
brightness_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
colorCorrect(brightness_red_lut,brightness_green_lut,brightness_blue_lut);
}
//============================================================================