ACME-1236 : Refactor filters and vignette into llimagefilter, add example filters to llimage_libtest

master
Merov Linden 2014-01-14 21:01:51 -08:00
parent d972efa64c
commit 7cc64a09a3
22 changed files with 956 additions and 683 deletions

View File

@ -0,0 +1,41 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>contrast</string>
<real>0.8</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>colorize</string>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
<real>0.5</real>
<real>0.0</real>
<real>0.0</real>
</array>
<array>
<string>blend</string>
<real>10.0</real>
<real>0.0</real>
</array>
<array>
<string>colorize</string>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
<real>0.1</real>
<real>0.1</real>
<real>0.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,11 @@
<llsd>
<array>
<array>
<string>brighten</string>
<real>50.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,20 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>colorize</string>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
<real>0.2</real>
<real>0.0</real>
<real>0.2</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,18 @@
<llsd>
<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>1.5</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,11 @@
<llsd>
<array>
<array>
<string>darken</string>
<real>50.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,18 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>gamma</string>
<real>1.5</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,14 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>grayscale</string>
</array>
</array>
</llsd>

View File

@ -0,0 +1,25 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>grayscale</string>
</array>
<array>
<string>blend</string>
<real>0.0</real>
<real>0.0</real>
</array>
<array>
<string>screen</string>
<string>line</string>
<real>5.0</real>
<real>0.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,11 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,25 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>grayscale</string>
</array>
<array>
<string>blend</string>
<real>0.0</real>
<real>0.0</real>
</array>
<array>
<string>screen</string>
<string>2Dsine</string>
<real>5.0</real>
<real>0.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,18 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>posterize</string>
<real>10.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,8 @@
<llsd>
<array>
<array>
<string>rotate</string>
<real>180.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,8 @@
<llsd>
<array>
<array>
<string>saturate</string>
<real>3.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,14 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>sepia</string>
</array>
</array>
</llsd>

View File

@ -0,0 +1,25 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>grayscale</string>
</array>
<array>
<string>blend</string>
<real>0.0</real>
<real>0.0</real>
</array>
<array>
<string>screen</string>
<string>line</string>
<real>5.0</real>
<real>45.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,45 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>contrast</string>
<real>0.8</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>saturate</string>
<real>1.5</real>
</array>
<array>
<string>fade</string>
<real>1.0</real>
<real>0.25</real>
</array>
<array>
<string>saturate</string>
<real>0.8</real>
</array>
<array>
<string>contrast</string>
<real>1.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>brighten</string>
<real>30</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,25 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.1</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>grayscale</string>
</array>
<array>
<string>blend</string>
<real>0.0</real>
<real>0.0</real>
</array>
<array>
<string>screen</string>
<string>line</string>
<real>5.0</real>
<real>90.0</real>
</array>
</array>
</llsd>

View File

@ -0,0 +1,23 @@
<llsd>
<array>
<array>
<string>linearize</string>
<real>0.01</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
<array>
<string>lines</string>
<real>10.0</real>
<real>0.0</real>
</array>
<array>
<string>brighten</string>
<real>100.0</real>
<real>1.0</real>
<real>1.0</real>
<real>1.0</real>
</array>
</array>
</llsd>

View File

@ -98,14 +98,7 @@ LLImageBase::LLImageBase()
mHeight(0),
mComponents(0),
mBadBufferAllocation(false),
mAllowOverSize(false),
mHistoRed(NULL),
mHistoGreen(NULL),
mHistoBlue(NULL),
mHistoBrightness(NULL),
mVignetteMode(VIGNETTE_MODE_NONE),
mVignetteGamma(1.0),
mVignetteMin(0.0)
mAllowOverSize(false)
{
}
@ -113,10 +106,6 @@ LLImageBase::LLImageBase()
LLImageBase::~LLImageBase()
{
deleteData(); // virtual
ll_aligned_free_16(mHistoRed);
ll_aligned_free_16(mHistoGreen);
ll_aligned_free_16(mHistoBlue);
ll_aligned_free_16(mHistoBrightness);
}
//static
@ -947,520 +936,6 @@ BOOL LLImageRaw::scale( S32 new_width, S32 new_height, BOOL scale_image_data )
return TRUE ;
}
// Filter Operations
void LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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 LLImageRaw::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);
}
// Filter Primitives
void LLImageRaw::colorTransform(const LLMatrix3 &transform)
{
const S32 components = getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = getWidth();
S32 height = getHeight();
U8* dst_data = 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 LLImageRaw::colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue)
{
const S32 components = getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = getWidth();
S32 height = getHeight();
U8* dst_data = 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 LLImageRaw::filterScreen(EScreenMode mode, const S32 wave_length, const F32 angle)
{
const S32 components = getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = getWidth();
S32 height = getHeight();
F32 sin = sinf(angle*DEG_TO_RAD);
F32 cos = cosf(angle*DEG_TO_RAD);
U8* dst_data = 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;
}
}
}
void LLImageRaw::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 = getWidth()/2;
mVignetteCenterY = getHeight()/2;
mVignetteWidth = llmin(getWidth()/2,getHeight()/2);
}
F32 LLImageRaw::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));
}
U32* LLImageRaw::getBrightnessHistogram()
{
if (!mHistoBrightness)
{
computeHistograms();
}
return mHistoBrightness;
}
void LLImageRaw::computeHistograms()
{
const S32 components = 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 = getWidth() * getHeight();
U8* dst_data = 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;
}
}
void LLImageRaw::copyLineScaled( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len, S32 in_pixel_step, S32 out_pixel_step )
{
const S32 components = getComponents();

View File

@ -88,25 +88,6 @@ typedef enum e_image_codec
IMG_CODEC_EOF = 8
} EImageCodec;
typedef enum e_vignette_mode
{
VIGNETTE_MODE_NONE = 0,
VIGNETTE_MODE_BLEND = 1,
VIGNETTE_MODE_FADE = 2
} EVignetteMode;
typedef enum e_vignette_type
{
VIGNETTE_TYPE_CENTER = 0,
VIGNETTE_TYPE_LINES = 1
} EVignetteType;
typedef enum e_screen_mode
{
SCREEN_MODE_2DSINE = 0,
SCREEN_MODE_LINE = 1
} EScreenMode;
//============================================================================
// library initialization class
@ -171,21 +152,6 @@ protected:
// special accessor to allow direct setting of mData and mDataSize by LLImageFormatted
void setDataAndSize(U8 *data, S32 size);
// Histograms (if we ever happen to need them)
U32 *mHistoRed;
U32 *mHistoGreen;
U32 *mHistoBlue;
U32 *mHistoBrightness;
// Vignette filtering
EVignetteMode mVignetteMode;
EVignetteType mVignetteType;
S32 mVignetteCenterX;
S32 mVignetteCenterY;
S32 mVignetteWidth;
F32 mVignetteGamma;
F32 mVignetteMin;
public:
static void generateMip(const U8 *indata, U8* mipdata, int width, int height, S32 nchannels);
@ -291,30 +257,6 @@ public:
// Src and dst are same size. Src has 4 components. Dst has 3 components.
void compositeUnscaled4onto3( LLImageRaw* src );
// Filter Operations : Transforms
void filterGrayScale(); // Convert to grayscale
void filterSepia(); // Convert to sepia
void filterSaturate(F32 saturation); // < 1.0 desaturates, > 1.0 saturates
void filterRotate(F32 angle); // Rotates hue according to angle, angle in degrees
// Filter Operations : Color Corrections
// When specified, the LLColor3 alpha parameter indicates the intensity of the effect for each color channel
// acting in effect as an alpha blending factor different for each channel. For instance (1.0,0.0,0.0) will apply
// the effect only to the Red channel. Intermediate values blends the effect with the source color.
void filterGamma(F32 gamma, const LLColor3& alpha); // Apply gamma to each channel
void filterLinearize(F32 tail, const LLColor3& alpha); // Use histogram to linearize constrast between min and max values minus tail
void filterEqualize(S32 nb_classes, const LLColor3& alpha); // Use histogram to equalize constrast between nb_classes throughout the image
void filterColorize(const LLColor3& color, const LLColor3& alpha); // Colorize with color and alpha per channel
void filterContrast(F32 slope, const LLColor3& alpha); // Change contrast according to slope: > 1.0 more contrast, < 1.0 less contrast
void filterBrightness(S32 add, const LLColor3& alpha); // 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);
void filterScreen(EScreenMode mode, const S32 wave_length, const F32 angle);
void setVignette(EVignetteMode mode, EVignetteType type, F32 gamma, F32 min);
U32* getBrightnessHistogram();
protected:
// Create an image from a local file (generally used in tools)
@ -327,9 +269,6 @@ protected:
void setDataAndSize(U8 *data, S32 width, S32 height, S8 components) ;
void computeHistograms();
F32 getVignetteAlpha(S32 i, S32 j);
public:
static S32 sGlobalRawMemory;
static S32 sRawImageCount;

View File

@ -40,16 +40,28 @@
//---------------------------------------------------------------------------
LLImageFilter::LLImageFilter() :
mFilterData(LLSD::emptyArray())
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);
}
/*
" -f, --filter <name> [<param>]\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"
@ -67,86 +79,10 @@ LLImageFilter::~LLImageFilter()
" - '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"
" -v, --vignette <name> [<feather> <min>]\n"
" Apply a circular central vignette <name> to the filter using the optional <feather> and <min> 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"
// Set the vignette if any
if (vignette_name == "blend")
{
raw_image->setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_CENTER,(float)(vignette_param_1),(float)(vignette_param_2));
}
else if (vignette_name == "fade")
{
raw_image->setVignette(VIGNETTE_MODE_FADE,VIGNETTE_TYPE_CENTER,(float)(vignette_param_1),(float)(vignette_param_2));
}
// Apply filter if any
if (filter_name == "sepia")
{
raw_image->filterSepia();
}
else if (filter_name == "grayscale")
{
raw_image->filterGrayScale();
}
else if (filter_name == "saturate")
{
raw_image->filterSaturate((float)(filter_param));
}
else if (filter_name == "rotate")
{
raw_image->filterRotate((float)(filter_param));
}
else if (filter_name == "gamma")
{
raw_image->filterGamma((float)(filter_param),LLColor3::white);
}
else if (filter_name == "colorize")
{
// For testing, we just colorize in the red channel, modulate by the alpha passed as a parameter
LLColor3 color(1.0,0.0,0.0);
LLColor3 alpha((F32)(filter_param),0.0,0.0);
raw_image->filterColorize(color,alpha);
}
else if (filter_name == "contrast")
{
raw_image->filterContrast((float)(filter_param),LLColor3::white);
}
else if (filter_name == "brighten")
{
raw_image->filterBrightness((S32)(filter_param),LLColor3::white);
}
else if (filter_name == "darken")
{
raw_image->filterBrightness((S32)(-filter_param),LLColor3::white);
}
else if (filter_name == "linearize")
{
raw_image->filterLinearize((float)(filter_param),LLColor3::white);
}
else if (filter_name == "posterize")
{
raw_image->filterEqualize((S32)(filter_param),LLColor3::white);
}
else if (filter_name == "newsscreen")
{
raw_image->filterScreen(SCREEN_MODE_2DSINE,(S32)(filter_param),0.0);
}
else if (filter_name == "horizontalscreen")
{
raw_image->filterScreen(SCREEN_MODE_LINE,(S32)(filter_param),0.0);
}
else if (filter_name == "verticalscreen")
{
raw_image->filterScreen(SCREEN_MODE_LINE,(S32)(filter_param),90.0);
}
else if (filter_name == "slantedscreen")
{
raw_image->filterScreen(SCREEN_MODE_LINE,(S32)(filter_param),45.0);
}
*/
// Load filter from file
@ -171,6 +107,8 @@ void LLImageFilter::loadFromFile(const std::string& file_path)
// Apply the filter data to the image passed as parameter
void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image)
{
mImage = raw_image;
//std::cout << "Filter : size = " << mFilterData.size() << std::endl;
for (S32 i = 0; i < mFilterData.size(); ++i)
{
@ -186,67 +124,67 @@ void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image)
// Execute the filter described on this line
if (filter_name == "blend")
{
raw_image->setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_CENTER,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_CENTER,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
}
else if (filter_name == "fade")
{
raw_image->setVignette(VIGNETTE_MODE_FADE,VIGNETTE_TYPE_CENTER,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
setVignette(VIGNETTE_MODE_FADE,VIGNETTE_TYPE_CENTER,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
}
else if (filter_name == "lines")
{
raw_image->setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_LINES,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
setVignette(VIGNETTE_MODE_BLEND,VIGNETTE_TYPE_LINES,(float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()));
}
else if (filter_name == "sepia")
{
raw_image->filterSepia();
filterSepia();
}
else if (filter_name == "grayscale")
{
raw_image->filterGrayScale();
filterGrayScale();
}
else if (filter_name == "saturate")
{
raw_image->filterSaturate((float)(mFilterData[i][1].asReal()));
filterSaturate((float)(mFilterData[i][1].asReal()));
}
else if (filter_name == "rotate")
{
raw_image->filterRotate((float)(mFilterData[i][1].asReal()));
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()));
raw_image->filterGamma((float)(mFilterData[i][1].asReal()),color);
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()));
raw_image->filterColorize(color,alpha);
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()));
raw_image->filterContrast((float)(mFilterData[i][1].asReal()),color);
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()));
raw_image->filterBrightness((S32)(mFilterData[i][1].asReal()),color);
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()));
raw_image->filterBrightness((S32)(-mFilterData[i][1].asReal()),color);
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()));
raw_image->filterLinearize((float)(mFilterData[i][1].asReal()),color);
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()));
raw_image->filterEqualize((S32)(mFilterData[i][1].asReal()),color);
filterEqualize((S32)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "screen")
{
@ -260,10 +198,525 @@ void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image)
{
mode = SCREEN_MODE_LINE;
}
raw_image->filterScreen(mode,(S32)(mFilterData[i][2].asReal()),(F32)(mFilterData[i][3].asReal()));
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);
}
//============================================================================

View File

@ -29,7 +29,6 @@
#include "llimage.h"
/*
class LLImageRaw;
class LLColor4U;
class LLColor3;
@ -53,7 +52,7 @@ typedef enum e_screen_mode
SCREEN_MODE_2DSINE = 0,
SCREEN_MODE_LINE = 1
} EScreenMode;
*/
//============================================================================
// Image Filter
@ -65,8 +64,55 @@ public:
void loadFromFile(const std::string& file_path);
void executeFilter(LLPointer<LLImageRaw> raw_image);
private:
// Filter Operations : Transforms
void filterGrayScale(); // Convert to grayscale
void filterSepia(); // Convert to sepia
void filterSaturate(F32 saturation); // < 1.0 desaturates, > 1.0 saturates
void filterRotate(F32 angle); // Rotates hue according to angle, angle in degrees
// Filter Operations : Color Corrections
// When specified, the LLColor3 alpha parameter indicates the intensity of the effect for each color channel
// acting in effect as an alpha blending factor different for each channel. For instance (1.0,0.0,0.0) will apply
// the effect only to the Red channel. Intermediate values blends the effect with the source color.
void filterGamma(F32 gamma, const LLColor3& alpha); // Apply gamma to each channel
void filterLinearize(F32 tail, const LLColor3& alpha); // Use histogram to linearize constrast between min and max values minus tail
void filterEqualize(S32 nb_classes, const LLColor3& alpha); // Use histogram to equalize constrast between nb_classes throughout the image
void filterColorize(const LLColor3& color, const LLColor3& alpha); // Colorize with color and alpha per channel
void filterContrast(F32 slope, const LLColor3& alpha); // Change contrast according to slope: > 1.0 more contrast, < 1.0 less contrast
void filterBrightness(S32 add, const LLColor3& alpha); // 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);
void filterScreen(EScreenMode mode, const S32 wave_length, const F32 angle);
// Procedural Stencils
void setVignette(EVignetteMode mode, EVignetteType type, F32 gamma, F32 min);
F32 getVignetteAlpha(S32 i, S32 j);
// Histograms
U32* getBrightnessHistogram();
void computeHistograms();
LLSD mFilterData;
LLPointer<LLImageRaw> mImage;
// Histograms (if we ever happen to need them)
U32 *mHistoRed;
U32 *mHistoGreen;
U32 *mHistoBlue;
U32 *mHistoBrightness;
// Vignette filtering
EVignetteMode mVignetteMode;
EVignetteType mVignetteType;
S32 mVignetteCenterX;
S32 mVignetteCenterY;
S32 mVignetteWidth;
F32 mVignetteGamma;
F32 mVignetteMin;
};