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 }