1
/* ============================================================
3
* This file is a part of digiKam project
4
* http://www.digikam.org
7
* Description : Red eyes correction tool for image editor
9
* Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
10
* Copyright (C) 2004-2010 by Gilles Caulier <caulier dot gilles at gmail dot com>
12
* This program is free software; you can redistribute it
13
* and/or modify it under the terms of the GNU General
14
* Public License as published by the Free Software Foundation;
15
* either version 2, or (at your option)
18
* This program is distributed in the hope that it will be useful,
19
* but WITHOUT ANY WARRANTY; without even the implied warranty of
20
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
* GNU General Public License for more details.
23
* ============================================================ */
25
#include "redeyetool.moc"
31
#include <QGridLayout>
33
#include <QHBoxLayout>
36
#include <QPushButton>
37
#include <QToolButton>
41
#include <kapplication.h>
42
#include <kcolordialog.h>
43
#include <kcolorvalueselector.h>
45
#include <kconfiggroup.h>
48
#include <khuesaturationselect.h>
50
#include <kiconloader.h>
52
#include <kstandarddirs.h>
57
#include <libkdcraw/rnuminput.h>
61
#include "colorgradientwidget.h"
63
#include "blurfilter.h"
64
#include "editortoolsettings.h"
65
#include "histogramwidget.h"
66
#include "histogrambox.h"
67
#include "imageiface.h"
68
#include "imageguidewidget.h"
70
using namespace KDcrawIface;
72
namespace DigikamEnhanceImagePlugin
80
configGroupName("redeye Tool"),
81
configHistogramChannelEntry("Histogram Channel"),
82
configHistogramScaleEntry("Histogram Scale"),
83
configRedThresholdEntry("RedThreshold"),
84
configSmoothLevelEntry("SmoothLevel"),
85
configHueColoringTintEntry("HueColoringTint"),
86
configSatColoringTintEntry("SatColoringTint"),
87
configValColoringTintEntry("ValColoringTint"),
88
configTintLevelEntry("TintLevel"),
90
destinationPreviewData(0),
102
const QString configGroupName;
103
const QString configHistogramChannelEntry;
104
const QString configHistogramScaleEntry;
105
const QString configRedThresholdEntry;
106
const QString configSmoothLevelEntry;
107
const QString configHueColoringTintEntry;
108
const QString configSatColoringTintEntry;
109
const QString configValColoringTintEntry;
110
const QString configTintLevelEntry;
112
uchar* destinationPreviewData;
116
QLabel* thresholdLabel;
119
KHueSaturationSelector* HSSelector;
120
KColorValueSelector* VSelector;
122
RIntNumInput* tintLevel;
123
RIntNumInput* redThreshold;
124
RIntNumInput* smoothLevel;
126
ImageGuideWidget* previewWidget;
127
EditorToolSettings* gboxSettings;
130
RedEyeTool::RedEyeTool(QObject* parent)
131
: EditorTool(parent),
132
d(new RedEyeToolPriv)
134
setObjectName("redeye");
135
setToolName(i18n("Red Eye"));
136
setToolIcon(SmallIcon("redeyes"));
137
setToolHelp("redeyecorrectiontool.anchor");
139
d->destinationPreviewData = 0;
141
d->previewWidget = new ImageGuideWidget(0, true, ImageGuideWidget::PickColorMode, Qt::red, 1, false, true);
142
d->previewWidget->setToolTip(i18n("Here you can see the image selection preview with "
143
"red eye reduction applied."));
144
setToolView(d->previewWidget);
145
setPreviewModeMask(PreviewToolBar::AllPreviewModes);
147
// -------------------------------------------------------------
149
d->gboxSettings = new EditorToolSettings;
150
d->gboxSettings->setTools(EditorToolSettings::Histogram);
152
// -------------------------------------------------------------
154
d->thresholdLabel = new QLabel(i18n("Sensitivity:"));
155
d->redThreshold = new RIntNumInput();
156
d->redThreshold->setRange(10, 90, 1);
157
d->redThreshold->setSliderEnabled(true);
158
d->redThreshold->setDefaultValue(20);
159
d->redThreshold->setWhatsThis(i18n("<p>Control the red pixel selection threshold.</p>"
160
"<p>Low values will select more red pixels "
161
"(aggressive correction), high values will select fewer (mild correction). "
162
"Use a low value if an eye has been selected exactly. "
163
"Use a high value if other parts of the face have been selected too.</p>"));
165
d->smoothLabel = new QLabel(i18nc("Smoothness when blurring border of changed pixels", "Smooth:"));
166
d->smoothLevel = new RIntNumInput();
167
d->smoothLevel->setRange(0, 5, 1);
168
d->smoothLevel->setSliderEnabled(true);
169
d->smoothLevel->setDefaultValue(1);
170
d->smoothLevel->setWhatsThis(i18n("Sets the smoothness value when blurring the border "
171
"of the changed pixels. "
172
"This leads to a more naturally looking pupil."));
174
QLabel *label3 = new QLabel(i18n("Coloring Tint:"));
176
d->HSSelector = new KHueSaturationSelector();
177
d->HSSelector->setWhatsThis(i18n("Sets a custom color when re-colorizing the eyes."));
178
d->HSSelector->setMinimumSize(200, 142);
179
d->HSSelector->setChooserMode(ChooserValue);
180
d->HSSelector->setColorValue(255);
182
d->VSelector = new KColorValueSelector();
183
d->VSelector->setChooserMode(ChooserValue);
184
d->VSelector->setMinimumSize(26, 142);
185
d->VSelector->setIndent(false);
187
QLabel *label4 = new QLabel(i18n("Tint Level:"));
188
d->tintLevel = new RIntNumInput();
189
d->tintLevel->setRange(1, 200, 1);
190
d->tintLevel->setSliderEnabled(true);
191
d->tintLevel->setDefaultValue(128);
192
d->tintLevel->setWhatsThis(i18n("Set the tint level to adjust the luminosity of "
193
"the new color of the pupil."));
195
// -------------------------------------------------------------
197
QGridLayout* mainLayout = new QGridLayout();
198
mainLayout->addWidget(d->thresholdLabel, 0, 0, 1, 5);
199
mainLayout->addWidget(d->redThreshold, 1, 0, 1, 5);
200
mainLayout->addWidget(d->smoothLabel, 2, 0, 1, 5);
201
mainLayout->addWidget(d->smoothLevel, 3, 0, 1, 5);
202
mainLayout->addWidget(label3, 4, 0, 1, 5);
203
mainLayout->addWidget(d->HSSelector, 5, 0, 1, 4);
204
mainLayout->addWidget(d->VSelector, 5, 4, 1, 1);
205
mainLayout->addWidget(label4, 6, 0, 1, 5);
206
mainLayout->addWidget(d->tintLevel, 7, 0, 1, 5);
207
mainLayout->setRowStretch(8, 10);
208
mainLayout->setColumnStretch(3, 10);
209
mainLayout->setMargin(d->gboxSettings->spacingHint());
210
mainLayout->setSpacing(d->gboxSettings->spacingHint());
211
d->gboxSettings->plainPage()->setLayout(mainLayout);
213
// -------------------------------------------------------------
215
setToolSettings(d->gboxSettings);
218
// -------------------------------------------------------------
220
connect(d->previewWidget, SIGNAL(spotPositionChangedFromTarget(const Digikam::DColor&, const QPoint&)),
221
this, SLOT(slotColorSelectedFromTarget(const Digikam::DColor&)));
223
connect(d->previewWidget, SIGNAL(signalResized()),
224
this, SLOT(slotEffect()));
226
connect(d->redThreshold, SIGNAL(valueChanged(int)),
227
this, SLOT(slotTimer()));
229
connect(d->smoothLevel, SIGNAL(valueChanged(int)),
230
this, SLOT(slotTimer()));
232
connect(d->HSSelector, SIGNAL(valueChanged(int, int)),
233
this, SLOT(slotHSChanged(int, int)));
235
connect(d->VSelector, SIGNAL(valueChanged(int)),
236
this, SLOT(slotVChanged(int)));
238
connect(d->tintLevel, SIGNAL(valueChanged(int)),
239
this, SLOT(slotTimer()));
242
RedEyeTool::~RedEyeTool()
244
if (d->destinationPreviewData)
245
delete [] d->destinationPreviewData;
250
void RedEyeTool::slotHSChanged(int h, int s)
254
int val = d->selColor.value();
256
color.setHsv(h, s, val);
260
void RedEyeTool::slotVChanged(int v)
264
int hue = d->selColor.hue();
265
int sat = d->selColor.saturation();
267
color.setHsv(hue, sat, v);
271
void RedEyeTool::setColor(QColor c)
278
d->HSSelector->setValues(c.hue(), c.saturation());
279
d->VSelector->setValue(c.value());
282
d->HSSelector->blockSignals(true);
283
d->HSSelector->setHue(c.hue());
284
d->HSSelector->setSaturation(c.saturation());
285
d->HSSelector->setColorValue(c.value());
286
d->HSSelector->updateContents();
287
d->HSSelector->blockSignals(false);
288
d->HSSelector->repaint();
290
d->VSelector->blockSignals(true);
291
d->VSelector->setHue(c.hue());
292
d->VSelector->setSaturation(c.saturation());
293
d->VSelector->setColorValue(c.value());
294
d->VSelector->updateContents();
295
d->VSelector->blockSignals(false);
296
d->VSelector->repaint();
302
void RedEyeTool::slotColorSelectedFromTarget(const DColor& color)
304
d->gboxSettings->histogramBox()->histogram()->setHistogramGuideByColor(color);
307
void RedEyeTool::readSettings()
309
KSharedConfig::Ptr config = KGlobal::config();
310
KConfigGroup group = config->group(d->configGroupName);
312
d->gboxSettings->histogramBox()->setChannel((ChannelType)group.readEntry(d->configHistogramChannelEntry,
313
(int)LuminosityChannel));
314
d->gboxSettings->histogramBox()->setScale((HistogramScale)group.readEntry(d->configHistogramScaleEntry,
315
(int)LogScaleHistogram));
317
d->redThreshold->setValue(group.readEntry(d->configRedThresholdEntry, d->redThreshold->defaultValue()));
318
d->smoothLevel->setValue(group.readEntry(d->configSmoothLevelEntry, d->smoothLevel->defaultValue()));
319
d->HSSelector->setHue(group.readEntry(d->configHueColoringTintEntry, 0));
320
d->HSSelector->setSaturation(group.readEntry(d->configSatColoringTintEntry, 128));
321
d->VSelector->setValue(group.readEntry(d->configValColoringTintEntry, 255));
322
d->tintLevel->setValue(group.readEntry(d->configTintLevelEntry, d->tintLevel->defaultValue()));
325
col.setHsv(d->HSSelector->hue(),
326
d->HSSelector->saturation(),
327
d->VSelector->value());
331
void RedEyeTool::writeSettings()
333
KSharedConfig::Ptr config = KGlobal::config();
334
KConfigGroup group = config->group(d->configGroupName);
335
group.writeEntry(d->configHistogramChannelEntry, (int)d->gboxSettings->histogramBox()->channel());
336
group.writeEntry(d->configHistogramScaleEntry, (int)d->gboxSettings->histogramBox()->scale());
337
group.writeEntry(d->configRedThresholdEntry, d->redThreshold->value());
338
group.writeEntry(d->configSmoothLevelEntry, d->smoothLevel->value());
339
group.writeEntry(d->configHueColoringTintEntry, d->HSSelector->hue());
340
group.writeEntry(d->configSatColoringTintEntry, d->HSSelector->saturation());
341
group.writeEntry(d->configValColoringTintEntry, d->VSelector->value());
342
group.writeEntry(d->configTintLevelEntry, d->tintLevel->value());
347
void RedEyeTool::slotResetSettings()
349
d->redThreshold->blockSignals(true);
350
d->HSSelector->blockSignals(true);
351
d->VSelector->blockSignals(true);
352
d->tintLevel->blockSignals(true);
354
d->redThreshold->slotReset();
355
d->smoothLevel->slotReset();
356
d->tintLevel->slotReset();
358
// Black color by default
363
d->redThreshold->blockSignals(false);
364
d->HSSelector->blockSignals(false);
365
d->VSelector->blockSignals(false);
366
d->tintLevel->blockSignals(false);
371
void RedEyeTool::slotEffect()
373
kapp->setOverrideCursor( Qt::WaitCursor );
375
d->gboxSettings->histogramBox()->histogram()->stopHistogramComputation();
377
if (d->destinationPreviewData)
378
delete [] d->destinationPreviewData;
380
// Here, we need to use the real selection image data because we will apply
381
// a Gaussian blur filter on pixels and we cannot use directly the preview scaled image
382
// else the blur radius will not give the same result between preview and final rendering.
383
ImageIface* iface = d->previewWidget->imageIface();
384
d->destinationPreviewData = iface->getImageSelection();
385
int w = iface->selectedWidth();
386
int h = iface->selectedHeight();
387
bool sb = iface->originalSixteenBit();
388
bool a = iface->originalHasAlpha();
389
DImg selection(w, h, sb, a, d->destinationPreviewData);
391
redEyeFilter(selection);
393
DImg preview = selection.smoothScale(iface->previewWidth(), iface->previewHeight());
395
iface->putPreviewImage(preview.bits());
396
d->previewWidget->updatePreview();
400
memcpy(d->destinationPreviewData, selection.bits(), selection.numBytes());
401
d->gboxSettings->histogramBox()->histogram()->updateData(d->destinationPreviewData, w, h, sb, 0, 0, 0, false);
403
kapp->restoreOverrideCursor();
406
void RedEyeTool::finalRendering()
408
kapp->setOverrideCursor( Qt::WaitCursor );
410
ImageIface* iface = d->previewWidget->imageIface();
411
uchar *data = iface->getImageSelection();
412
int w = iface->selectedWidth();
413
int h = iface->selectedHeight();
414
bool sixteenBit = iface->originalSixteenBit();
415
bool hasAlpha = iface->originalHasAlpha();
416
DImg selection(w, h, sixteenBit, hasAlpha, data);
419
redEyeFilter(selection);
421
iface->putImageSelection(i18n("Red Eyes Correction"), selection.bits());
423
kapp->restoreOverrideCursor();
426
void RedEyeTool::redEyeFilter(DImg& selection)
428
DImg mask(selection.width(), selection.height(), selection.sixteenBit(), true,
429
selection.bits(), true);
431
selection = mask.copy();
432
float redThreshold = d->redThreshold->value()/10.0f;
433
int hue = d->VSelector->hue();
434
int sat = d->VSelector->saturation();
435
int val = d->VSelector->value();
436
QColor coloring = QColor::fromHsv(hue, sat, val);
445
channel red_chan, green_chan, blue_chan;
447
red_chan.red_gain = 0.1f;
448
red_chan.green_gain = 0.6f;
449
red_chan.blue_gain = 0.3f;
451
green_chan.red_gain = 0.0f;
452
green_chan.green_gain = 1.0f;
453
green_chan.blue_gain = 0.0f;
455
blue_chan.red_gain = 0.0f;
456
blue_chan.green_gain = 0.0f;
457
blue_chan.blue_gain = 1.0f;
459
float red_norm, green_norm, blue_norm;
460
int level = 201 - d->tintLevel->value();
462
red_norm = 1.0f / (red_chan.red_gain + red_chan.green_gain + red_chan.blue_gain);
463
green_norm = 1.0f / (green_chan.red_gain + green_chan.green_gain + green_chan.blue_gain);
464
blue_norm = 1.0f / (blue_chan.red_gain + blue_chan.green_gain + blue_chan.blue_gain);
466
red_norm *= coloring.red() / level;
467
green_norm *= coloring.green() / level;
468
blue_norm *= coloring.blue() / level;
470
// Perform a red color pixels detection in selection image and create a correction mask using an alpha channel.
472
if (!selection.sixteenBit()) // 8 bits image.
474
uchar* ptr = selection.bits();
475
uchar* mptr = mask.bits();
476
uchar r, g, b, r1, g1, b1;
478
for (uint i = 0 ; i < selection.width() * selection.height() ; ++i)
485
if (r >= ( redThreshold * g))
487
r1 = qMin(255, (int)(red_norm * (red_chan.red_gain * r +
488
red_chan.green_gain * g +
489
red_chan.blue_gain * b)));
491
g1 = qMin(255, (int)(green_norm * (green_chan.red_gain * r +
492
green_chan.green_gain * g +
493
green_chan.blue_gain * b)));
495
b1 = qMin(255, (int)(blue_norm * (blue_chan.red_gain * r +
496
blue_chan.green_gain * g +
497
blue_chan.blue_gain * b)));
502
mptr[3] = qMin( (int)((r-g) / 150.0 * 255.0), 255);
509
else // 16 bits image.
511
unsigned short* ptr = (unsigned short*)selection.bits();
512
unsigned short* mptr = (unsigned short*)mask.bits();
513
unsigned short r, g, b, r1, g1, b1;
515
for (uint i = 0 ; i < selection.width() * selection.height() ; ++i)
522
if (r >= ( redThreshold * g))
524
r1 = qMin(65535, (int)(red_norm * (red_chan.red_gain * r +
525
red_chan.green_gain * g +
526
red_chan.blue_gain * b)));
528
g1 = qMin(65535, (int)(green_norm * (green_chan.red_gain * r +
529
green_chan.green_gain * g +
530
green_chan.blue_gain * b)));
532
b1 = qMin(65535, (int)(blue_norm * (blue_chan.red_gain * r +
533
blue_chan.green_gain * g +
534
blue_chan.blue_gain * b)));
539
mptr[3] = qMin( (int)((r-g) / 38400.0 * 65535.0), 65535);
547
// Now, we will blur only the transparency pixels from the mask.
549
DImg mask2 = mask.copy();
550
BlurFilter blur(&mask2, 0L, d->smoothLevel->value());
551
blur.startFilterDirectly();
552
mask2.putImageData(blur.getTargetImage().bits());
554
if (!selection.sixteenBit()) // 8 bits image.
556
uchar* mptr = mask.bits();
557
uchar* mptr2 = mask2.bits();
559
for (uint i = 0 ; i < mask2.width() * mask2.height() ; ++i)
573
else // 16 bits image.
575
unsigned short* mptr = (unsigned short*)mask.bits();
576
unsigned short* mptr2 = (unsigned short*)mask2.bits();
578
for (uint i = 0 ; i < mask2.width() * mask2.height() ; ++i)
580
if (mptr2[3] < 65535)
593
// - Perform pixels blending using alpha channel between the mask and the selection.
595
DColorComposer *composer = DColorComposer::getComposer(DColorComposer::PorterDuffSrcOver);
597
// NOTE: 'mask' is the Source image, 'selection' is the Destination image.
599
selection.bitBlendImage(composer, &mask,
600
0, 0, mask.width(), mask.height(),
606
} // namespace DigikamEnhanceImagePlugin