1
/* ============================================================
3
* This file is a part of digiKam project
4
* http://www.digikam.org
7
* Description : a digiKam image plugin for to apply a color
10
* Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
11
* Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
13
* This program is free software; you can redistribute it
14
* and/or modify it under the terms of the GNU General
15
* Public License as published by the Free Software Foundation;
16
* either version 2, or (at your option)
19
* This program is distributed in the hope that it will be useful,
20
* but WITHOUT ANY WARRANTY; without even the implied warranty of
21
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
* GNU General Public License for more details.
24
* ============================================================ */
28
#include <qvgroupbox.h>
29
#include <qhgroupbox.h>
30
#include <qhbuttongroup.h>
31
#include <qcombobox.h>
33
#include <qpushbutton.h>
34
#include <qwhatsthis.h>
43
#include <knuminput.h>
46
#include <kaboutdata.h>
47
#include <khelpmenu.h>
48
#include <kiconloader.h>
49
#include <kapplication.h>
50
#include <kpopupmenu.h>
51
#include <kstandarddirs.h>
58
#include "dimgimagefilters.h"
59
#include "imageiface.h"
60
#include "imagewidget.h"
61
#include "imagecurves.h"
62
#include "imagehistogram.h"
63
#include "histogramwidget.h"
64
#include "colorgradientwidget.h"
65
#include "imageeffect_colorfx.h"
66
#include "imageeffect_colorfx.moc"
68
namespace DigikamColorFXImagesPlugin
71
ImageEffect_ColorFX::ImageEffect_ColorFX(QWidget* parent)
72
: Digikam::ImageDlgBase(parent,
73
i18n("Apply Color Special Effects to Photograph"),
74
"coloreffect", false, false)
76
m_destinationPreviewData = 0;
78
// About data and help button.
80
KAboutData *about = new KAboutData("digikam",
81
I18N_NOOP("Color Effects"),
83
I18N_NOOP("A digiKam plugin to apply special color effects to an image."),
84
KAboutData::License_GPL,
85
"(c) 2004-2005, Renchi Raju\n(c) 2006-2008, Gilles Caulier",
87
"http://www.digikam.org");
89
about->addAuthor("Renchi Raju", I18N_NOOP("Original Author"),
90
"renchi@pooh.tam.uiuc.edu");
92
about->addAuthor("Caulier Gilles", I18N_NOOP("Maintainer"),
93
"caulier dot gilles at gmail dot com");
97
// -------------------------------------------------------------
99
m_previewWidget = new Digikam::ImageWidget("coloreffect Tool Dialog", plainPage(),
100
i18n("<p>This is the color effect preview"));
102
setPreviewAreaWidget(m_previewWidget);
104
// -------------------------------------------------------------
106
QWidget *gboxSettings = new QWidget(plainPage());
107
QGridLayout* gridSettings = new QGridLayout( gboxSettings, 9, 4, spacingHint());
109
QLabel *label1 = new QLabel(i18n("Channel:"), gboxSettings);
110
label1->setAlignment ( Qt::AlignRight | Qt::AlignVCenter );
111
m_channelCB = new QComboBox( false, gboxSettings );
112
m_channelCB->insertItem( i18n("Luminosity") );
113
m_channelCB->insertItem( i18n("Red") );
114
m_channelCB->insertItem( i18n("Green") );
115
m_channelCB->insertItem( i18n("Blue") );
116
QWhatsThis::add( m_channelCB, i18n("<p>Select the histogram channel to display here:<p>"
117
"<b>Luminosity</b>: display the image's luminosity values.<p>"
118
"<b>Red</b>: display the red image-channel values.<p>"
119
"<b>Green</b>: display the green image-channel values.<p>"
120
"<b>Blue</b>: display the blue image-channel values.<p>"));
122
m_scaleBG = new QHButtonGroup(gboxSettings);
123
m_scaleBG->setExclusive(true);
124
m_scaleBG->setFrameShape(QFrame::NoFrame);
125
m_scaleBG->setInsideMargin( 0 );
126
QWhatsThis::add( m_scaleBG, i18n("<p>Select the histogram scale here.<p>"
127
"If the image's maximal counts are small, you can use the linear scale.<p>"
128
"Logarithmic scale can be used when the maximal counts are big; "
129
"if it is used, all values (small and large) will be visible on the graph."));
131
QPushButton *linHistoButton = new QPushButton( m_scaleBG );
132
QToolTip::add( linHistoButton, i18n( "<p>Linear" ) );
133
m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram);
134
KGlobal::dirs()->addResourceType("histogram-lin", KGlobal::dirs()->kde_default("data") + "digikam/data");
135
QString directory = KGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png");
136
linHistoButton->setPixmap( QPixmap( directory + "histogram-lin.png" ) );
137
linHistoButton->setToggleButton(true);
139
QPushButton *logHistoButton = new QPushButton( m_scaleBG );
140
QToolTip::add( logHistoButton, i18n( "<p>Logarithmic" ) );
141
m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram);
142
KGlobal::dirs()->addResourceType("histogram-log", KGlobal::dirs()->kde_default("data") + "digikam/data");
143
directory = KGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png");
144
logHistoButton->setPixmap( QPixmap( directory + "histogram-log.png" ) );
145
logHistoButton->setToggleButton(true);
147
QHBoxLayout* l1 = new QHBoxLayout();
148
l1->addWidget(label1);
149
l1->addWidget(m_channelCB);
151
l1->addWidget(m_scaleBG);
153
gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4);
155
// -------------------------------------------------------------
157
QVBox *histoBox = new QVBox(gboxSettings);
158
m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true);
159
QWhatsThis::add( m_histogramWidget, i18n("<p>Here you can see the target preview image histogram drawing "
160
"of the selected image channel. This one is re-computed at any "
161
"settings changes."));
162
QLabel *space = new QLabel(histoBox);
163
space->setFixedHeight(1);
164
m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox );
165
m_hGradient->setColors( QColor( "black" ), QColor( "white" ) );
167
gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4);
169
// -------------------------------------------------------------
171
m_effectTypeLabel = new QLabel(i18n("Type:"), gboxSettings);
173
m_effectType = new QComboBox( false, gboxSettings );
174
m_effectType->insertItem( i18n("Solarize") );
175
m_effectType->insertItem( i18n("Vivid") );
176
m_effectType->insertItem( i18n("Neon") );
177
m_effectType->insertItem( i18n("Find Edges") );
178
QWhatsThis::add( m_effectType, i18n("<p>Select the effect type to apply to the image here.<p>"
179
"<b>Solarize</b>: simulates solarization of photograph.<p>"
180
"<b>Vivid</b>: simulates the Velvia(tm) slide film colors.<p>"
181
"<b>Neon</b>: coloring the edges in a photograph to "
182
"reproduce a fluorescent light effect.<p>"
183
"<b>Find Edges</b>: detects the edges in a photograph "
184
"and their strength."
186
gridSettings->addMultiCellWidget(m_effectTypeLabel, 3, 3, 0, 4);
187
gridSettings->addMultiCellWidget(m_effectType, 4, 4, 0, 4);
189
m_levelLabel = new QLabel(i18n("Level:"), gboxSettings);
190
m_levelInput = new KIntNumInput(gboxSettings);
191
m_levelInput->setRange(0, 100, 1, true);
192
QWhatsThis::add( m_levelInput, i18n("<p>Set here the level of the effect."));
194
gridSettings->addMultiCellWidget(m_levelLabel, 5, 5, 0, 4);
195
gridSettings->addMultiCellWidget(m_levelInput, 6, 6, 0, 4);
197
m_iterationLabel = new QLabel(i18n("Iteration:"), gboxSettings);
198
m_iterationInput = new KIntNumInput(gboxSettings);
199
m_iterationInput->setRange(0, 100, 1, true);
200
QWhatsThis::add( m_iterationInput, i18n("<p>This value controls the number of iterations "
201
"to use with the Neon and Find Edges effects."));
203
gridSettings->addMultiCellWidget(m_iterationLabel, 7, 7, 0, 4);
204
gridSettings->addMultiCellWidget(m_iterationInput, 8, 8, 0, 4);
206
gridSettings->setRowStretch(9, 10);
207
setUserAreaWidget(gboxSettings);
209
// -------------------------------------------------------------
211
connect(m_channelCB, SIGNAL(activated(int)),
212
this, SLOT(slotChannelChanged(int)));
214
connect(m_scaleBG, SIGNAL(released(int)),
215
this, SLOT(slotScaleChanged(int)));
217
connect(m_previewWidget, SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const QPoint & )),
218
this, SLOT(slotColorSelectedFromTarget( const Digikam::DColor & )));
220
connect(m_levelInput, SIGNAL(valueChanged(int)),
221
this, SLOT(slotTimer()));
223
connect(m_iterationInput, SIGNAL(valueChanged(int)),
224
this, SLOT(slotTimer()));
226
connect(m_previewWidget, SIGNAL(signalResized()),
227
this, SLOT(slotEffect()));
229
connect(m_effectType, SIGNAL(activated(int)),
230
this, SLOT(slotEffectTypeChanged(int)));
233
ImageEffect_ColorFX::~ImageEffect_ColorFX()
235
m_histogramWidget->stopHistogramComputation();
237
if (m_destinationPreviewData)
238
delete [] m_destinationPreviewData;
240
delete m_previewWidget;
243
void ImageEffect_ColorFX::readUserSettings()
245
KConfig* config = kapp->config();
246
config->setGroup("coloreffect Tool Dialog");
247
m_effectType->setCurrentItem(config->readNumEntry("EffectType", ColorFX));
248
m_levelInput->setValue(config->readNumEntry("LevelAjustment", 0));
249
m_iterationInput->setValue(config->readNumEntry("IterationAjustment", 3));
250
slotEffectTypeChanged(m_effectType->currentItem()); //check for enable/disable of iteration
253
void ImageEffect_ColorFX::writeUserSettings()
255
KConfig* config = kapp->config();
256
config->setGroup("coloreffect Tool Dialog");
257
config->writeEntry("EffectType", m_effectType->currentItem());
258
config->writeEntry("LevelAjustment", m_levelInput->value());
259
config->writeEntry("IterationAjustment", m_iterationInput->value());
263
void ImageEffect_ColorFX::resetValues()
265
m_levelInput->setValue(0);
268
void ImageEffect_ColorFX::slotChannelChanged(int channel)
272
case LuminosityChannel:
273
m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram;
274
m_hGradient->setColors( QColor( "black" ), QColor( "white" ) );
278
m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram;
279
m_hGradient->setColors( QColor( "black" ), QColor( "red" ) );
283
m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram;
284
m_hGradient->setColors( QColor( "black" ), QColor( "green" ) );
288
m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram;
289
m_hGradient->setColors( QColor( "black" ), QColor( "blue" ) );
293
m_histogramWidget->repaint(false);
296
void ImageEffect_ColorFX::slotScaleChanged(int scale)
298
m_histogramWidget->m_scaleType = scale;
299
m_histogramWidget->repaint(false);
302
void ImageEffect_ColorFX::slotColorSelectedFromTarget( const Digikam::DColor &color )
304
m_histogramWidget->setHistogramGuideByColor(color);
307
void ImageEffect_ColorFX::slotEffectTypeChanged(int type)
309
m_levelInput->setEnabled(true);
310
m_levelLabel->setEnabled(true);
312
m_levelInput->blockSignals(true);
313
m_iterationInput->blockSignals(true);
314
m_levelInput->setRange(0, 100, 1, true);
315
m_levelInput->setValue(25);
320
m_levelInput->setRange(0, 100, 1, true);
321
m_levelInput->setValue(0);
322
m_iterationInput->setEnabled(false);
323
m_iterationLabel->setEnabled(false);
327
m_levelInput->setRange(0, 50, 1, true);
328
m_levelInput->setValue(5);
329
m_iterationInput->setEnabled(false);
330
m_iterationLabel->setEnabled(false);
335
m_levelInput->setRange(0, 5, 1, true);
336
m_levelInput->setValue(3);
337
m_iterationInput->setEnabled(true);
338
m_iterationLabel->setEnabled(true);
339
m_iterationInput->setRange(0, 5, 1, true);
340
m_iterationInput->setValue(2);
344
m_levelInput->blockSignals(false);
345
m_iterationInput->blockSignals(false);
350
void ImageEffect_ColorFX::slotEffect()
352
kapp->setOverrideCursor( KCursor::waitCursor() );
354
m_histogramWidget->stopHistogramComputation();
356
if (m_destinationPreviewData)
357
delete [] m_destinationPreviewData;
359
Digikam::ImageIface* iface = m_previewWidget->imageIface();
360
uchar *m_destinationPreviewData = iface->getPreviewImage();
361
int w = iface->previewWidth();
362
int h = iface->previewHeight();
363
bool sb = iface->previewSixteenBit();
365
colorEffect(m_destinationPreviewData, w, h, sb);
367
iface->putPreviewImage(m_destinationPreviewData);
368
m_previewWidget->updatePreview();
372
m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false);
374
kapp->restoreOverrideCursor();
377
void ImageEffect_ColorFX::finalRendering()
379
kapp->setOverrideCursor( KCursor::waitCursor() );
380
Digikam::ImageIface* iface = m_previewWidget->imageIface();
381
uchar *data = iface->getOriginalImage();
382
int w = iface->originalWidth();
383
int h = iface->originalHeight();
384
bool sb = iface->originalSixteenBit();
388
colorEffect(data, w, h, sb);
391
switch (m_effectType->currentItem())
394
name = i18n("ColorFX");
398
name = i18n("Vivid");
406
name = i18n("Find Edges");
410
iface->putOriginalImage(name, data);
414
kapp->restoreOverrideCursor();
418
void ImageEffect_ColorFX::colorEffect(uchar *data, int w, int h, bool sb)
420
switch (m_effectType->currentItem())
423
solarize(m_levelInput->value(), data, w, h, sb);
427
vivid(m_levelInput->value(), data, w, h, sb);
431
neon(data, w, h, sb, m_levelInput->value(), m_iterationInput->value());
435
findEdges(data, w, h, sb, m_levelInput->value(), m_iterationInput->value());
440
void ImageEffect_ColorFX::solarize(int factor, uchar *data, int w, int h, bool sb)
444
if (!sb) // 8 bits image.
446
uint threshold = (uint)((100-factor)*(255+1)/100);
447
threshold = QMAX(1, threshold);
451
for (int x=0 ; x < w*h ; x++)
460
r = (r > threshold) ? (255-r)*255/(255-threshold) : r*255/threshold;
461
g = (g > threshold) ? (255-g)*255/(255-threshold) : g*255/threshold;
462
b = (b > threshold) ? (255-b)*255/(255-threshold) : b*255/threshold;
482
else // 16 bits image.
484
uint threshold = (uint)((100-factor)*(65535+1)/100);
485
threshold = QMAX(1, threshold);
486
unsigned short *ptr = (unsigned short *)data;
487
unsigned short a, r, g, b;
489
for (int x=0 ; x < w*h ; x++)
498
r = (r > threshold) ? (65535-r)*65535/(65535-threshold) : r*65535/threshold;
499
g = (g > threshold) ? (65535-g)*65535/(65535-threshold) : g*65535/threshold;
500
b = (b > threshold) ? (65535-b)*65535/(65535-threshold) : b*65535/threshold;
522
void ImageEffect_ColorFX::vivid(int factor, uchar *data, int w, int h, bool sb)
524
float amount = factor/100.0;
526
Digikam::DImgImageFilters filter;
528
// Apply Channel Mixer adjustments.
530
filter.channelMixerImage(
531
data, w, h, sb, // Image data.
532
true, // Preserve Luminosity
533
false, // Disable Black & White mode.
534
1.0 + amount + amount, (-1.0)*amount, (-1.0)*amount, // Red Gains.
535
(-1.0)*amount, 1.0 + amount + amount, (-1.0)*amount, // Green Gains.
536
(-1.0)*amount, (-1.0)*amount, 1.0 + amount + amount // Blue Gains.
539
// Allocate the destination image data.
541
uchar *dest = new uchar[w*h*(sb ? 8 : 4)];
543
// And now apply the curve correction.
545
Digikam::ImageCurves Curves(sb);
547
if (!sb) // 8 bits image.
549
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, QPoint(0, 0));
550
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 5, QPoint(63, 60));
551
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 10, QPoint(191, 194));
552
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, QPoint(255, 255));
554
else // 16 bits image.
556
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, QPoint(0, 0));
557
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 5, QPoint(16128, 15360));
558
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 10, QPoint(48896, 49664));
559
Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, QPoint(65535, 65535));
562
Curves.curvesCalculateCurve(Digikam::ImageHistogram::AlphaChannel); // Calculate cure on all channels.
563
Curves.curvesLutSetup(Digikam::ImageHistogram::AlphaChannel); // ... and apply it on all channels
564
Curves.curvesLutProcess(data, dest, w, h);
566
memcpy(data, dest, w*h*(sb ? 8 : 4));
570
/* Function to apply the Neon effect
572
* data => The image data in RGBA mode.
573
* Width => Width of image.
574
* Height => Height of image.
575
* Intensity => Intensity value
578
* Theory => Wow, this is a great effect, you've never seen a Neon effect
579
* like this on PSC. Is very similar to Growing Edges (photoshop)
580
* Some pictures will be very interesting
582
void ImageEffect_ColorFX::neon(uchar *data, int w, int h, bool sb, int Intensity, int BW)
584
neonFindEdges(data, w, h, sb, true, Intensity, BW);
587
/* Function to apply the Find Edges effect
589
* data => The image data in RGBA mode.
590
* Width => Width of image.
591
* Height => Height of image.
592
* Intensity => Intensity value
595
* Theory => Wow, another Photoshop filter (FindEdges). Do you understand
596
* Neon effect ? This is the same engine, but is inversed with
599
void ImageEffect_ColorFX::findEdges(uchar *data, int w, int h, bool sb, int Intensity, int BW)
601
neonFindEdges(data, w, h, sb, false, Intensity, BW);
604
// Implementation of neon and FindEdges. They share 99% of their code.
605
void ImageEffect_ColorFX::neonFindEdges(uchar *data, int w, int h, bool sb, bool neon, int Intensity, int BW)
609
bool sixteenBit = sb;
610
int bytesDepth = sb ? 8 : 4;
611
uchar* pResBits = new uchar[Width*Height*bytesDepth];
613
Intensity = (Intensity < 0) ? 0 : (Intensity > 5) ? 5 : Intensity;
614
BW = (BW < 1) ? 1 : (BW > 5) ? 5 : BW;
616
uchar *ptr, *ptr1, *ptr2;
618
// these must be uint, we need full 2^32 range for 16 bit
619
uint color_1, color_2, colorPoint, colorOther1, colorOther2;
622
memcpy (pResBits, data, Width*Height*bytesDepth);
624
double intensityFactor = sqrt( 1 << Intensity );
626
for (int h = 0; h < Height; h++)
628
for (int w = 0; w < Width; w++)
630
ptr = pResBits + getOffset(Width, w, h, bytesDepth);
631
ptr1 = pResBits + getOffset(Width, w + Lim_Max (w, BW, Width), h, bytesDepth);
632
ptr2 = pResBits + getOffset(Width, w, h + Lim_Max (h, BW, Height), bytesDepth);
636
for (int k = 0; k <= 2; k++)
638
colorPoint = ((unsigned short *)ptr)[k];
639
colorOther1 = ((unsigned short *)ptr1)[k];
640
colorOther2 = ((unsigned short *)ptr2)[k];
641
color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
642
color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
645
// sqrt ((color_1 + color_2) << Intensity)
646
// As (a << I) = a * (1 << I) = a * (2^I), and we can split the square root
649
((unsigned short *)ptr)[k] = CLAMP065535 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
651
((unsigned short *)ptr)[k] = 65535 - CLAMP065535 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
656
for (int k = 0; k <= 2; k++)
659
colorOther1 = ptr1[k];
660
colorOther2 = ptr2[k];
661
color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
662
color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
665
ptr[k] = CLAMP0255 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
667
ptr[k] = 255 - CLAMP0255 ((int)( sqrt(color_1 + color_2) * intensityFactor ));
673
memcpy (data, pResBits, Width*Height*bytesDepth);
677
int ImageEffect_ColorFX::getOffset(int Width, int X, int Y, int bytesDepth)
679
return (Y * Width * bytesDepth) + (X * bytesDepth);
682
inline int ImageEffect_ColorFX::Lim_Max(int Now, int Up, int Max)
685
while (Now > Max - Up) --Up;
689
} // NameSpace DigikamColorFXImagesPlugin