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/curves/imagecurves.cpp

769 lines
18 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-12-01
* Description : image curves 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/curves.c, gimplut.c, and app/base/gimpcurvetool.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.
*
* ============================================================ */
#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
// C++ includes.
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cerrno>
// TQt includes.
#include <tqfile.h>
// Local includes.
#include "ddebug.h"
#include "imagecurves.h"
namespace Digikam
{
class ImageCurvesPriv
{
public:
struct _Curves
{
ImageCurves::CurveType curve_type[5]; // Curve types by channels (Smooth or Free).
int points[5][17][2]; // Curve main points in Smooth mode ([channel][point id][x,y]).
unsigned short curve[5][65536]; // Curve values by channels.
};
struct _Lut
{
unsigned short **luts;
int nchannels;
};
public:
ImageCurvesPriv()
{
curves = 0;
lut = 0;
dirty = false;
}
// Curves data.
struct _Curves *curves;
// Lut data.
struct _Lut *lut;
int segmentMax;
bool dirty;
};
ImageCurves::CRMatrix CR_basis =
{
{ -0.5, 1.5, -1.5, 0.5 },
{ 1.0, -2.5, 2.0, -0.5 },
{ -0.5, 0.0, 0.5, 0.0 },
{ 0.0, 1.0, 0.0, 0.0 },
};
ImageCurves::ImageCurves(bool sixteenBit)
{
d = new ImageCurvesPriv;
d->lut = new ImageCurvesPriv::_Lut;
d->curves = new ImageCurvesPriv::_Curves;
d->segmentMax = sixteenBit ? 65535 : 255;
curvesReset();
}
ImageCurves::~ImageCurves()
{
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->curves)
delete d->curves;
delete d;
}
bool ImageCurves::isDirty()
{
return d->dirty;
}
bool ImageCurves::isSixteenBits()
{
return (d->segmentMax == 65535);
}
void ImageCurves::curvesReset()
{
memset(d->curves, 0, sizeof(struct ImageCurvesPriv::_Curves));
d->lut->luts = NULL;
d->lut->nchannels = 0;
d->dirty = false;
for (int channel = 0 ; channel < 5 ; channel++)
{
setCurveType(channel, CURVE_SMOOTH);
curvesChannelReset(channel);
}
}
void ImageCurves::curvesChannelReset(int channel)
{
int j;
if (!d->curves) return;
// Contruct a linear curve.
for (j = 0 ; j <= d->segmentMax ; j++)
d->curves->curve[channel][j] = j;
// Init coordinates points to null.
for (j = 0 ; j < 17 ; j++)
{
d->curves->points[channel][j][0] = -1;
d->curves->points[channel][j][1] = -1;
}
// First and last points init.
d->curves->points[channel][0][0] = 0;
d->curves->points[channel][0][1] = 0;
d->curves->points[channel][16][0] = d->segmentMax;
d->curves->points[channel][16][1] = d->segmentMax;
}
void ImageCurves::curvesCalculateCurve(int channel)
{
int i;
int points[17];
int num_pts;
int p1, p2, p3, p4;
if (!d->curves) return;
switch (d->curves->curve_type[channel])
{
case CURVE_FREE:
break;
case CURVE_SMOOTH:
{
// Cycle through the curves
num_pts = 0;
for (i = 0 ; i < 17 ; i++)
if (d->curves->points[channel][i][0] != -1)
points[num_pts++] = i;
// Initialize boundary curve points
if (num_pts != 0)
{
for (i = 0 ; i < d->curves->points[channel][points[0]][0] ; i++)
{
d->curves->curve[channel][i] = d->curves->points[channel][points[0]][1];
}
for (i = d->curves->points[channel][points[num_pts - 1]][0] ; i <= d->segmentMax ; i++)
{
d->curves->curve[channel][i] = d->curves->points[channel][points[num_pts - 1]][1];
}
}
for (i = 0 ; i < num_pts - 1 ; i++)
{
p1 = (i == 0) ? points[i] : points[(i - 1)];
p2 = points[i];
p3 = points[(i + 1)];
p4 = (i == (num_pts - 2)) ? points[(num_pts - 1)] : points[(i + 2)];
curvesPlotCurve(channel, p1, p2, p3, p4);
}
// Ensure that the control points are used exactly
for (i = 0 ; i < num_pts ; i++)
{
int x, y;
x = d->curves->points[channel][points[i]][0];
y = d->curves->points[channel][points[i]][1];
d->curves->curve[channel][x] = y;
}
break;
}
}
}
float ImageCurves::curvesLutFunc(int n_channels, int channel, float value)
{
float f;
int index;
double inten;
int j;
if (!d->curves) 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;
if (inten < 0.0)
inten = d->curves->curve[j][0]/(float)d->segmentMax;
else if (inten >= 1.0)
inten = d->curves->curve[j][d->segmentMax]/(float)(d->segmentMax);
else // interpolate the curve.
{
index = (int)floor(inten * (float)(d->segmentMax));
f = inten * (float)(d->segmentMax) - index;
inten = ((1.0 - f) * d->curves->curve[j][index ] +
( f) * d->curves->curve[j][index + 1] ) / (float)(d->segmentMax);
}
}
return inten;
}
void ImageCurves::curvesPlotCurve(int channel, int p1, int p2, int p3, int p4)
{
CRMatrix geometry;
CRMatrix tmp1, tmp2;
CRMatrix deltas;
double x, dx, dx2, dx3;
double y, dy, dy2, dy3;
double d1, d2, d3;
int lastx, lasty;
int newx, newy;
int i;
int loopdiv = d->segmentMax * 3;
if (!d->curves) return;
// Construct the geometry matrix from the segment.
for (i = 0 ; i < 4 ; i++)
{
geometry[i][2] = 0;
geometry[i][3] = 0;
}
for (i = 0 ; i < 2 ; i++)
{
geometry[0][i] = d->curves->points[channel][p1][i];
geometry[1][i] = d->curves->points[channel][p2][i];
geometry[2][i] = d->curves->points[channel][p3][i];
geometry[3][i] = d->curves->points[channel][p4][i];
}
// Subdivide the curve 1000 times.
// n can be adjusted to give a finer or coarser curve.
d1 = 1.0 / loopdiv;
d2 = d1 * d1;
d3 = d1 * d1 * d1;
// Construct a temporary matrix for determining the forward differencing deltas.
tmp2[0][0] = 0; tmp2[0][1] = 0; tmp2[0][2] = 0; tmp2[0][3] = 1;
tmp2[1][0] = d3; tmp2[1][1] = d2; tmp2[1][2] = d1; tmp2[1][3] = 0;
tmp2[2][0] = 6*d3; tmp2[2][1] = 2*d2; tmp2[2][2] = 0; tmp2[2][3] = 0;
tmp2[3][0] = 6*d3; tmp2[3][1] = 0; tmp2[3][2] = 0; tmp2[3][3] = 0;
// Compose the basis and geometry matrices.
curvesCRCompose(CR_basis, geometry, tmp1);
// Compose the above results to get the deltas matrix.
curvesCRCompose(tmp2, tmp1, deltas);
// Extract the x deltas.
x = deltas[0][0];
dx = deltas[1][0];
dx2 = deltas[2][0];
dx3 = deltas[3][0];
// Extract the y deltas.
y = deltas[0][1];
dy = deltas[1][1];
dy2 = deltas[2][1];
dy3 = deltas[3][1];
lastx = (int)CLAMP (x, 0, d->segmentMax);
lasty = (int)CLAMP (y, 0, d->segmentMax);
d->curves->curve[channel][lastx] = lasty;
// Loop over the curve.
for (i = 0 ; i < loopdiv ; i++)
{
// Increment the x values.
x += dx;
dx += dx2;
dx2 += dx3;
// Increment the y values.
y += dy;
dy += dy2;
dy2 += dy3;
newx = CLAMP(ROUND (x), 0, d->segmentMax);
newy = CLAMP(ROUND (y), 0, d->segmentMax);
// If this point is different than the last one...then draw it.
if ((lastx != newx) || (lasty != newy))
d->curves->curve[channel][newx] = newy;
lastx = newx;
lasty = newy;
}
}
void ImageCurves::curvesCRCompose(CRMatrix a, CRMatrix b, CRMatrix ab)
{
int i, j;
for (i = 0 ; i < 4 ; i++)
{
for (j = 0 ; j < 4 ; j++)
{
ab[i][j] = (a[i][0] * b[0][j] +
a[i][1] * b[1][j] +
a[i][2] * b[2][j] +
a[i][3] * b[3][j]);
}
}
}
void ImageCurves::curvesLutSetup(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->segmentMax+1];
for (v = 0 ; v <= (uint)d->segmentMax ; v++)
{
// To add gamma correction use func(v ^ g) ^ 1/g instead.
val = (float)(d->segmentMax) * curvesLutFunc( d->lut->nchannels, i, v / (float)(d->segmentMax)) + 0.5;
d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, d->segmentMax);
}
}
}
void ImageCurves::curvesLutProcess(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->segmentMax == 255) // 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;
}
}
}
int ImageCurves::getCurveValue(int channel, int bin)
{
if ( d->curves &&
channel>=0 && channel<5 &&
bin>=0 && bin<=d->segmentMax )
return(d->curves->curve[channel][bin]);
return 0;
}
TQPoint ImageCurves::getCurvePoint(int channel, int point)
{
if ( d->curves &&
channel>=0 && channel<5 &&
point>=0 && point<=17 )
return(TQPoint(d->curves->points[channel][point][0],
d->curves->points[channel][point][1]) );
return TQPoint(-1, -1);
}
TQPointArray ImageCurves::getCurvePoints(int channel)
{
TQPointArray array(18);
if ( d->curves &&
channel>=0 && channel<5)
{
for (int j = 0 ; j <= 17 ; j++)
array.setPoint(j, getCurvePoint(channel, j));
}
return array;
}
int ImageCurves::getCurvePointX(int channel, int point)
{
if ( d->curves &&
channel>=0 && channel<5 &&
point>=0 && point<=17 )
return(d->curves->points[channel][point][0]);
return(-1);
}
int ImageCurves::getCurvePointY(int channel, int point)
{
if ( d->curves &&
channel>=0 && channel<5 &&
point>=0 && point<=17 )
return(d->curves->points[channel][point][1]);
return (-1);
}
int ImageCurves::getCurveType(int channel)
{
if ( d->curves &&
channel>=0 && channel<5 )
return ( d->curves->curve_type[channel] );
return (-1);
}
void ImageCurves::setCurveValue(int channel, int bin, int val)
{
if ( d->curves &&
channel>=0 && channel<5 &&
bin>=0 && bin<=d->segmentMax )
{
d->dirty = true;
d->curves->curve[channel][bin] = val;
}
}
void ImageCurves::setCurvePoint(int channel, int point, const TQPoint& val)
{
if ( d->curves &&
channel>=0 && channel<5 &&
point>=0 && point<=17 &&
val.x()>=-1 && val.x()<=d->segmentMax && // x can be egal to -1
val.y()>=0 && val.y()<=d->segmentMax) // if the current point is disable !!!
{
d->dirty = true;
d->curves->points[channel][point][0] = val.x();
d->curves->points[channel][point][1] = val.y();
}
}
void ImageCurves::setCurvePoints(int channel, const TQPointArray& vals)
{
if ( d->curves &&
channel>=0 && channel<5 &&
vals.size() == 18 )
{
d->dirty = true;
for (int j = 0 ; j <= 17 ; j++)
{
setCurvePoint(channel, j, vals.point(j));
}
}
}
void ImageCurves::setCurvePointX(int channel, int point, int x)
{
if ( d->curves &&
channel>=0 && channel<5 &&
point>=0 && point<=17 &&
x>=-1 && x<=d->segmentMax) // x can be egal to -1 if the current point is disable !!!
{
d->dirty = true;
d->curves->points[channel][point][0] = x;
}
}
void ImageCurves::setCurvePointY(int channel, int point, int y)
{
if ( d->curves &&
channel>=0 && channel<5 &&
point>=0 && point<=17 &&
y>=0 && y<=d->segmentMax)
{
d->dirty = true;
d->curves->points[channel][point][1] = y;
}
}
void ImageCurves::setCurveType(int channel, CurveType type)
{
if ( d->curves &&
channel>=0 && channel<5 &&
type>=CURVE_SMOOTH && type<=CURVE_FREE )
d->curves->curve_type[channel] = type;
}
bool ImageCurves::loadCurvesFromGimpCurvesFile(const KURL& fileUrl)
{
// TODO : support KURL !
FILE *file;
int i, j;
int fields;
char buf[50];
int index[5][17];
int value[5][17];
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 Curves File\n") != 0)
return false;
for (i = 0 ; i < 5 ; i++)
{
for (j = 0 ; j < 17 ; j++)
{
fields = fscanf (file, "%d %d ", &index[i][j], &value[i][j]);
if (fields != 2)
{
DWarning() << "Invalid Gimp curves file!" << endl;
fclose(file);
return false;
}
}
}
curvesReset();
for (i = 0 ; i < 5 ; i++)
{
d->curves->curve_type[i] = CURVE_SMOOTH;
for (j = 0 ; j < 17 ; j++)
{
d->curves->points[i][j][0] = ((d->segmentMax == 65535) && (index[i][j] !=-1) ?
index[i][j]*255 : index[i][j]);
d->curves->points[i][j][1] = ((d->segmentMax == 65535) && (value[i][j] !=-1) ?
value[i][j]*255 : value[i][j]);
}
}
for (i = 0 ; i < 5 ; i++)
curvesCalculateCurve(i);
fclose(file);
return true;
}
bool ImageCurves::saveCurvesToGimpCurvesFile(const KURL& fileUrl)
{
// TODO : support KURL !
FILE *file;
int i, j;
int index;
file = fopen(TQFile::encodeName(fileUrl.path()), "w");
if (!file)
return false;
for (i = 0 ; i < 5 ; i++)
{
if (d->curves->curve_type[i] == CURVE_FREE)
{
// Pick representative points from the curve and make them control points.
for (j = 0 ; j <= 8 ; j++)
{
index = CLAMP(j * 32, 0, d->segmentMax);
d->curves->points[i][j * 2][0] = index;
d->curves->points[i][j * 2][1] = d->curves->curve[i][index];
}
}
}
fprintf (file, "# GIMP Curves File\n");
for (i = 0 ; i < 5 ; i++)
{
for (j = 0 ; j < 17 ; j++)
{
fprintf (file, "%d %d ",
((d->segmentMax == 65535) && (d->curves->points[i][j][0]!=-1) ?
d->curves->points[i][j][0]/255 : d->curves->points[i][j][0]),
((d->segmentMax == 65535) && (d->curves->points[i][j][1]!=-1) ?
d->curves->points[i][j][1]/255 : d->curves->points[i][j][1]));
fprintf (file, "\n");
}
}
fflush(file);
fclose(file);
return true;
}
} // NameSpace Digikam