/* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-05-25 * Description : Charcoal threaded image filter. * * Copyright (C) 2005-2007 by Gilles Caulier * * Original Charcoal algorithm copyright 2002 * by Daniel M. Duley from KImageEffect API. * * 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 SQ2PI 2.50662827463100024161235523934010416269302368164062 #define Epsilon 1.0e-12 // C++ includes. #include // Local includes. #include "ddebug.h" #include "dimg.h" #include "dimggaussianblur.h" #include "dimgimagefilters.h" #include "charcoal.h" namespace DigikamCharcoalImagesPlugin { Charcoal::Charcoal(Digikam::DImg *orgImage, TQObject *parent, double pencil, double smooth) : Digikam::DImgThreadedFilter(orgImage, parent, "Charcoal") { m_pencil = pencil; m_smooth = smooth; initFilter(); } void Charcoal::filterImage(void) { if (m_orgImage.isNull()) { DWarning() << k_funcinfo << "No image data available!" << endl; return; } if (m_pencil <= 0.0) { m_destImage = m_orgImage; return; } // -- Applying Edge effect ----------------------------------------------- long i=0; int kernelWidth = getOptimalKernelWidth(m_pencil, m_smooth); if((int)m_orgImage.width() < kernelWidth) { DWarning() << k_funcinfo << "Image is smaller than radius!" << endl; return; } double *kernel = new double[kernelWidth*kernelWidth]; if(!kernel) { DWarning() << k_funcinfo << "Unable to allocate memory!" << endl; return; } for(i = 0 ; i < (kernelWidth*kernelWidth) ; i++) kernel[i]=(-1.0); kernel[i/2]=kernelWidth*kernelWidth-1.0; convolveImage(kernelWidth, kernel); delete [] kernel; // -- Applying Gaussian blur effect --------------------------------------- Digikam::DImgGaussianBlur(this, m_destImage, m_destImage, 50, 60, (int)(m_smooth/10.0)); if (m_cancel) return; // -- Applying strech contrast color effect ------------------------------- Digikam::DImgImageFilters().stretchContrastImage(m_destImage.bits(), m_destImage.width(), m_destImage.height(), m_destImage.sixteenBit()); postProgress( 70 ); if (m_cancel) return; // -- Inverting image color ----------------------------------------------- Digikam::DImgImageFilters().invertImage(m_destImage.bits(), m_destImage.width(), m_destImage.height(), m_destImage.sixteenBit()); postProgress( 80 ); if (m_cancel) return; // -- Convert to neutral black & white ------------------------------------ Digikam::DImgImageFilters().channelMixerImage( m_destImage.bits(), m_destImage.width(), m_destImage.height(), m_destImage.sixteenBit(), // Image data. true, // Preserve luminosity. true, // Monochrome. 0.3, 0.59 , 0.11, // Red channel gains. 0.0, 1.0, 0.0, // Green channel gains (not used). 0.0, 0.0, 1.0); // Blue channel gains (not used). postProgress( 90 ); if (m_cancel) return; } bool Charcoal::convolveImage(const unsigned int order, const double *kernel) { uint x, y; int mx, my, sx, sy, mcx, mcy, progress; long kernelWidth, i; double red, green, blue, alpha, normalize=0.0; double *k=0; Digikam::DColor color; kernelWidth = order; if((kernelWidth % 2) == 0) { DWarning() << k_funcinfo << "Kernel width must be an odd number!" << endl; return(false); } double *normal_kernel = new double[kernelWidth*kernelWidth]; if(!normal_kernel) { DWarning() << k_funcinfo << "Unable to allocate memory!" << endl; return(false); } for(i=0 ; i < (kernelWidth*kernelWidth) ; i++) normalize += kernel[i]; if(fabs(normalize) <= Epsilon) normalize=1.0; normalize = 1.0/normalize; for(i=0 ; i < (kernelWidth*kernelWidth) ; i++) normal_kernel[i] = normalize*kernel[i]; double maxClamp = m_destImage.sixteenBit() ? 16777215.0 : 65535.0; for(y=0 ; !m_cancel && (y < m_destImage.height()) ; y++) { sy = y-(kernelWidth/2); for(x=0 ; !m_cancel && (x < m_destImage.width()) ; x++) { k = normal_kernel; red = green = blue = alpha = 0; sy = y-(kernelWidth/2); for(mcy=0 ; !m_cancel && (mcy < kernelWidth) ; mcy++, sy++) { my = sy < 0 ? 0 : sy > (int)m_destImage.height()-1 ? m_destImage.height()-1 : sy; sx = x+(-kernelWidth/2); for(mcx=0 ; !m_cancel && (mcx < kernelWidth) ; mcx++, sx++) { mx = sx < 0 ? 0 : sx > (int)m_destImage.width()-1 ? m_destImage.width()-1 : sx; color = m_orgImage.getPixelColor(mx, my); red += (*k)*(color.red() * 257.0); green += (*k)*(color.green() * 257.0); blue += (*k)*(color.blue() * 257.0); alpha += (*k)*(color.alpha() * 257.0); k++; } } red = red < 0.0 ? 0.0 : red > maxClamp ? maxClamp : red+0.5; green = green < 0.0 ? 0.0 : green > maxClamp ? maxClamp : green+0.5; blue = blue < 0.0 ? 0.0 : blue > maxClamp ? maxClamp : blue+0.5; alpha = alpha < 0.0 ? 0.0 : alpha > maxClamp ? maxClamp : alpha+0.5; m_destImage.setPixelColor(x, y, Digikam::DColor((int)(red / 257UL), (int)(green / 257UL), (int)(blue / 257UL), (int)(alpha / 257UL), m_destImage.sixteenBit())); } progress = (int)(((double)y * 50.0) / m_destImage.height()); if ( progress%5 == 0 ) postProgress( progress ); } delete [] normal_kernel; return(true); } int Charcoal::getOptimalKernelWidth(double radius, double sigma) { double normalize, value; long kernelWidth; long u; if(radius > 0.0) return((int)(2.0*ceil(radius)+1.0)); for(kernelWidth=5; ;) { normalize=0.0; for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++) normalize += exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma); u = kernelWidth/2; value = exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma)/normalize; if((long)(65535*value) <= 0) break; kernelWidth+=2; } return((int)kernelWidth-2); } } // NameSpace DigikamCharcoalImagesPlugin