You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digikam/digikam/libs/levels/imagelevels.cpp

716 lines
17 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-07-29
* Description : image levels manipulation methods.
*
* Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* Some code parts are inspired from gimp 2.0
* app/base/levels.c, gimplut.c, and app/base/gimpleveltool.c
* source files.
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program 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 General Public License for more details.
*
* ============================================================ */
// TQt includes.
#include <tqfile.h>
// C++ includes.
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cerrno>
// Local includes.
#include "ddebug.h"
#include "imagehistogram.h"
#include "imagelevels.h"
namespace Digikam
{
class ImageLevelsPriv
{
public:
enum PixelType
{
RedPixel = 0,
GreenPixel,
BluePixel,
AlphaPixel
};
struct _Levels
{
double gamma[5];
int low_input[5];
int high_input[5];
int low_output[5];
int high_output[5];
};
struct _Lut
{
unsigned short **luts;
int nchannels;
};
public:
ImageLevelsPriv()
{
levels = 0;
lut = 0;
dirty = false;
}
// Levels data.
struct _Levels *levels;
// Lut data.
struct _Lut *lut;
bool sixteenBit;
bool dirty;
};
ImageLevels::ImageLevels(bool sixteenBit)
{
d = new ImageLevelsPriv;
d->lut = new ImageLevelsPriv::_Lut;
d->levels = new ImageLevelsPriv::_Levels;
d->sixteenBit = sixteenBit;
memset(d->levels, 0, sizeof(struct ImageLevelsPriv::_Levels));
d->lut->luts = NULL;
d->lut->nchannels = 0;
reset();
}
ImageLevels::~ImageLevels()
{
if (d->lut)
{
if (d->lut->luts)
{
for (int i = 0 ; i < d->lut->nchannels ; i++)
delete [] d->lut->luts[i];
delete [] d->lut->luts;
}
delete d->lut;
}
if (d->levels)
delete d->levels;
delete d;
}
bool ImageLevels::isDirty()
{
return d->dirty;
}
bool ImageLevels::isSixteenBits()
{
return d->sixteenBit;
}
void ImageLevels::reset()
{
for (int channel = 0 ; channel < 5 ; channel++)
levelsChannelReset(channel);
}
void ImageLevels::levelsChannelReset(int channel)
{
if (!d->levels) return;
d->levels->gamma[channel] = 1.0;
d->levels->low_input[channel] = 0;
d->levels->high_input[channel] = d->sixteenBit ? 65535 : 255;
d->levels->low_output[channel] = 0;
d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255;
d->dirty = false;
}
void ImageLevels::levelsAuto(ImageHistogram *hist)
{
if (!d->levels || !hist) return;
levelsChannelReset(ImageHistogram::ValueChannel);
for (int channel = ImageHistogram::RedChannel ;
channel <= ImageHistogram::BlueChannel ;
channel++)
{
levelsChannelAuto(hist, channel);
}
d->dirty = true;
}
void ImageLevels::levelsChannelAuto(ImageHistogram *hist, int channel)
{
int i;
double count, new_count, percentage, next_percentage;
if (!d->levels || !hist) return;
d->levels->gamma[channel] = 1.0;
d->levels->low_output[channel] = 0;
d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255;
count = hist->getCount(channel, 0, d->sixteenBit ? 65535 : 255);
if (count == 0.0)
{
d->levels->low_input[channel] = 0;
d->levels->high_input[channel] = 0;
}
else
{
// Set the low input
new_count = 0.0;
for (i = 0 ; i < (d->sixteenBit ? 65535 : 255) ; i++)
{
new_count += hist->getValue(channel, i);
percentage = new_count / count;
next_percentage = (new_count + hist->getValue(channel, i + 1)) / count;
if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
{
d->levels->low_input[channel] = i + 1;
break;
}
}
// Set the high input
new_count = 0.0;
for (i = (d->sixteenBit ? 65535 : 255) ; i > 0 ; i--)
{
new_count += hist->getValue(channel, i);
percentage = new_count / count;
next_percentage = (new_count + hist->getValue(channel, i - 1)) / count;
if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
{
d->levels->high_input[channel] = i - 1;
break;
}
}
}
d->dirty = true;
}
int ImageLevels::levelsInputFromColor(int channel, const DColor& color)
{
switch (channel)
{
case ImageHistogram::ValueChannel:
return TQMAX (TQMAX (color.red(), color.green()), color.blue());
case ImageHistogram::RedChannel:
return color.red();
case ImageHistogram::GreenChannel:
return color.green();
case ImageHistogram::BlueChannel:
return color.blue();
}
return 0; // just to please the compiler.
}
void ImageLevels::levelsBlackToneAdjustByColors(int channel, const DColor& color)
{
if (!d->levels) return;
d->levels->low_input[channel] = levelsInputFromColor(channel, color);
d->dirty = true;
}
void ImageLevels::levelsWhiteToneAdjustByColors(int channel, const DColor& color)
{
if (!d->levels) return;
d->levels->high_input[channel] = levelsInputFromColor(channel, color);
d->dirty = true;
}
void ImageLevels::levelsGrayToneAdjustByColors(int channel, const DColor& color)
{
if (!d->levels) return;
int input;
int range;
double inten;
double out_light;
unsigned short lightness;
// Calculate lightness value.
lightness = (unsigned short)LEVELS_RGB_INTENSITY (color.red(), color.green(), color.blue());
input = levelsInputFromColor(channel, color);
range = d->levels->high_input[channel] - d->levels->low_input[channel];
if (range <= 0)
return;
input -= d->levels->low_input[channel];
if (input < 0)
return;
// Normalize input and lightness.
inten = (double) input / (double) range;
out_light = (double) lightness/ (double) range;
if (out_light <= 0)
return;
// Map selected color to corresponding lightness.
d->levels->gamma[channel] = log (inten) / log (out_light);
d->dirty = true;
}
void ImageLevels::levelsCalculateTransfers()
{
double inten;
int i, j;
if (!d->levels) return;
// Recalculate the levels arrays.
for (j = 0 ; j < 5 ; j++)
{
for (i = 0; i <= (d->sixteenBit ? 65535 : 255); i++)
{
// determine input intensity.
if (d->levels->high_input[j] != d->levels->low_input[j])
{
inten = ((double) (i - d->levels->low_input[j]) /
(double) (d->levels->high_input[j] - d->levels->low_input[j]));
}
else
{
inten = (double) (i - d->levels->low_input[j]);
}
inten = CLAMP (inten, 0.0, 1.0);
if (d->levels->gamma[j] != 0.0)
inten = pow (inten, (1.0 / d->levels->gamma[j]));
}
}
}
float ImageLevels::levelsLutFunc(int n_channels, int channel, float value)
{
double inten;
int j;
if (!d->levels) return 0.0;
if (n_channels == 1)
j = 0;
else
j = channel + 1;
inten = value;
// For color images this runs through the loop with j = channel +1
// the first time and j = 0 the second time.
//
// For bw images this runs through the loop with j = 0 the first and
// only time.
for ( ; j >= 0 ; j -= (channel + 1) )
{
// Don't apply the overall curve to the alpha channel.
if (j == 0 && (n_channels == 2 || n_channels == 4)
&& channel == n_channels -1)
return inten;
// Determine input intensity.
if (d->levels->high_input[j] != d->levels->low_input[j])
inten = ((double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]) /
(double) (d->levels->high_input[j] - d->levels->low_input[j]));
else
inten = (double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]);
if (d->levels->gamma[j] != 0.0)
{
if (inten >= 0.0)
inten = pow ( inten, (1.0 / d->levels->gamma[j]));
else
inten = -pow (-inten, (1.0 / d->levels->gamma[j]));
}
// determine the output intensity.
if (d->levels->high_output[j] >= d->levels->low_output[j])
inten = (double) (inten * (d->levels->high_output[j] -
d->levels->low_output[j]) + d->levels->low_output[j]);
else if (d->levels->high_output[j] < d->levels->low_output[j])
inten = (double) (d->levels->low_output[j] - inten *
(d->levels->low_output[j] - d->levels->high_output[j]));
inten /= (float)(d->sixteenBit ? 65535 : 255);
}
return inten;
}
void ImageLevels::levelsLutSetup(int nchannels)
{
int i;
uint v;
double val;
if (d->lut->luts)
{
for (i = 0 ; i < d->lut->nchannels ; i++)
delete [] d->lut->luts[i];
delete [] d->lut->luts;
}
d->lut->nchannels = nchannels;
d->lut->luts = new unsigned short*[d->lut->nchannels];
for (i = 0 ; i < d->lut->nchannels ; i++)
{
d->lut->luts[i] = new unsigned short[(d->sixteenBit ? 65535 : 255) + 1];
for (v = 0 ; v <= (d->sixteenBit ? 65535 : 255) ; v++)
{
// to add gamma correction use func(v ^ g) ^ 1/g instead.
val = (float)(d->sixteenBit ? 65535 : 255) *
levelsLutFunc( d->lut->nchannels, i, v/(float)(d->sixteenBit ? 65535 : 255)) + 0.5;
d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, (d->sixteenBit ? 65535 : 255));
}
}
}
void ImageLevels::levelsLutProcess(uchar *srcPR, uchar *destPR, int w, int h)
{
unsigned short *lut0 = NULL, *lut1 = NULL, *lut2 = NULL, *lut3 = NULL;
int i;
if (d->lut->nchannels > 0)
lut0 = d->lut->luts[0];
if (d->lut->nchannels > 1)
lut1 = d->lut->luts[1];
if (d->lut->nchannels > 2)
lut2 = d->lut->luts[2];
if (d->lut->nchannels > 3)
lut3 = d->lut->luts[3];
if (!d->sixteenBit) // 8 bits image.
{
uchar red, green, blue, alpha;
uchar *ptr = srcPR;
uchar *dst = destPR;
for (i = 0 ; i < w*h ; i++)
{
blue = ptr[0];
green = ptr[1];
red = ptr[2];
alpha = ptr[3];
if ( d->lut->nchannels > 0 )
red = lut0[red];
if ( d->lut->nchannels > 1 )
green = lut1[green];
if ( d->lut->nchannels > 2 )
blue = lut2[blue];
if ( d->lut->nchannels > 3 )
alpha = lut3[alpha];
dst[0] = blue;
dst[1] = green;
dst[2] = red;
dst[3] = alpha;
ptr += 4;
dst += 4;
}
}
else // 16 bits image.
{
unsigned short red, green, blue, alpha;
unsigned short *ptr = (unsigned short *)srcPR;
unsigned short *dst = (unsigned short *)destPR;
for (i = 0 ; i < w*h ; i++)
{
blue = ptr[0];
green = ptr[1];
red = ptr[2];
alpha = ptr[3];
if ( d->lut->nchannels > 0 )
red = lut0[red];
if ( d->lut->nchannels > 1 )
green = lut1[green];
if ( d->lut->nchannels > 2 )
blue = lut2[blue];
if ( d->lut->nchannels > 3 )
alpha = lut3[alpha];
dst[0] = blue;
dst[1] = green;
dst[2] = red;
dst[3] = alpha;
ptr += 4;
dst += 4;
}
}
}
void ImageLevels::setLevelGammaValue(int Channel, double val)
{
if ( d->levels && Channel>=0 && Channel<5 )
{
d->levels->gamma[Channel] = val;
d->dirty = true;
}
}
void ImageLevels::setLevelLowInputValue(int Channel, int val)
{
if ( d->levels && Channel>=0 && Channel<5 )
{
d->levels->low_input[Channel] = val;
d->dirty = true;
}
}
void ImageLevels::setLevelHighInputValue(int Channel, int val)
{
if ( d->levels && Channel>=0 && Channel<5 )
{
d->levels->high_input[Channel] = val;
d->dirty = true;
}
}
void ImageLevels::setLevelLowOutputValue(int Channel, int val)
{
if ( d->levels && Channel>=0 && Channel<5 )
{
d->levels->low_output[Channel] = val;
d->dirty = true;
}
}
void ImageLevels::setLevelHighOutputValue(int Channel, int val)
{
if ( d->levels && Channel>=0 && Channel<5 )
{
d->levels->high_output[Channel] = val;
d->dirty = true;
}
}
double ImageLevels::getLevelGammaValue(int Channel)
{
if ( d->levels && Channel>=0 && Channel<5 )
return (d->levels->gamma[Channel]);
return 0.0;
}
int ImageLevels::getLevelLowInputValue(int Channel)
{
if ( d->levels && Channel>=0 && Channel<5 )
return (d->levels->low_input[Channel]);
return 0;
}
int ImageLevels::getLevelHighInputValue(int Channel)
{
if ( d->levels && Channel>=0 && Channel<5 )
return (d->levels->high_input[Channel]);
return 0;
}
int ImageLevels::getLevelLowOutputValue(int Channel)
{
if ( d->levels && Channel>=0 && Channel<5 )
return (d->levels->low_output[Channel]);
return 0;
}
int ImageLevels::getLevelHighOutputValue(int Channel)
{
if ( d->levels && Channel>=0 && Channel<5 )
return (d->levels->high_output[Channel]);
return 0;
}
bool ImageLevels::loadLevelsFromGimpLevelsFile(const KURL& fileUrl)
{
// TODO : support KURL !
FILE *file;
int low_input[5];
int high_input[5];
int low_output[5];
int high_output[5];
double gamma[5];
int i, fields;
char buf[50];
char *nptr;
file = fopen(TQFile::encodeName(fileUrl.path()), "r");
if (!file)
return false;
if (! fgets (buf, sizeof (buf), file))
{
fclose(file);
return false;
}
if (strcmp (buf, "# GIMP Levels File\n") != 0)
{
fclose(file);
return false;
}
for (i = 0 ; i < 5 ; i++)
{
fields = fscanf (file, "%d %d %d %d ",
&low_input[i],
&high_input[i],
&low_output[i],
&high_output[i]);
if (fields != 4)
{
DWarning() << "Invalid Gimp levels file!" << endl;
fclose(file);
return false;
}
if (!fgets (buf, 50, file))
{
DWarning() << "Invalid Gimp levels file!" << endl;
fclose(file);
return false;
}
gamma[i] = strtod (buf, &nptr);
if (buf == nptr || errno == ERANGE)
{
DWarning() << "Invalid Gimp levels file!" << endl;
fclose(file);
return false;
}
}
for (i = 0 ; i < 5 ; i++)
{
setLevelGammaValue(i, gamma[i]);
setLevelLowInputValue(i, d->sixteenBit ? low_input[i]*255 : low_input[i]);
setLevelHighInputValue(i, d->sixteenBit ? high_input[i]*255 : high_input[i]);
setLevelLowOutputValue(i, d->sixteenBit ? low_output[i]*255 : low_output[i]);
setLevelHighOutputValue(i, d->sixteenBit ? high_output[i]*255 : high_output[i]);
}
fclose(file);
return true;
}
bool ImageLevels::saveLevelsToGimpLevelsFile(const KURL& fileUrl)
{
// TODO : support KURL !
FILE *file;
int i;
file = fopen(TQFile::encodeName(fileUrl.path()), "w");
if (!file)
return false;
fprintf (file, "# GIMP Levels File\n");
for (i = 0 ; i < 5 ; i++)
{
char buf[256];
sprintf (buf, "%f", getLevelGammaValue(i));
fprintf (file, "%d %d %d %d %s\n",
d->sixteenBit ? getLevelLowInputValue(i)/255 : getLevelLowInputValue(i),
d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i),
d->sixteenBit ? getLevelLowOutputValue(i)/255 : getLevelLowOutputValue(i),
d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i),
buf);
}
fflush(file);
fclose(file);
return true;
}
} // NameSpace Digikam