You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digikam/digikam/imageplugins/raindrop/raindrop.cpp

458 lines
17 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-05-25
* Description : Raindrop threaded image filter.
*
* Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* Original RainDrop algorithm copyrighted 2004-2005 by
* Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot com>.
*
* 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.
*
* ============================================================ */
// C++ includes.
#include <cmath>
#include <cstdlib>
// TQt includes.
#include <tqdeepcopy.h>
#include <tqdatetime.h>
#include <tqrect.h>
// Local includes.
#include "dimg.h"
#include "dimgimagefilters.h"
#include "raindrop.h"
namespace DigikamRainDropImagesPlugin
{
RainDrop::RainDrop(Digikam::DImg *orgImage, TQObject *parent, int drop,
int amount, int coeff, TQRect *selection)
: Digikam::DImgThreadedFilter(orgImage, parent, "RainDrop")
{
m_drop = drop;
m_amount = amount;
m_coeff = coeff;
m_selectedX = m_selectedY = m_selectedW = m_selectedH = 0;
if ( selection )
{
m_selectedX = selection->left();
m_selectedY = selection->top();
m_selectedW = selection->width();
m_selectedH = selection->height();
}
initFilter();
}
void RainDrop::filterImage(void)
{
int w = m_orgImage.width();
int h = m_orgImage.height();
// If we have a region selection in image, use it to apply the filter modification around,
// else, applied the filter on the full image.
if (m_selectedW && m_selectedH)
{
Digikam::DImg zone1, zone2, zone3, zone4,
zone1Dest, zone2Dest, zone3Dest, zone4Dest,
selectedImg;
selectedImg = m_orgImage.copy(m_selectedX, m_selectedY, m_selectedW, m_selectedH);
// Cut the original image in 4 areas without clipping region.
zone1 = m_orgImage.copy(0, 0, m_selectedX, w);
zone2 = m_orgImage.copy(m_selectedX, 0, m_selectedX + m_selectedW, m_selectedY);
zone3 = m_orgImage.copy(m_selectedX, m_selectedY + m_selectedH, m_selectedX + m_selectedW, h);
zone4 = m_orgImage.copy(m_selectedX + m_selectedW, 0, w, h);
zone1Dest = Digikam::DImg(zone1.width(), zone1.height(), zone1.sixteenBit(), zone1.hasAlpha());
zone2Dest = Digikam::DImg(zone2.width(), zone2.height(), zone2.sixteenBit(), zone2.hasAlpha());
zone3Dest = Digikam::DImg(zone3.width(), zone3.height(), zone3.sixteenBit(), zone3.hasAlpha());
zone4Dest = Digikam::DImg(zone4.width(), zone4.height(), zone4.sixteenBit(), zone4.hasAlpha());
// Apply effect on each area.
rainDropsImage(&zone1, &zone1Dest, 0, m_drop, m_amount, m_coeff, true, 0, 25);
rainDropsImage(&zone2, &zone2Dest, 0, m_drop, m_amount, m_coeff, true, 25, 50);
rainDropsImage(&zone3, &zone3Dest, 0, m_drop, m_amount, m_coeff, true, 50, 75);
rainDropsImage(&zone4, &zone4Dest, 0, m_drop, m_amount, m_coeff, true, 75, 100);
// Build the target image.
m_destImage.bitBltImage(&zone1Dest, 0, 0);
m_destImage.bitBltImage(&zone2Dest, m_selectedX, 0);
m_destImage.bitBltImage(&zone3Dest, m_selectedX, m_selectedY + m_selectedH);
m_destImage.bitBltImage(&zone4Dest, m_selectedX + m_selectedW, 0);
m_destImage.bitBltImage(&selectedImg, m_selectedX, m_selectedY);
}
else
{
rainDropsImage(&m_orgImage, &m_destImage, 0, m_drop, m_amount, m_coeff, true, 0, 100);
}
}
/* Function to apply the RainDrops effect backported from ImageProcessing version 2
*
* orgImage => The image
* MinDropSize => It's the minimum random size for rain drop.
* MaxDropSize => It's the minimum random size for rain drop.
* Amount => It's the maximum number for rain drops inside the image.
* Coeff => It's the fisheye's coefficient.
* bLimitRange => If true, the drop will not be cut.
* progressMin => Min. value for progress bar (can be different if using clipping area).
* progressMax => Max. value for progress bar (can be different if using clipping area).
*
* Theory => This functions does several math's functions and the engine
* is simple to undestand, but a little hard to implement. A
* control will indicate if there is or not a raindrop in that
* area, if not, a fisheye effect with a random size (max=MaxDropSize)
* will be applied, after this, a shadow will be applied too.
* and after this, a blur function will finish the effect.
*/
void RainDrop::rainDropsImage(Digikam::DImg *orgImage, Digikam::DImg *destImage, int MinDropSize, int MaxDropSize,
int Amount, int Coeff, bool bLimitRange, int progressMin, int progressMax)
{
bool bResp;
int nRandSize, i;
int nRandX, nRandY;
int nCounter = 0;
int nWidth = orgImage->width();
int nHeight = orgImage->height();
bool sixteenBit = orgImage->sixteenBit();
int bytesDepth = orgImage->bytesDepth();
uchar *data = orgImage->bits();
uchar *pResBits = destImage->bits();
if (Amount <= 0)
return;
if (MinDropSize >= MaxDropSize)
MaxDropSize = MinDropSize + 1;
if (MaxDropSize <= 0)
return;
uchar *pStatusBits = new uchar[nHeight * nWidth];
memset(pStatusBits, 0, sizeof(nHeight * nWidth));
// Initially, copy all pixels to destination
destImage->bitBltImage(orgImage, 0, 0);
// Randomize.
TQDateTime dt = TQDateTime::currentDateTime();
TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
uint seed = dt.secsTo(Y2000);
for (i = 0; !m_cancel && (i < Amount); i++)
{
nCounter = 0;
do
{
nRandX = (int)(rand_r(&seed) * ((double)( nWidth - 1) / RAND_MAX));
nRandY = (int)(rand_r(&seed) * ((double)(nHeight - 1) / RAND_MAX));
nRandSize = (rand() % (MaxDropSize - MinDropSize)) + MinDropSize;
bResp = CreateRainDrop (data, nWidth, nHeight, sixteenBit, bytesDepth,
pResBits, pStatusBits,
nRandX, nRandY, nRandSize, Coeff, bLimitRange);
nCounter++;
}
while ((bResp == false) && (nCounter < 10000) && !m_cancel);
// Update the progress bar in dialog.
if (nCounter >= 10000)
{
i = Amount;
postProgress(progressMax);
break;
}
postProgress( (int)(progressMin + ((double)(i) *
(double)(progressMax-progressMin)) / (double)Amount) );
}
delete [] pStatusBits;
}
bool RainDrop::CreateRainDrop(uchar *pBits, int Width, int Height, bool sixteenBit, int bytesDepth,
uchar *pResBits, uchar* pStatusBits,
int X, int Y, int DropSize, double Coeff, bool bLimitRange)
{
int w, h, nw1, nh1, nw2, nh2;
int nHalfSize = DropSize / 2;
int nBright;
double lfRadius, lfOldRadius, lfAngle, lfDiv;
Digikam::DColor imageData;
uint nTotalR, nTotalG, nTotalB, offset;
int nBlurPixels, nBlurRadius;
if (CanBeDropped(Width, Height, pStatusBits, X, Y, DropSize, bLimitRange))
{
Coeff *= 0.01;
lfDiv = (double)nHalfSize / log (Coeff * (double)nHalfSize + 1.0);
for (h = -nHalfSize; !m_cancel && (h <= nHalfSize); h++)
{
for (w = -nHalfSize; !m_cancel && (w <= nHalfSize); w++)
{
lfRadius = sqrt (h * h + w * w);
lfAngle = atan2 ((double)h, (double)w);
if (lfRadius <= (double)nHalfSize)
{
lfOldRadius = lfRadius;
lfRadius = (exp (lfRadius / lfDiv) - 1.0) / Coeff;
nw1 = (int)((double)X + lfRadius * cos (lfAngle));
nh1 = (int)((double)Y + lfRadius * sin (lfAngle));
nw2 = X + w;
nh2 = Y + h;
if (IsInside(Width, Height, nw1, nh1))
{
if (IsInside(Width, Height, nw2, nh2))
{
nBright = 0;
if (lfOldRadius >= 0.9 * (double)nHalfSize)
{
if ((lfAngle >= 0.0) && (lfAngle < 2.25))
nBright = -80;
else if ((lfAngle >= 2.25) && (lfAngle < 2.5))
nBright = -40;
else if ((lfAngle >= -0.25) && (lfAngle < 0.0))
nBright = -40;
}
else if (lfOldRadius >= 0.8 * (double)nHalfSize)
{
if ((lfAngle >= 0.75) && (lfAngle < 1.50))
nBright = -40;
else if ((lfAngle >= -0.10) && (lfAngle < 0.75))
nBright = -30;
else if ((lfAngle >= 1.50) && (lfAngle < 2.35))
nBright = -30;
}
else if (lfOldRadius >= 0.7 * (double)nHalfSize)
{
if ((lfAngle >= 0.10) && (lfAngle < 2.0))
nBright = -20;
else if ((lfAngle >= -2.50) && (lfAngle < -1.90))
nBright = 60;
}
else if (lfOldRadius >= 0.6 * (double)nHalfSize)
{
if ((lfAngle >= 0.50) && (lfAngle < 1.75))
nBright = -20;
else if ((lfAngle >= 0.0) && (lfAngle < 0.25))
nBright = 20;
else if ((lfAngle >= 2.0) && (lfAngle < 2.25))
nBright = 20;
}
else if (lfOldRadius >= 0.5 * (double)nHalfSize)
{
if ((lfAngle >= 0.25) && (lfAngle < 0.50))
nBright = 30;
else if ((lfAngle >= 1.75 ) && (lfAngle < 2.0))
nBright = 30;
}
else if (lfOldRadius >= 0.4 * (double)nHalfSize)
{
if ((lfAngle >= 0.5) && (lfAngle < 1.75))
nBright = 40;
}
else if (lfOldRadius >= 0.3 * (double)nHalfSize)
{
if ((lfAngle >= 0.0) && (lfAngle < 2.25))
nBright = 30;
}
else if (lfOldRadius >= 0.2 * (double)nHalfSize)
{
if ((lfAngle >= 0.5) && (lfAngle < 1.75))
nBright = 20;
}
imageData.setColor(pBits + Offset(Width, nw1, nh1, bytesDepth), sixteenBit);
if (sixteenBit)
{
// convert difference to 16-bit range
if (nBright > 0)
nBright = (nBright + 1) * 256 - 1;
else
nBright = (nBright - 1) * 256 + 1;
imageData.setRed (LimitValues16(imageData.red() + nBright));
imageData.setGreen(LimitValues16(imageData.green() + nBright));
imageData.setBlue (LimitValues16(imageData.blue() + nBright));
}
else
{
imageData.setRed (LimitValues8(imageData.red() + nBright));
imageData.setGreen(LimitValues8(imageData.green() + nBright));
imageData.setBlue (LimitValues8(imageData.blue() + nBright));
}
imageData.setPixel(pResBits + Offset(Width, nw2, nh2, bytesDepth));
}
}
}
}
}
nBlurRadius = DropSize / 25 + 1;
for (h = -nHalfSize - nBlurRadius; !m_cancel && (h <= nHalfSize + nBlurRadius); h++)
{
for (w = -nHalfSize - nBlurRadius; !m_cancel && (w <= nHalfSize + nBlurRadius); w++)
{
lfRadius = sqrt (h * h + w * w);
if (lfRadius <= (double)nHalfSize * 1.1)
{
nTotalR = nTotalG = nTotalB = 0;
nBlurPixels = 0;
for (nh1 = -nBlurRadius; !m_cancel && (nh1 <= nBlurRadius); nh1++)
{
for (nw1 = -nBlurRadius; !m_cancel && (nw1 <= nBlurRadius); nw1++)
{
nw2 = X + w + nw1;
nh2 = Y + h + nh1;
if (IsInside (Width, Height, nw2, nh2))
{
imageData.setColor(pResBits + Offset(Width, nw2, nh2, bytesDepth), sixteenBit);
nTotalR += imageData.red();
nTotalG += imageData.green();
nTotalB += imageData.blue();
nBlurPixels++;
}
}
}
nw1 = X + w;
nh1 = Y + h;
if (IsInside (Width, Height, nw1, nh1))
{
offset = Offset(Width, nw1, nh1, bytesDepth);
// to preserve alpha channel
imageData.setColor(pResBits + offset, sixteenBit);
imageData.setRed (nTotalR / nBlurPixels);
imageData.setGreen(nTotalG / nBlurPixels);
imageData.setBlue (nTotalB / nBlurPixels);
imageData.setPixel(pResBits + offset);
}
}
}
}
SetDropStatusBits (Width, Height, pStatusBits, X, Y, DropSize);
}
else
return (false);
return (true);
}
bool RainDrop::CanBeDropped(int Width, int Height, uchar *pStatusBits, int X, int Y,
int DropSize, bool bLimitRange)
{
int w, h, i = 0;
int nHalfSize = DropSize / 2;
if (pStatusBits == NULL)
return (true);
for (h = Y - nHalfSize; h <= Y + nHalfSize; h++)
{
for (w = X - nHalfSize; w <= X + nHalfSize; w++)
{
if (IsInside (Width, Height, w, h))
{
i = h * Width + w;
if (pStatusBits[i])
return (false);
}
else
{
if (bLimitRange)
return (false);
}
}
}
return (true);
}
bool RainDrop::SetDropStatusBits (int Width, int Height, uchar *pStatusBits,
int X, int Y, int DropSize)
{
int w, h, i = 0;
int nHalfSize = DropSize / 2;
if (pStatusBits == NULL)
return (false);
for (h = Y - nHalfSize; h <= Y + nHalfSize; h++)
{
for (w = X - nHalfSize; w <= X + nHalfSize; w++)
{
if (IsInside (Width, Height, w, h))
{
i = h * Width + w;
pStatusBits[i] = 255;
}
}
}
return (true);
}
} // NameSpace DigikamRainDropImagesPlugin