File indexing completed on 2024-06-16 04:16:43

0001 /*
0002  * This file is part of the KDE project
0003  *
0004  * SPDX-FileCopyrightText: 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
0005  *
0006  * ported from digikam, copyrighted 2004 by Gilles Caulier,
0007  * Original RainDrops algorithm copyrighted 2004 by
0008  * Pieter Z. Voloshyn <pieter_voloshyn at ame.com.br>.
0009  *
0010  *  SPDX-License-Identifier: GPL-2.0-or-later
0011  */
0012 
0013 #include "kis_raindrops_filter.h"
0014 
0015 #include <stdlib.h>
0016 #include <vector>
0017 #include <math.h>
0018 
0019 #include <QDateTime>
0020 #include <QPoint>
0021 #include <QSpinBox>
0022 
0023 #include <klocalizedstring.h>
0024 #include <kis_debug.h>
0025 #include <kpluginfactory.h>
0026 
0027 #include "KoIntegerMaths.h"
0028 #include <KoUpdater.h>
0029 
0030 #include <filter/kis_filter_category_ids.h>
0031 #include <filter/kis_filter_registry.h>
0032 #include <filter/kis_filter.h>
0033 #include <kis_global.h>
0034 #include <kis_selection.h>
0035 #include <kis_types.h>
0036 #include <kis_paint_device.h>
0037 #include <filter/kis_filter_configuration.h>
0038 #include <kis_processing_information.h>
0039 #include <kis_random_accessor_ng.h>
0040 #include <KisGlobalResourcesInterface.h>
0041 
0042 #include "widgets/kis_multi_integer_filter_widget.h"
0043 
0044 
0045 KisRainDropsFilter::KisRainDropsFilter()
0046     : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Raindrops..."))
0047 {
0048     setSupportsPainting(false);
0049     setSupportsThreading(false);
0050     setSupportsAdjustmentLayers(false);
0051 }
0052 
0053 // This method have been ported from Pieter Z. Voloshyn algorithm code.
0054 
0055 /* Function to apply the RainDrops effect (inspired from Jason Waltman code)
0056  *
0057  * data             => The image data in RGBA mode.
0058  * Width            => Width of image.
0059  * Height           => Height of image.
0060  * DropSize         => Raindrop size
0061  * number           => Maximum number of raindrops
0062  * fishEyes            => FishEye coefficient
0063  *
0064  * Theory           => This functions does several math's functions and the engine
0065  *                     is simple to understand, but a little hard to implement. A
0066  *                     control will indicate if there is or not a raindrop in that
0067  *                     area, if not, a fisheye effect with a random size (max=DropSize)
0068  *                     will be applied, after this, a shadow will be applied too.
0069  *                     and after this, a blur function will finish the effect.
0070  */
0071 
0072 
0073 void KisRainDropsFilter::processImpl(KisPaintDeviceSP device,
0074                                      const QRect& applyRect,
0075                                      const KisFilterConfigurationSP config,
0076                                      KoUpdater* progressUpdater ) const
0077 {
0078     /**
0079      * This case should be filtered out at the higher level, in
0080      * KisFilter::process() or in KisFilterStrokeStrategy
0081      */
0082     KIS_SAFE_ASSERT_RECOVER_RETURN(!applyRect.isEmpty());
0083 
0084     QPoint srcTopLeft = applyRect.topLeft();
0085     Q_ASSERT(device);
0086 
0087     //read the filter configuration values from the KisFilterConfiguration object
0088     quint32 DropSize = config->getInt("dropSize", 80);
0089     quint32 number = config->getInt("number", 80);
0090     quint32 fishEyes = config->getInt("fishEyes", 30);
0091     qsrand(config->getInt("seed"));
0092 
0093     if (fishEyes <= 0) fishEyes = 1;
0094 
0095     if (fishEyes > 100) fishEyes = 100;
0096 
0097     int Width = applyRect.width();
0098     int Height = applyRect.height();
0099 
0100     bool** BoolMatrix = CreateBoolArray(Width, Height);
0101 
0102     int       i, j, k, l, m, n;                 // loop variables
0103     int       Bright;                           // Bright value for shadows and highlights
0104     int       x, y;                             // center coordinates
0105     int       Counter = 0;                      // Counter (duh !)
0106     int       NewSize;                          // Size of current raindrop
0107     int       halfSize;                         // Half of the current raindrop
0108     int       Radius;                           // Maximum radius for raindrop
0109     int       BlurRadius;                       // Blur Radius
0110     int       BlurPixels;
0111 
0112     double    r, a;                             // polar coordinates
0113     double    OldRadius;                        // Radius before processing
0114     double    NewfishEyes = (double)fishEyes * 0.01;  // FishEye Coefficients
0115     double    s;
0116     double    R, G, B;
0117 
0118     bool      FindAnother = false;              // To search for good coordinates
0119 
0120     const KoColorSpace * cs = device->colorSpace();
0121 
0122     // Init boolean Matrix.
0123 
0124     for (i = 0 ; i < Width; ++i) {
0125         for (j = 0 ; j < Height; ++j) {
0126             BoolMatrix[i][j] = false;
0127         }
0128     }
0129 
0130     progressUpdater->setRange(0, number);
0131     KisRandomAccessorSP dstAccessor = device->createRandomAccessorNG();
0132     
0133     for (uint NumBlurs = 0; NumBlurs <= number; ++NumBlurs) {
0134         NewSize = (int)(qrand() * ((double)(DropSize - 5) / RAND_MAX) + 5);
0135         halfSize = NewSize / 2;
0136         Radius = halfSize;
0137         s = Radius / log(NewfishEyes * Radius + 1);
0138 
0139         Counter = 0;
0140 
0141         do {
0142             FindAnother = false;
0143             y = (int)(qrand() * ((double)(Width - 1) / RAND_MAX));
0144             x = (int)(qrand() * ((double)(Height - 1) / RAND_MAX));
0145 
0146             if (BoolMatrix[y][x])
0147                 FindAnother = true;
0148             else
0149                 for (i = x - halfSize ; i <= x + halfSize; i++)
0150                     for (j = y - halfSize ; j <= y + halfSize; j++)
0151                         if ((i >= 0) && (i < Height) && (j >= 0) && (j < Width))
0152                             if (BoolMatrix[j][i])
0153                                 FindAnother = true;
0154 
0155             Counter++;
0156         } while (FindAnother && Counter < 10000);
0157 
0158         if (Counter >= 10000) {
0159             NumBlurs = number;
0160             break;
0161         }
0162 
0163         for (i = -1 * halfSize ; i < NewSize - halfSize; i++) {
0164             for (j = -1 * halfSize ; j < NewSize - halfSize; j++) {
0165                 r = sqrt((double)i * i + j * j);
0166                 a = atan2(static_cast<double>(i), static_cast<double>(j));
0167 
0168                 if (r <= Radius) {
0169                     OldRadius = r;
0170                     r = (exp(r / s) - 1) / NewfishEyes;
0171 
0172                     k = x + (int)(r * sin(a));
0173                     l = y + (int)(r * cos(a));
0174 
0175                     m = x + i;
0176                     n = y + j;
0177 
0178                     if ((k >= 0) && (k < Height) && (l >= 0) && (l < Width)) {
0179                         if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) {
0180                             Bright = 0;
0181 
0182                             if (OldRadius >= 0.9 * Radius) {
0183                                 if ((a <= 0) && (a > -2.25))
0184                                     Bright = -80;
0185                                 else if ((a <= -2.25) && (a > -2.5))
0186                                     Bright = -40;
0187                                 else if ((a <= 0.25) && (a > 0))
0188                                     Bright = -40;
0189                             }
0190 
0191                             else if (OldRadius >= 0.8 * Radius) {
0192                                 if ((a <= -0.75) && (a > -1.50))
0193                                     Bright = -40;
0194                                 else if ((a <= 0.10) && (a > -0.75))
0195                                     Bright = -30;
0196                                 else if ((a <= -1.50) && (a > -2.35))
0197                                     Bright = -30;
0198                             }
0199 
0200                             else if (OldRadius >= 0.7 * Radius) {
0201                                 if ((a <= -0.10) && (a > -2.0))
0202                                     Bright = -20;
0203                                 else if ((a <= 2.50) && (a > 1.90))
0204                                     Bright = 60;
0205                             }
0206 
0207                             else if (OldRadius >= 0.6 * Radius) {
0208                                 if ((a <= -0.50) && (a > -1.75))
0209                                     Bright = -20;
0210                                 else if ((a <= 0) && (a > -0.25))
0211                                     Bright = 20;
0212                                 else if ((a <= -2.0) && (a > -2.25))
0213                                     Bright = 20;
0214                             }
0215 
0216                             else if (OldRadius >= 0.5 * Radius) {
0217                                 if ((a <= -0.25) && (a > -0.50))
0218                                     Bright = 30;
0219                                 else if ((a <= -1.75) && (a > -2.0))
0220                                     Bright = 30;
0221                             }
0222 
0223                             else if (OldRadius >= 0.4 * Radius) {
0224                                 if ((a <= -0.5) && (a > -1.75))
0225                                     Bright = 40;
0226                             }
0227 
0228                             else if (OldRadius >= 0.3 * Radius) {
0229                                 if ((a <= 0) && (a > -2.25))
0230                                     Bright = 30;
0231                             }
0232 
0233                             else if (OldRadius >= 0.2 * Radius) {
0234                                 if ((a <= -0.5) && (a > -1.75))
0235                                     Bright = 20;
0236                             }
0237 
0238                             BoolMatrix[n][m] = true;
0239 
0240                             QColor originalColor;
0241 
0242                             dstAccessor->moveTo(srcTopLeft.x() + l, srcTopLeft.y() + k);
0243                             cs->toQColor(dstAccessor->oldRawData(), &originalColor);
0244 
0245                             int newRed = CLAMP(originalColor.red() + Bright, 0, quint8_MAX);
0246                             int newGreen = CLAMP(originalColor.green() + Bright, 0, quint8_MAX);
0247                             int newBlue = CLAMP(originalColor.blue() + Bright, 0, quint8_MAX);
0248 
0249                             QColor newColor;
0250                             newColor.setRgb(newRed, newGreen, newBlue);
0251 
0252                             dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m);
0253                             cs->fromQColor(newColor, dstAccessor->rawData());
0254                         }
0255                     }
0256                 }
0257             }
0258         }
0259 
0260         BlurRadius = NewSize / 25 + 1;
0261 
0262         for (i = -1 * halfSize - BlurRadius ; i < NewSize - halfSize + BlurRadius; i++) {
0263             for (j = -1 * halfSize - BlurRadius; j < NewSize - halfSize + BlurRadius; ++j) {
0264                 r = sqrt((double)i * i + j * j);
0265 
0266                 if (r <= Radius * 1.1) {
0267                     R = G = B = 0;
0268                     BlurPixels = 0;
0269 
0270                     for (k = -1 * BlurRadius; k < BlurRadius + 1; k++)
0271                         for (l = -1 * BlurRadius; l < BlurRadius + 1; l++) {
0272                             m = x + i + k;
0273                             n = y + j + l;
0274 
0275                             if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) {
0276                                 QColor color;
0277                                 dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m);
0278                                 cs->toQColor(dstAccessor->rawData(), &color);
0279 
0280                                 R += color.red();
0281                                 G += color.green();
0282                                 B += color.blue();
0283                                 BlurPixels++;
0284                             }
0285                         }
0286 
0287                     m = x + i;
0288                     n = y + j;
0289 
0290                     if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) {
0291                         QColor color;
0292 
0293                         if (BlurPixels == 0) {
0294                             // Coverity complains that it *is* possible
0295                             // for BlurPixels to be 0, so let's make sure
0296                             // Krita doesn't crash here
0297                             BlurPixels = 1;
0298                         }
0299 
0300                         color.setRgb((int)(R / BlurPixels), (int)(G / BlurPixels), (int)(B / BlurPixels));
0301                         dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m);
0302                         cs->fromQColor(color, dstAccessor->rawData());
0303                     }
0304                 }
0305             }
0306         }
0307 
0308         progressUpdater->setValue(NumBlurs);
0309     }
0310 
0311     FreeBoolArray(BoolMatrix, Width);
0312 }
0313 
0314 // This method have been ported from Pieter Z. Voloshyn algorithm code.
0315 
0316 /* Function to free a dynamic boolean array
0317  *
0318  * lpbArray          => Dynamic boolean array
0319  * Columns           => The number of array columns
0320  *
0321  * Theory            => An easy to understand 'for' statement
0322  */
0323 void KisRainDropsFilter::FreeBoolArray(bool** lpbArray, uint Columns) const
0324 {
0325     for (uint i = 0; i < Columns; ++i)
0326         free(lpbArray[i]);
0327 
0328     free(lpbArray);
0329 }
0330 
0331 /* Function to create a bidimensional dynamic boolean array
0332  *
0333  * Columns           => Number of columns
0334  * Rows              => Number of rows
0335  *
0336  * Theory            => Using 'for' statement, we can alloc multiple dynamic arrays
0337  *                      To create more dimensions, just add some 'for's, ok?
0338  */
0339 bool** KisRainDropsFilter::CreateBoolArray(uint Columns, uint Rows) const
0340 {
0341     bool** lpbArray = 0;
0342     lpbArray = (bool**) malloc(Columns * sizeof(bool*));
0343 
0344     if (lpbArray == 0)
0345         return (0);
0346 
0347     for (uint i = 0; i < Columns; ++i) {
0348         lpbArray[i] = (bool*) malloc(Rows * sizeof(bool));
0349         if (lpbArray[i] == 0) {
0350             FreeBoolArray(lpbArray, Columns);
0351             return (0);
0352         }
0353     }
0354 
0355     return (lpbArray);
0356 }
0357 
0358 // This method have been ported from Pieter Z. Voloshyn algorithm code.
0359 
0360 /* This function limits the RGB values
0361  *
0362  * ColorValue        => Here, is an RGB value to be analyzed
0363  *
0364  * Theory            => A color is represented in RGB value (e.g. 0xFFFFFF is
0365  *                      white color). But R, G and B values have 256 values to be used
0366  *                      so, this function analyzes the value and limits to this range
0367  */
0368 
0369 uchar KisRainDropsFilter::LimitValues(int ColorValue) const
0370 {
0371     if (ColorValue > 255)        // MAX = 255
0372         ColorValue = 255;
0373     if (ColorValue < 0)          // MIN = 0
0374         ColorValue = 0;
0375     return ((uchar) ColorValue);
0376 }
0377 
0378 KisConfigWidget * KisRainDropsFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const
0379 {
0380     vKisIntegerWidgetParam param;
0381     param.push_back(KisIntegerWidgetParam(1, 200, 80, i18n("Drop size"), "dropsize"));
0382     param.push_back(KisIntegerWidgetParam(1, 500, 80, i18n("Number of drops"), "number"));
0383     param.push_back(KisIntegerWidgetParam(1, 100, 30, i18n("Fish eyes"), "fishEyes"));
0384     KisMultiIntegerFilterWidget * w = new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param);
0385     w->setConfiguration(defaultConfiguration(KisGlobalResourcesInterface::instance()));
0386     return w;
0387 }
0388 
0389 KisFilterConfigurationSP KisRainDropsFilter::defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const
0390 {
0391     KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
0392     config->setProperty("dropsize", 80);
0393     config->setProperty("number", 80);
0394     config->setProperty("fishEyes", 30);
0395     config->setProperty("seed", QTime::currentTime().msec());
0396 
0397 
0398     return config;
0399 }