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.
440 lines
16 KiB
440 lines
16 KiB
/*
|
|
* This file is part of the KDE project
|
|
*
|
|
* Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
|
|
*
|
|
* ported from digikam, Copyright 2004 by Gilles Caulier,
|
|
* Original RainDrops algorithm copyrighted 2004 by
|
|
* Pieter Z. Voloshyn <pieter_voloshyn at ame.com.br>.
|
|
*
|
|
* 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 of the License, 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <vector>
|
|
|
|
#include <tqpoint.h>
|
|
#include <tqspinbox.h>
|
|
|
|
#include <tdelocale.h>
|
|
#include <kiconloader.h>
|
|
#include <kinstance.h>
|
|
#include <tdemessagebox.h>
|
|
#include <kstandarddirs.h>
|
|
#include <tdetempfile.h>
|
|
#include <kdebug.h>
|
|
#include <kgenericfactory.h>
|
|
#include <knuminput.h>
|
|
|
|
#include <kis_doc.h>
|
|
#include <kis_image.h>
|
|
#include <kis_iterators_pixel.h>
|
|
#include <kis_random_accessor.h>
|
|
#include <kis_layer.h>
|
|
#include <kis_filter_registry.h>
|
|
#include <kis_filter.h>
|
|
#include <kis_global.h>
|
|
#include <kis_types.h>
|
|
#include <kis_view.h>
|
|
#include <kis_progress_display_interface.h>
|
|
|
|
#include "kis_multi_integer_filter_widget.h"
|
|
#include "kis_raindrops_filter.h"
|
|
|
|
KisRainDropsFilter::KisRainDropsFilter() : KisFilter(id(), "artistic", i18n("&Raindrops..."))
|
|
{
|
|
}
|
|
|
|
void KisRainDropsFilter::process(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisFilterConfiguration* configuration, const TQRect& rect)
|
|
{
|
|
|
|
Q_UNUSED(dst);
|
|
|
|
//read the filter configuration values from the KisFilterConfiguration object
|
|
TQ_UINT32 dropSize = ((KisRainDropsFilterConfiguration*)configuration)->dropSize();
|
|
TQ_UINT32 number = ((KisRainDropsFilterConfiguration*)configuration)->number();
|
|
TQ_UINT32 fishEyes = ((KisRainDropsFilterConfiguration*)configuration)->fishEyes();
|
|
|
|
|
|
rainDrops(src, dst, rect, dropSize, number, fishEyes);
|
|
}
|
|
|
|
// This method have been ported from Pieter Z. Voloshyn algorithm code.
|
|
|
|
/* Function to apply the RainDrops effect (inspired from Jason Waltman code)
|
|
*
|
|
* data => The image data in RGBA mode.
|
|
* Width => Width of image.
|
|
* Height => Height of image.
|
|
* DropSize => Raindrop size
|
|
* Amount => Maximum number of raindrops
|
|
* Coeff => FishEye coefficient
|
|
*
|
|
* 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=DropSize)
|
|
* will be applied, after this, a shadow will be applied too.
|
|
* and after this, a blur function will finish the effect.
|
|
*/
|
|
|
|
void KisRainDropsFilter::rainDrops(KisPaintDeviceSP src, KisPaintDeviceSP dst, const TQRect& rect, int DropSize, int Amount, int Coeff)
|
|
{
|
|
setProgressTotalSteps(Amount);
|
|
setProgressStage(i18n("Applying oilpaint filter..."),0);
|
|
|
|
if (Coeff <= 0) Coeff = 1;
|
|
|
|
if (Coeff > 100) Coeff = 100;
|
|
|
|
int Width = rect.width();
|
|
int Height = rect.height();
|
|
|
|
bool** BoolMatrix = CreateBoolArray (Width, Height);
|
|
|
|
int i, j, k, l, m, n; // loop variables
|
|
int Bright; // Bright value for shadows and highlights
|
|
int x, y; // center coordinates
|
|
int Counter = 0; // Counter (duh !)
|
|
int NewSize; // Size of current raindrop
|
|
int halfSize; // Half of the current raindrop
|
|
int Radius; // Maximum radius for raindrop
|
|
int BlurRadius; // Blur Radius
|
|
int BlurPixels;
|
|
|
|
double r, a; // polar coordinates
|
|
double OldRadius; // Radius before processing
|
|
double NewCoeff = (double)Coeff * 0.01; // FishEye Coefficients
|
|
double s;
|
|
double R, G, B;
|
|
|
|
bool FindAnother = false; // To search for good coordinates
|
|
|
|
KisColorSpace * cs = src->colorSpace();
|
|
|
|
TQDateTime dt = TQDateTime::currentDateTime();
|
|
TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
|
|
|
|
srand ((uint) dt.secsTo(Y2000));
|
|
|
|
// Init booleen Matrix.
|
|
|
|
for (i = 0 ; !cancelRequested() && (i < Width) ; ++i)
|
|
{
|
|
for (j = 0 ; !cancelRequested() && (j < Height) ; ++j)
|
|
{
|
|
BoolMatrix[i][j] = false;
|
|
}
|
|
}
|
|
KisRandomAccessorPixel oldIt = src->createRandomAccessor(0,0,false);
|
|
KisRandomAccessorPixel dstIt = dst->createRandomAccessor(0,0,true);
|
|
|
|
for (int NumBlurs = 0 ; !cancelRequested() && (NumBlurs <= Amount) ; ++NumBlurs)
|
|
{
|
|
NewSize = (int)(rand() * ((double)(DropSize - 5) / RAND_MAX) + 5);
|
|
halfSize = NewSize / 2;
|
|
Radius = halfSize;
|
|
s = Radius / log (NewCoeff * Radius + 1);
|
|
|
|
Counter = 0;
|
|
|
|
do
|
|
{
|
|
FindAnother = false;
|
|
y = (int)(rand() * ((double)( Width - 1) / RAND_MAX));
|
|
x = (int)(rand() * ((double)(Height - 1) / RAND_MAX));
|
|
|
|
if (BoolMatrix[y][x])
|
|
FindAnother = true;
|
|
else
|
|
for (i = x - halfSize ; !cancelRequested() && (i <= x + halfSize) ; i++)
|
|
for (j = y - halfSize ; !cancelRequested() && (j <= y + halfSize) ; j++)
|
|
if ((i >= 0) && (i < Height) && (j >= 0) && (j < Width))
|
|
if (BoolMatrix[j][i])
|
|
FindAnother = true;
|
|
|
|
Counter++;
|
|
}
|
|
while (!cancelRequested() && (FindAnother && (Counter < 10000)) );
|
|
|
|
if (Counter >= 10000)
|
|
{
|
|
NumBlurs = Amount;
|
|
break;
|
|
}
|
|
|
|
for (i = -1 * halfSize ; !cancelRequested() && (i < NewSize - halfSize) ; i++)
|
|
{
|
|
for (j = -1 * halfSize ; !cancelRequested() && (j < NewSize - halfSize) ; j++)
|
|
{
|
|
r = sqrt (i * i + j * j);
|
|
a = atan2 (i, j);
|
|
|
|
if (r <= Radius)
|
|
{
|
|
OldRadius = r;
|
|
r = (exp (r / s) - 1) / NewCoeff;
|
|
|
|
k = x + (int)(r * sin (a));
|
|
l = y + (int)(r * cos (a));
|
|
|
|
m = x + i;
|
|
n = y + j;
|
|
|
|
if ((k >= 0) && (k < Height) && (l >= 0) && (l < Width))
|
|
{
|
|
if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width))
|
|
{
|
|
Bright = 0;
|
|
|
|
if (OldRadius >= 0.9 * Radius)
|
|
{
|
|
if ((a <= 0) && (a > -2.25))
|
|
Bright = -80;
|
|
else if ((a <= -2.25) && (a > -2.5))
|
|
Bright = -40;
|
|
else if ((a <= 0.25) && (a > 0))
|
|
Bright = -40;
|
|
}
|
|
|
|
else if (OldRadius >= 0.8 * Radius)
|
|
{
|
|
if ((a <= -0.75) && (a > -1.50))
|
|
Bright = -40;
|
|
else if ((a <= 0.10) && (a > -0.75))
|
|
Bright = -30;
|
|
else if ((a <= -1.50) && (a > -2.35))
|
|
Bright = -30;
|
|
}
|
|
|
|
else if (OldRadius >= 0.7 * Radius)
|
|
{
|
|
if ((a <= -0.10) && (a > -2.0))
|
|
Bright = -20;
|
|
else if ((a <= 2.50) && (a > 1.90))
|
|
Bright = 60;
|
|
}
|
|
|
|
else if (OldRadius >= 0.6 * Radius)
|
|
{
|
|
if ((a <= -0.50) && (a > -1.75))
|
|
Bright = -20;
|
|
else if ((a <= 0) && (a > -0.25))
|
|
Bright = 20;
|
|
else if ((a <= -2.0) && (a > -2.25))
|
|
Bright = 20;
|
|
}
|
|
|
|
else if (OldRadius >= 0.5 * Radius)
|
|
{
|
|
if ((a <= -0.25) && (a > -0.50))
|
|
Bright = 30;
|
|
else if ((a <= -1.75 ) && (a > -2.0))
|
|
Bright = 30;
|
|
}
|
|
|
|
else if (OldRadius >= 0.4 * Radius)
|
|
{
|
|
if ((a <= -0.5) && (a > -1.75))
|
|
Bright = 40;
|
|
}
|
|
|
|
else if (OldRadius >= 0.3 * Radius)
|
|
{
|
|
if ((a <= 0) && (a > -2.25))
|
|
Bright = 30;
|
|
}
|
|
|
|
else if (OldRadius >= 0.2 * Radius)
|
|
{
|
|
if ((a <= -0.5) && (a > -1.75))
|
|
Bright = 20;
|
|
}
|
|
|
|
BoolMatrix[n][m] = true;
|
|
|
|
TQColor originalColor;
|
|
oldIt.moveTo(rect.x() + l, rect.y() + k);
|
|
cs->toTQColor(oldIt.oldRawData(), &originalColor);
|
|
|
|
int newRed = CLAMP(originalColor.red() + Bright, 0, TQ_UINT8_MAX);
|
|
int newGreen = CLAMP(originalColor.green() + Bright, 0, TQ_UINT8_MAX);
|
|
int newBlue = CLAMP(originalColor.blue() + Bright, 0, TQ_UINT8_MAX);
|
|
|
|
TQColor newColor;
|
|
newColor.setRgb(newRed, newGreen, newBlue);
|
|
|
|
dstIt.moveTo(rect.x() + n, rect.y() + m);
|
|
cs->fromTQColor(newColor, dstIt.rawData());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BlurRadius = NewSize / 25 + 1;
|
|
|
|
for (i = -1 * halfSize - BlurRadius ; !cancelRequested() && (i < NewSize - halfSize + BlurRadius) ; i++)
|
|
{
|
|
for (j = -1 * halfSize - BlurRadius ; !cancelRequested() && (j < NewSize - halfSize + BlurRadius) ; j++)
|
|
{
|
|
r = sqrt (i * i + j * j);
|
|
|
|
if (r <= Radius * 1.1)
|
|
{
|
|
R = G = B = 0;
|
|
BlurPixels = 0;
|
|
|
|
for (k = -1 * BlurRadius; k < BlurRadius + 1; k++)
|
|
for (l = -1 * BlurRadius; l < BlurRadius + 1; l++)
|
|
{
|
|
m = x + i + k;
|
|
n = y + j + l;
|
|
|
|
if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width))
|
|
{
|
|
TQColor color;
|
|
dstIt.moveTo(rect.x() + n, rect.y() + m);
|
|
|
|
cs->toTQColor(dstIt.rawData(), &color);
|
|
|
|
R += color.red();
|
|
G += color.green();
|
|
B += color.blue();
|
|
BlurPixels++;
|
|
}
|
|
}
|
|
|
|
m = x + i;
|
|
n = y + j;
|
|
|
|
if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width))
|
|
{
|
|
TQColor color;
|
|
|
|
color.setRgb((int)(R / BlurPixels), (int)(G / BlurPixels), (int)(B / BlurPixels));
|
|
dstIt.moveTo(rect.x() + n, rect.y() + m);
|
|
|
|
cs->fromTQColor(color, dstIt.rawData());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setProgress(NumBlurs);
|
|
}
|
|
|
|
/* KisRectIteratorPixel srcIt2 = src->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), false);
|
|
KisRectIteratorPixel dstIt2 = src->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), true);
|
|
|
|
while (!srcIt2.isDone()) {
|
|
|
|
if (!srcIt2.isSelected()) {
|
|
memcpy(dstIt2.rawData(), srcIt2.oldRawData(), src->pixelSize());
|
|
}
|
|
++srcIt2;
|
|
}
|
|
*/
|
|
FreeBoolArray (BoolMatrix, Width);
|
|
|
|
setProgressDone();
|
|
}
|
|
|
|
// This method have been ported from Pieter Z. Voloshyn algorithm code.
|
|
|
|
/* Function to free a dinamic boolean array
|
|
*
|
|
* lpbArray => Dynamic boolean array
|
|
* Columns => The array bidimension value
|
|
*
|
|
* Theory => An easy to undestand 'for' statement
|
|
*/
|
|
void KisRainDropsFilter::FreeBoolArray (bool** lpbArray, uint Columns)
|
|
{
|
|
for (uint i = 0; i < Columns; ++i)
|
|
free (lpbArray[i]);
|
|
|
|
free (lpbArray);
|
|
}
|
|
|
|
/* Function to create a bidimentional dinamic boolean array
|
|
*
|
|
* Columns => Number of columns
|
|
* Rows => Number of rows
|
|
*
|
|
* Theory => Using 'for' statement, we can alloc multiple dinamic arrays
|
|
* To create more dimentions, just add some 'for's, ok?
|
|
*/
|
|
bool** KisRainDropsFilter::CreateBoolArray (uint Columns, uint Rows)
|
|
{
|
|
bool** lpbArray = NULL;
|
|
lpbArray = (bool**) malloc (Columns * sizeof (bool*));
|
|
|
|
if (lpbArray == NULL)
|
|
return (NULL);
|
|
|
|
for (uint i = 0; i < Columns; ++i)
|
|
{
|
|
lpbArray[i] = (bool*) malloc (Rows * sizeof (bool));
|
|
if (lpbArray[i] == NULL)
|
|
{
|
|
FreeBoolArray (lpbArray, Columns);
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
return (lpbArray);
|
|
}
|
|
|
|
// This method have been ported from Pieter Z. Voloshyn algorithm code.
|
|
|
|
/* This function limits the RGB values
|
|
*
|
|
* ColorValue => Here, is an RGB value to be analized
|
|
*
|
|
* Theory => A color is represented in RGB value (e.g. 0xFFFFFF is
|
|
* white color). But R, G and B values has 256 values to be used
|
|
* so, this function analize the value and limits to this range
|
|
*/
|
|
|
|
uchar KisRainDropsFilter::LimitValues (int ColorValue)
|
|
{
|
|
if (ColorValue > 255) // MAX = 255
|
|
ColorValue = 255;
|
|
if (ColorValue < 0) // MIN = 0
|
|
ColorValue = 0;
|
|
return ((uchar) ColorValue);
|
|
}
|
|
|
|
KisFilterConfigWidget * KisRainDropsFilter::createConfigurationWidget(TQWidget* parent, KisPaintDeviceSP)
|
|
{
|
|
vKisIntegerWidgetParam param;
|
|
param.push_back( KisIntegerWidgetParam( 1, 200, 80, i18n("Drop size"), "dropsize" ) );
|
|
param.push_back( KisIntegerWidgetParam( 1, 500, 80, i18n("Number"), "number" ) );
|
|
param.push_back( KisIntegerWidgetParam( 1, 100, 30, i18n("Fish eyes"), "fishEyes" ) );
|
|
return new KisMultiIntegerFilterWidget(parent, id().id().ascii(), id().id().ascii(), param );
|
|
}
|
|
|
|
KisFilterConfiguration* KisRainDropsFilter::configuration(TQWidget* nwidget)
|
|
{
|
|
KisMultiIntegerFilterWidget* widget = (KisMultiIntegerFilterWidget*) nwidget;
|
|
if( widget == 0 )
|
|
{
|
|
return new KisRainDropsFilterConfiguration( 30, 80, 20);
|
|
} else {
|
|
return new KisRainDropsFilterConfiguration( widget->valueAt( 0 ), widget->valueAt( 1 ), widget->valueAt( 2 ) );
|
|
}
|
|
}
|