summaryrefslogtreecommitdiffstats
path: root/src/libs/whitebalance/whitebalance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/whitebalance/whitebalance.cpp')
-rw-r--r--src/libs/whitebalance/whitebalance.cpp382
1 files changed, 382 insertions, 0 deletions
diff --git a/src/libs/whitebalance/whitebalance.cpp b/src/libs/whitebalance/whitebalance.cpp
new file mode 100644
index 00000000..9e74708d
--- /dev/null
+++ b/src/libs/whitebalance/whitebalance.cpp
@@ -0,0 +1,382 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2007-16-01
+ * Description : white balance color correction.
+ *
+ * Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ * Copyright (C) 2008 by Guillaume Castagnino <casta at xwing dot info>
+ *
+ * Some parts are inspired from RawPhoto implementation copyrighted
+ * 2004-2005 by Pawel T. Jochym <jochym at ifj edu pl>
+ *
+ * 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 <cmath>
+
+// TQt includes.
+
+#include <tqcolor.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "imagehistogram.h"
+#include "whitebalance.h"
+
+namespace Digikam
+{
+
+class WhiteBalancePriv
+{
+
+public:
+
+ WhiteBalancePriv()
+ {
+ // Obsolete in algorithm since over/under exposure indicators
+ // are implemented directly with preview widget.
+ WBind = false;
+ overExp = false;
+
+ clipSat = true;
+ mr = 1.0;
+ mg = 1.0;
+ mb = 1.0;
+ BP = 0;
+
+ // Neutral color temperature settings.
+ dark = 0.5;
+ black = 0.0;
+ exposition = 0.0;
+ gamma = 1.0;
+ saturation = 1.0;
+ green = 1.0;
+ temperature = 6500.0;
+ }
+
+ bool clipSat;
+ bool overExp;
+ bool WBind;
+
+ double saturation;
+ double temperature;
+ double gamma;
+ double black;
+ double exposition;
+ double dark;
+ double green;
+
+ int BP;
+ int WP;
+
+ uint rgbMax;
+
+ float curve[65536];
+ float mr;
+ float mg;
+ float mb;
+};
+
+WhiteBalance::WhiteBalance(bool sixteenBit)
+{
+ d = new WhiteBalancePriv;
+ d->WP = sixteenBit ? 65536 : 256;
+ d->rgbMax = sixteenBit ? 65536 : 256;
+}
+
+WhiteBalance::~WhiteBalance()
+{
+ delete d;
+}
+
+void WhiteBalance::whiteBalance(uchar *data, int width, int height, bool sixteenBit,
+ double black, double exposition,
+ double temperature, double green, double dark,
+ double gamma, double saturation)
+{
+ d->temperature = temperature;
+ d->green = green;
+ d->dark = dark;
+ d->black = black;
+ d->exposition = exposition;
+ d->gamma = gamma;
+ d->saturation = saturation;
+
+ // Set final lut.
+ setRGBmult();
+ d->mr = d->mb = 1.0;
+ if (d->clipSat) d->mg = 1.0;
+ setLUTv();
+ setRGBmult();
+
+ // Apply White balance adjustments.
+ adjustWhiteBalance(data, width, height, sixteenBit);
+}
+
+void WhiteBalance::autoWBAdjustementFromColor(const TQColor &tc, double &temperature, double &green)
+{
+ // Calculate Temperature and Green component from color picked.
+
+ double tmin, tmax, mBR;
+ float mr, mg, mb;
+
+ DDebug() << "Sums: R:" << tc.red() << " G:" << tc.green() << " B:" << tc.blue() << endl;
+
+ /* This is a dichotomic search based on Blue and Red layers ratio
+ to find the matching temperature
+ Adapted from ufraw (0.12.1) RGB_to_Temperature
+ */
+ tmin = 2000.0;
+ tmax = 12000.0;
+ mBR = (double)tc.blue() / (double)tc.red();
+ green = 1.0;
+ for (temperature = (tmin+tmax)/2; tmax-tmin > 10; temperature = (tmin+tmax)/2)
+ {
+ DDebug() << "Intermediate Temperature (K):" << temperature << endl;
+ setRGBmult(temperature, green, mr, mg, mb);
+ if (mr/mb > mBR)
+ tmax = temperature;
+ else
+ tmin = temperature;
+ }
+ // Calculate the green level to neutralize picture
+ green = (mr / mg) / ((double)tc.green() / (double)tc.red());
+
+ DDebug() << "Temperature (K):" << temperature << endl;
+ DDebug() << "Green component:" << green << endl;
+}
+
+void WhiteBalance::autoExposureAdjustement(uchar* data, int width, int height, bool sb,
+ double &black, double &expo)
+{
+ // Create an histogram of original image.
+
+ ImageHistogram *histogram = new ImageHistogram(data, width, height, sb);
+
+ // Calculate optimal exposition and black level
+
+ int i;
+ double sum, stop;
+ uint rgbMax = sb ? 65536 : 256;
+
+ // Cutoff at 0.5% of the histogram.
+
+ stop = width * height / 200;
+
+ for (i = rgbMax, sum = 0; (i >= 0) && (sum < stop); i--)
+ sum += histogram->getValue(Digikam::ImageHistogram::ValueChannel, i);
+
+ expo = -log((float)(i+1) / rgbMax) / log(2);
+ DDebug() << "White level at:" << i << endl;
+
+ for (i = 1, sum = 0; (i < (int)rgbMax) && (sum < stop); i++)
+ sum += histogram->getValue(Digikam::ImageHistogram::ValueChannel, i);
+
+ black = (double)i / rgbMax;
+ black /= 2;
+
+ DDebug() << "Black:" << black << " Exposition:" << expo << endl;
+
+ delete histogram;
+}
+
+void WhiteBalance::setRGBmult(double &temperature, double &green, float &mr, float &mg, float &mb)
+{
+ float mi;
+ double xD, yD, X, Y, Z;
+
+ if ( temperature > 12000 ) temperature = 12000.0;
+
+ /* Here starts the code picked from ufraw (0.12.1)
+ to convert Temperature + green multiplier to RGB multipliers
+ */
+ /* Convert between Temperature and RGB.
+ * Base on information from http://www.brucelindbloom.com/
+ * The fit for D-illuminant between 4000K and 12000K are from CIE
+ * The generalization to 2000K < T < 4000K and the blackbody fits
+ * are my own and should be taken with a grain of salt.
+ */
+ const double XYZ_to_RGB[3][3] = {
+ { 3.24071, -0.969258, 0.0556352 },
+ {-1.53726, 1.87599, -0.203996 },
+ {-0.498571, 0.0415557, 1.05707 } };
+ // Fit for CIE Daylight illuminant
+ if (temperature <= 4000)
+ {
+ xD = 0.27475e9/(temperature*temperature*temperature)
+ - 0.98598e6/(temperature*temperature)
+ + 1.17444e3/temperature + 0.145986;
+ }
+ else if (temperature <= 7000)
+ {
+ xD = -4.6070e9/(temperature*temperature*temperature)
+ + 2.9678e6/(temperature*temperature)
+ + 0.09911e3/temperature + 0.244063;
+ }
+ else
+ {
+ xD = -2.0064e9/(temperature*temperature*temperature)
+ + 1.9018e6/(temperature*temperature)
+ + 0.24748e3/temperature + 0.237040;
+ }
+ yD = -3*xD*xD + 2.87*xD - 0.275;
+
+ X = xD/yD;
+ Y = 1;
+ Z = (1-xD-yD)/yD;
+ mr = X*XYZ_to_RGB[0][0] + Y*XYZ_to_RGB[1][0] + Z*XYZ_to_RGB[2][0];
+ mg = X*XYZ_to_RGB[0][1] + Y*XYZ_to_RGB[1][1] + Z*XYZ_to_RGB[2][1];
+ mb = X*XYZ_to_RGB[0][2] + Y*XYZ_to_RGB[1][2] + Z*XYZ_to_RGB[2][2];
+ /* End of the code picked to ufraw
+ */
+
+ // Apply green multiplier
+ mg = mg / green;
+
+ mr = 1.0 / mr;
+ mg = 1.0 / mg;
+ mb = 1.0 / mb;
+
+ // Normalize to at least 1.0, so we are not dimming colors only bumping.
+ mi = TQMIN(mr, TQMIN(mg, mb));
+ mr /= mi;
+ mg /= mi;
+ mb /= mi;
+}
+
+void WhiteBalance::setRGBmult()
+{
+ setRGBmult(d->temperature, d->green, d->mr, d->mg, d->mb);
+}
+
+void WhiteBalance::setLUTv()
+{
+ double b = d->mg * pow(2, d->exposition);
+ d->BP = (uint)(d->rgbMax * d->black);
+ d->WP = (uint)(d->rgbMax / b);
+
+ if (d->WP - d->BP < 1) d->WP = d->BP + 1;
+
+ DDebug() << "T(K): " << d->temperature
+ << " => R:" << d->mr
+ << " G:" << d->mg
+ << " B:" << d->mb
+ << " BP:" << d->BP
+ << " WP:" << d->WP
+ << endl;
+
+ d->curve[0] = 0;
+
+ // We will try to reproduce the same Gamma effect here than BCG tool.
+ double gamma;
+ if (d->gamma >= 1.0)
+ gamma = 0.335*(2.0-d->gamma) + 0.665;
+ else
+ gamma = 1.8*(2.0-d->gamma) - 0.8;
+
+ for (int i = 1; i < (int)d->rgbMax; i++)
+ {
+ float x = (float)(i - d->BP)/(d->WP - d->BP);
+ d->curve[i] = (i < d->BP) ? 0 : (d->rgbMax-1) * pow((double)x, gamma);
+ d->curve[i] *= (1 - d->dark * exp(-x * x / 0.002));
+ d->curve[i] /= (float)i;
+ }
+}
+
+void WhiteBalance::adjustWhiteBalance(uchar *data, int width, int height, bool sixteenBit)
+{
+ uint i, j;
+
+ if (!sixteenBit) // 8 bits image.
+ {
+ uchar red, green, blue;
+ uchar *ptr = data;
+
+ for (j = 0 ; j < (uint)(width*height) ; j++)
+ {
+ int v, rv[3];
+
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ rv[0] = (int)(blue * d->mb);
+ rv[1] = (int)(green * d->mg);
+ rv[2] = (int)(red * d->mr);
+ v = TQMAX(rv[0], rv[1]);
+ v = TQMAX(v, rv[2]);
+
+ if (d->clipSat) v = TQMIN(v, (int)d->rgbMax-1);
+ i = v;
+
+ ptr[0] = (uchar)pixelColor(rv[0], i, v);
+ ptr[1] = (uchar)pixelColor(rv[1], i, v);
+ ptr[2] = (uchar)pixelColor(rv[2], i, v);
+ ptr += 4;
+ }
+ }
+ else // 16 bits image.
+ {
+ unsigned short red, green, blue;
+ unsigned short *ptr = (unsigned short *)data;
+
+ for (j = 0 ; j < (uint)(width*height) ; j++)
+ {
+ int v, rv[3];
+
+ blue = ptr[0];
+ green = ptr[1];
+ red = ptr[2];
+
+ rv[0] = (int)(blue * d->mb);
+ rv[1] = (int)(green * d->mg);
+ rv[2] = (int)(red * d->mr);
+ v = TQMAX(rv[0], rv[1]);
+ v = TQMAX(v, rv[2]);
+
+ if (d->clipSat) v = TQMIN(v, (int)d->rgbMax-1);
+ i = v;
+
+ ptr[0] = pixelColor(rv[0], i, v);
+ ptr[1] = pixelColor(rv[1], i, v);
+ ptr[2] = pixelColor(rv[2], i, v);
+ ptr += 4;
+ }
+ }
+}
+
+unsigned short WhiteBalance::pixelColor(int colorMult, int index, int value)
+{
+ int r = (d->clipSat && colorMult > (int)d->rgbMax) ? d->rgbMax : colorMult;
+
+ if (value > d->BP && d->overExp && value > d->WP)
+ {
+ if (d->WBind)
+ r = (colorMult > d->WP) ? 0 : r;
+ else
+ r = 0;
+ }
+
+ return( (unsigned short)CLAMP((int)((index - d->saturation*(index - r)) * d->curve[index]),
+ 0, (int)(d->rgbMax-1)) );
+}
+
+} // NameSpace Digikam