/* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-02-14 * Description : a digiKam image plugin for to apply a color * effect to an image. * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2006-2008 by Gilles Caulier * * 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 #include #include #include #include #include #include #include #include #include #include // KDE includes. #include #include #include #include #include #include #include #include #include #include // Local includes. #include "version.h" #include "ddebug.h" #include "dimg.h" #include "dimgimagefilters.h" #include "imageiface.h" #include "imagewidget.h" #include "imagecurves.h" #include "imagehistogram.h" #include "histogramwidget.h" #include "colorgradientwidget.h" #include "imageeffect_colorfx.h" #include "imageeffect_colorfx.moc" namespace DigikamColorFXImagesPlugin { ImageEffect_ColorFX::ImageEffect_ColorFX(TQWidget* parent) : Digikam::ImageDlgBase(parent, i18n("Apply Color Special Effects to Photograph"), "coloreffect", false, false) { m_destinationPreviewData = 0; // About data and help button. KAboutData *about = new KAboutData("digikam", I18N_NOOP("Color Effects"), digikam_version, I18N_NOOP("A digiKam plugin to apply special color effects to an image."), KAboutData::License_GPL, "(c) 2004-2005, Renchi Raju\n(c) 2006-2008, Gilles Caulier", 0, "http://www.digikam.org"); about->addAuthor("Renchi Raju", I18N_NOOP("Original Author"), "renchi@pooh.tam.uiuc.edu"); about->addAuthor("Caulier Gilles", I18N_NOOP("Maintainer"), "caulier dot gilles at gmail dot com"); setAboutData(about); // ------------------------------------------------------------- m_previewWidget = new Digikam::ImageWidget("coloreffect Tool Dialog", plainPage(), i18n("

This is the color effect preview")); setPreviewAreaWidget(m_previewWidget); // ------------------------------------------------------------- TQWidget *gboxSettings = new TQWidget(plainPage()); TQGridLayout* gridSettings = new TQGridLayout( gboxSettings, 9, 4, spacingHint()); TQLabel *label1 = new TQLabel(i18n("Channel:"), gboxSettings); label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter ); m_channelCB = new TQComboBox( false, gboxSettings ); m_channelCB->insertItem( i18n("Luminosity") ); m_channelCB->insertItem( i18n("Red") ); m_channelCB->insertItem( i18n("Green") ); m_channelCB->insertItem( i18n("Blue") ); TQWhatsThis::add( m_channelCB, i18n("

Select the histogram channel to display here:

" "Luminosity: display the image's luminosity values.

" "Red: display the red image-channel values.

" "Green: display the green image-channel values.

" "Blue: display the blue image-channel values.

")); m_scaleBG = new TQHButtonGroup(gboxSettings); m_scaleBG->setExclusive(true); m_scaleBG->setFrameShape(TQFrame::NoFrame); m_scaleBG->setInsideMargin( 0 ); TQWhatsThis::add( m_scaleBG, i18n("

Select the histogram scale here.

" "If the image's maximal counts are small, you can use the linear scale.

" "Logarithmic scale can be used when the maximal counts are big; " "if it is used, all values (small and large) will be visible on the graph.")); TQPushButton *linHistoButton = new TQPushButton( m_scaleBG ); TQToolTip::add( linHistoButton, i18n( "

Linear" ) ); m_scaleBG->insert(linHistoButton, Digikam::HistogramWidget::LinScaleHistogram); KGlobal::dirs()->addResourceType("histogram-lin", KGlobal::dirs()->kde_default("data") + "digikam/data"); TQString directory = KGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png"); linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) ); linHistoButton->setToggleButton(true); TQPushButton *logHistoButton = new TQPushButton( m_scaleBG ); TQToolTip::add( logHistoButton, i18n( "

Logarithmic" ) ); m_scaleBG->insert(logHistoButton, Digikam::HistogramWidget::LogScaleHistogram); KGlobal::dirs()->addResourceType("histogram-log", KGlobal::dirs()->kde_default("data") + "digikam/data"); directory = KGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png"); logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) ); logHistoButton->setToggleButton(true); TQHBoxLayout* l1 = new TQHBoxLayout(); l1->addWidget(label1); l1->addWidget(m_channelCB); l1->addStretch(10); l1->addWidget(m_scaleBG); gridSettings->addMultiCellLayout(l1, 0, 0, 0, 4); // ------------------------------------------------------------- TQVBox *histoBox = new TQVBox(gboxSettings); m_histogramWidget = new Digikam::HistogramWidget(256, 140, histoBox, false, true, true); TQWhatsThis::add( m_histogramWidget, i18n("

Here you can see the target preview image histogram drawing " "of the selected image channel. This one is re-computed at any " "settings changes.")); TQLabel *space = new TQLabel(histoBox); space->setFixedHeight(1); m_hGradient = new Digikam::ColorGradientWidget( Digikam::ColorGradientWidget::Horizontal, 10, histoBox ); m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) ); gridSettings->addMultiCellWidget(histoBox, 1, 2, 0, 4); // ------------------------------------------------------------- m_effectTypeLabel = new TQLabel(i18n("Type:"), gboxSettings); m_effectType = new TQComboBox( false, gboxSettings ); m_effectType->insertItem( i18n("Solarize") ); m_effectType->insertItem( i18n("Vivid") ); m_effectType->insertItem( i18n("Neon") ); m_effectType->insertItem( i18n("Find Edges") ); TQWhatsThis::add( m_effectType, i18n("

Select the effect type to apply to the image here.

" "Solarize: simulates solarization of photograph.

" "Vivid: simulates the Velvia(tm) slide film colors.

" "Neon: coloring the edges in a photograph to " "reproduce a fluorescent light effect.

" "Find Edges: detects the edges in a photograph " "and their strength." )); gridSettings->addMultiCellWidget(m_effectTypeLabel, 3, 3, 0, 4); gridSettings->addMultiCellWidget(m_effectType, 4, 4, 0, 4); m_levelLabel = new TQLabel(i18n("Level:"), gboxSettings); m_levelInput = new KIntNumInput(gboxSettings); m_levelInput->setRange(0, 100, 1, true); TQWhatsThis::add( m_levelInput, i18n("

Set here the level of the effect.")); gridSettings->addMultiCellWidget(m_levelLabel, 5, 5, 0, 4); gridSettings->addMultiCellWidget(m_levelInput, 6, 6, 0, 4); m_iterationLabel = new TQLabel(i18n("Iteration:"), gboxSettings); m_iterationInput = new KIntNumInput(gboxSettings); m_iterationInput->setRange(0, 100, 1, true); TQWhatsThis::add( m_iterationInput, i18n("

This value controls the number of iterations " "to use with the Neon and Find Edges effects.")); gridSettings->addMultiCellWidget(m_iterationLabel, 7, 7, 0, 4); gridSettings->addMultiCellWidget(m_iterationInput, 8, 8, 0, 4); gridSettings->setRowStretch(9, 10); setUserAreaWidget(gboxSettings); // ------------------------------------------------------------- connect(m_channelCB, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotChannelChanged(int))); connect(m_scaleBG, TQT_SIGNAL(released(int)), this, TQT_SLOT(slotScaleChanged(int))); connect(m_previewWidget, TQT_SIGNAL(spotPositionChangedFromTarget( const Digikam::DColor &, const TQPoint & )), this, TQT_SLOT(slotColorSelectedFromTarget( const Digikam::DColor & ))); connect(m_levelInput, TQT_SIGNAL(valueChanged(int)), this, TQT_SLOT(slotTimer())); connect(m_iterationInput, TQT_SIGNAL(valueChanged(int)), this, TQT_SLOT(slotTimer())); connect(m_previewWidget, TQT_SIGNAL(signalResized()), this, TQT_SLOT(slotEffect())); connect(m_effectType, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotEffectTypeChanged(int))); } ImageEffect_ColorFX::~ImageEffect_ColorFX() { m_histogramWidget->stopHistogramComputation(); if (m_destinationPreviewData) delete [] m_destinationPreviewData; delete m_previewWidget; } void ImageEffect_ColorFX::readUserSettings() { KConfig* config = kapp->config(); config->setGroup("coloreffect Tool Dialog"); m_effectType->setCurrentItem(config->readNumEntry("EffectType", ColorFX)); m_levelInput->setValue(config->readNumEntry("LevelAjustment", 0)); m_iterationInput->setValue(config->readNumEntry("IterationAjustment", 3)); slotEffectTypeChanged(m_effectType->currentItem()); //check for enable/disable of iteration } void ImageEffect_ColorFX::writeUserSettings() { KConfig* config = kapp->config(); config->setGroup("coloreffect Tool Dialog"); config->writeEntry("EffectType", m_effectType->currentItem()); config->writeEntry("LevelAjustment", m_levelInput->value()); config->writeEntry("IterationAjustment", m_iterationInput->value()); config->sync(); } void ImageEffect_ColorFX::resetValues() { m_levelInput->setValue(0); } void ImageEffect_ColorFX::slotChannelChanged(int channel) { switch(channel) { case LuminosityChannel: m_histogramWidget->m_channelType = Digikam::HistogramWidget::ValueHistogram; m_hGradient->setColors( TQColor( "black" ), TQColor( "white" ) ); break; case RedChannel: m_histogramWidget->m_channelType = Digikam::HistogramWidget::RedChannelHistogram; m_hGradient->setColors( TQColor( "black" ), TQColor( "red" ) ); break; case GreenChannel: m_histogramWidget->m_channelType = Digikam::HistogramWidget::GreenChannelHistogram; m_hGradient->setColors( TQColor( "black" ), TQColor( "green" ) ); break; case BlueChannel: m_histogramWidget->m_channelType = Digikam::HistogramWidget::BlueChannelHistogram; m_hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) ); break; } m_histogramWidget->tqrepaint(false); } void ImageEffect_ColorFX::slotScaleChanged(int scale) { m_histogramWidget->m_scaleType = scale; m_histogramWidget->tqrepaint(false); } void ImageEffect_ColorFX::slotColorSelectedFromTarget( const Digikam::DColor &color ) { m_histogramWidget->setHistogramGuideByColor(color); } void ImageEffect_ColorFX::slotEffectTypeChanged(int type) { m_levelInput->setEnabled(true); m_levelLabel->setEnabled(true); m_levelInput->blockSignals(true); m_iterationInput->blockSignals(true); m_levelInput->setRange(0, 100, 1, true); m_levelInput->setValue(25); switch (type) { case ColorFX: m_levelInput->setRange(0, 100, 1, true); m_levelInput->setValue(0); m_iterationInput->setEnabled(false); m_iterationLabel->setEnabled(false); break; case Vivid: m_levelInput->setRange(0, 50, 1, true); m_levelInput->setValue(5); m_iterationInput->setEnabled(false); m_iterationLabel->setEnabled(false); break; case Neon: case FindEdges: m_levelInput->setRange(0, 5, 1, true); m_levelInput->setValue(3); m_iterationInput->setEnabled(true); m_iterationLabel->setEnabled(true); m_iterationInput->setRange(0, 5, 1, true); m_iterationInput->setValue(2); break; } m_levelInput->blockSignals(false); m_iterationInput->blockSignals(false); slotEffect(); } void ImageEffect_ColorFX::slotEffect() { kapp->setOverrideCursor( KCursor::waitCursor() ); m_histogramWidget->stopHistogramComputation(); if (m_destinationPreviewData) delete [] m_destinationPreviewData; Digikam::ImageIface* iface = m_previewWidget->imageIface(); uchar *m_destinationPreviewData = iface->getPreviewImage(); int w = iface->previewWidth(); int h = iface->previewHeight(); bool sb = iface->previewSixteenBit(); colorEffect(m_destinationPreviewData, w, h, sb); iface->putPreviewImage(m_destinationPreviewData); m_previewWidget->updatePreview(); // Update histogram. m_histogramWidget->updateData(m_destinationPreviewData, w, h, sb, 0, 0, 0, false); kapp->restoreOverrideCursor(); } void ImageEffect_ColorFX::finalRendering() { kapp->setOverrideCursor( KCursor::waitCursor() ); Digikam::ImageIface* iface = m_previewWidget->imageIface(); uchar *data = iface->getOriginalImage(); int w = iface->originalWidth(); int h = iface->originalHeight(); bool sb = iface->originalSixteenBit(); if (data) { colorEffect(data, w, h, sb); TQString name; switch (m_effectType->currentItem()) { case ColorFX: name = i18n("ColorFX"); break; case Vivid: name = i18n("Vivid"); break; case Neon: name = i18n("Neon"); break; case FindEdges: name = i18n("Find Edges"); break; } iface->putOriginalImage(name, data); delete [] data; } kapp->restoreOverrideCursor(); accept(); } void ImageEffect_ColorFX::colorEffect(uchar *data, int w, int h, bool sb) { switch (m_effectType->currentItem()) { case ColorFX: solarize(m_levelInput->value(), data, w, h, sb); break; case Vivid: vivid(m_levelInput->value(), data, w, h, sb); break; case Neon: neon(data, w, h, sb, m_levelInput->value(), m_iterationInput->value()); break; case FindEdges: findEdges(data, w, h, sb, m_levelInput->value(), m_iterationInput->value()); break; } } void ImageEffect_ColorFX::solarize(int factor, uchar *data, int w, int h, bool sb) { bool stretch = true; if (!sb) // 8 bits image. { uint threshold = (uint)((100-factor)*(255+1)/100); threshold = TQMAX(1, threshold); uchar *ptr = data; uchar a, r, g, b; for (int x=0 ; x < w*h ; x++) { b = ptr[0]; g = ptr[1]; r = ptr[2]; a = ptr[3]; if (stretch) { r = (r > threshold) ? (255-r)*255/(255-threshold) : r*255/threshold; g = (g > threshold) ? (255-g)*255/(255-threshold) : g*255/threshold; b = (b > threshold) ? (255-b)*255/(255-threshold) : b*255/threshold; } else { if (r > threshold) r = (255-r); if (g > threshold) g = (255-g); if (b > threshold) b = (255-b); } ptr[0] = b; ptr[1] = g; ptr[2] = r; ptr[3] = a; ptr += 4; } } else // 16 bits image. { uint threshold = (uint)((100-factor)*(65535+1)/100); threshold = TQMAX(1, threshold); unsigned short *ptr = (unsigned short *)data; unsigned short a, r, g, b; for (int x=0 ; x < w*h ; x++) { b = ptr[0]; g = ptr[1]; r = ptr[2]; a = ptr[3]; if (stretch) { r = (r > threshold) ? (65535-r)*65535/(65535-threshold) : r*65535/threshold; g = (g > threshold) ? (65535-g)*65535/(65535-threshold) : g*65535/threshold; b = (b > threshold) ? (65535-b)*65535/(65535-threshold) : b*65535/threshold; } else { if (r > threshold) r = (65535-r); if (g > threshold) g = (65535-g); if (b > threshold) b = (65535-b); } ptr[0] = b; ptr[1] = g; ptr[2] = r; ptr[3] = a; ptr += 4; } } } void ImageEffect_ColorFX::vivid(int factor, uchar *data, int w, int h, bool sb) { float amount = factor/100.0; Digikam::DImgImageFilters filter; // Apply Channel Mixer adjustments. filter.channelMixerImage( data, w, h, sb, // Image data. true, // Preserve Luminosity false, // Disable Black & White mode. 1.0 + amount + amount, (-1.0)*amount, (-1.0)*amount, // Red Gains. (-1.0)*amount, 1.0 + amount + amount, (-1.0)*amount, // Green Gains. (-1.0)*amount, (-1.0)*amount, 1.0 + amount + amount // Blue Gains. ); // Allocate the destination image data. uchar *dest = new uchar[w*h*(sb ? 8 : 4)]; // And now apply the curve correction. Digikam::ImageCurves Curves(sb); if (!sb) // 8 bits image. { Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0)); Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 5, TQPoint(63, 60)); Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 10, TQPoint(191, 194)); Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(255, 255)); } else // 16 bits image. { Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 0, TQPoint(0, 0)); Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 5, TQPoint(16128, 15360)); Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 10, TQPoint(48896, 49664)); Curves.setCurvePoint(Digikam::ImageHistogram::ValueChannel, 16, TQPoint(65535, 65535)); } Curves.curvesCalculateCurve(Digikam::ImageHistogram::AlphaChannel); // Calculate cure on all channels. Curves.curvesLutSetup(Digikam::ImageHistogram::AlphaChannel); // ... and apply it on all channels Curves.curvesLutProcess(data, dest, w, h); memcpy(data, dest, w*h*(sb ? 8 : 4)); delete [] dest; } /* Function to apply the Neon effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Intensity => Intensity value * BW => Border Width * * Theory => Wow, this is a great effect, you've never seen a Neon effect * like this on PSC. Is very similar to Growing Edges (photoshop) * Some pictures will be very interesting */ void ImageEffect_ColorFX::neon(uchar *data, int w, int h, bool sb, int Intensity, int BW) { neonFindEdges(data, w, h, sb, true, Intensity, BW); } /* Function to apply the Find Edges effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Intensity => Intensity value * BW => Border Width * * Theory => Wow, another Photoshop filter (FindEdges). Do you understand * Neon effect ? This is the same engine, but is inversed with * 255 - color. */ void ImageEffect_ColorFX::findEdges(uchar *data, int w, int h, bool sb, int Intensity, int BW) { neonFindEdges(data, w, h, sb, false, Intensity, BW); } // Implementation of neon and FindEdges. They share 99% of their code. void ImageEffect_ColorFX::neonFindEdges(uchar *data, int w, int h, bool sb, bool neon, int Intensity, int BW) { int Width = w; int Height = h; bool sixteenBit = sb; int bytesDepth = sb ? 8 : 4; uchar* pResBits = new uchar[Width*Height*bytesDepth]; Intensity = (Intensity < 0) ? 0 : (Intensity > 5) ? 5 : Intensity; BW = (BW < 1) ? 1 : (BW > 5) ? 5 : BW; uchar *ptr, *ptr1, *ptr2; // these must be uint, we need full 2^32 range for 16 bit uint color_1, color_2, colorPoint, colorOther1, colorOther2; // initial copy memcpy (pResBits, data, Width*Height*bytesDepth); double intensityFactor = sqrt( 1 << Intensity ); for (int h = 0; h < Height; h++) { for (int w = 0; w < Width; w++) { ptr = pResBits + getOffset(Width, w, h, bytesDepth); ptr1 = pResBits + getOffset(Width, w + Lim_Max (w, BW, Width), h, bytesDepth); ptr2 = pResBits + getOffset(Width, w, h + Lim_Max (h, BW, Height), bytesDepth); if (sixteenBit) { for (int k = 0; k <= 2; k++) { colorPoint = ((unsigned short *)ptr)[k]; colorOther1 = ((unsigned short *)ptr1)[k]; colorOther2 = ((unsigned short *)ptr2)[k]; color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1); color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2); // old algorithm was // sqrt ((color_1 + color_2) << Intensity) // As (a << I) = a * (1 << I) = a * (2^I), and we can split the square root if (neon) ((unsigned short *)ptr)[k] = CLAMP065535 ((int)( sqrt(color_1 + color_2) * intensityFactor )); else ((unsigned short *)ptr)[k] = 65535 - CLAMP065535 ((int)( sqrt(color_1 + color_2) * intensityFactor )); } } else { for (int k = 0; k <= 2; k++) { colorPoint = ptr[k]; colorOther1 = ptr1[k]; colorOther2 = ptr2[k]; color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1); color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2); if (neon) ptr[k] = CLAMP0255 ((int)( sqrt(color_1 + color_2) * intensityFactor )); else ptr[k] = 255 - CLAMP0255 ((int)( sqrt(color_1 + color_2) * intensityFactor )); } } } } memcpy (data, pResBits, Width*Height*bytesDepth); delete [] pResBits; } int ImageEffect_ColorFX::getOffset(int Width, int X, int Y, int bytesDepth) { return (Y * Width * bytesDepth) + (X * bytesDepth); } inline int ImageEffect_ColorFX::Lim_Max(int Now, int Up, int Max) { --Max; while (Now > Max - Up) --Up; return (Up); } } // NameSpace DigikamColorFXImagesPlugin