Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_raindrops_filter.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the KDE project
3 *
4 * SPDX-FileCopyrightText: 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
5 *
6 * ported from digikam, copyrighted 2004 by Gilles Caulier,
7 * Original RainDrops algorithm copyrighted 2004 by
8 * Pieter Z. Voloshyn <pieter_voloshyn at ame.com.br>.
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 */
12
14
15#include <stdlib.h>
16#include <vector>
17#include <math.h>
18
19#include <QDateTime>
20#include <QPoint>
21#include <QSpinBox>
22
23#include <klocalizedstring.h>
24#include <kis_debug.h>
25#include <kpluginfactory.h>
26
27#include "KoIntegerMaths.h"
28#include <KoUpdater.h>
29
32#include <filter/kis_filter.h>
33#include <kis_global.h>
34#include <kis_selection.h>
35#include <kis_types.h>
36#include <kis_paint_device.h>
41
43
44#include <QRandomGenerator>
45
53
54// This method have been ported from Pieter Z. Voloshyn algorithm code.
55
56/* Function to apply the RainDrops effect (inspired from Jason Waltman code)
57 *
58 * data => The image data in RGBA mode.
59 * Width => Width of image.
60 * Height => Height of image.
61 * DropSize => Raindrop size
62 * number => Maximum number of raindrops
63 * fishEyes => FishEye coefficient
64 *
65 * Theory => This functions does several math's functions and the engine
66 * is simple to understand, but a little hard to implement. A
67 * control will indicate if there is or not a raindrop in that
68 * area, if not, a fisheye effect with a random size (max=DropSize)
69 * will be applied, after this, a shadow will be applied too.
70 * and after this, a blur function will finish the effect.
71 */
72
73
75 const QRect& applyRect,
76 const KisFilterConfigurationSP config,
77 KoUpdater* progressUpdater ) const
78{
83 KIS_SAFE_ASSERT_RECOVER_RETURN(!applyRect.isEmpty());
84
85 QPoint srcTopLeft = applyRect.topLeft();
86 Q_ASSERT(device);
87
88 //read the filter configuration values from the KisFilterConfiguration object
89 quint32 DropSize = config->getInt("dropSize", 80);
90 quint32 number = config->getInt("number", 80);
91 quint32 fishEyes = config->getInt("fishEyes", 30);
92 QRandomGenerator rng(config->getInt("seed"));
93
94 if (fishEyes <= 0) fishEyes = 1;
95
96 if (fishEyes > 100) fishEyes = 100;
97
98 int Width = applyRect.width();
99 int Height = applyRect.height();
100
101 bool** BoolMatrix = CreateBoolArray(Width, Height);
102
103 int i, j, k, l, m, n; // loop variables
104 int Bright; // Bright value for shadows and highlights
105 int x, y; // center coordinates
106 int Counter = 0; // Counter (duh !)
107 int NewSize; // Size of current raindrop
108 int halfSize; // Half of the current raindrop
109 int Radius; // Maximum radius for raindrop
110 int BlurRadius; // Blur Radius
111 int BlurPixels;
112
113 double r, a; // polar coordinates
114 double OldRadius; // Radius before processing
115 double NewfishEyes = (double)fishEyes * 0.01; // FishEye Coefficients
116 double s;
117 double R, G, B;
118
119 bool FindAnother = false; // To search for good coordinates
120
121 const KoColorSpace * cs = device->colorSpace();
122
123 // Init boolean Matrix.
124
125 for (i = 0 ; i < Width; ++i) {
126 for (j = 0 ; j < Height; ++j) {
127 BoolMatrix[i][j] = false;
128 }
129 }
130
131 progressUpdater->setRange(0, number);
132 KisRandomAccessorSP dstAccessor = device->createRandomAccessorNG();
133
134 for (uint NumBlurs = 0; NumBlurs <= number; ++NumBlurs) {
135 NewSize = 5 + static_cast<int>(rng.bounded(1.0) * (DropSize - 5));
136 halfSize = NewSize / 2;
137 Radius = halfSize;
138 s = Radius / log(NewfishEyes * Radius + 1);
139
140 Counter = 0;
141
142 do {
143 FindAnother = false;
144 y = static_cast<int>(rng.bounded(static_cast<double>(Width - 1)));
145 x = static_cast<int>(rng.bounded(static_cast<double>(Height - 1)));
146
147 if (BoolMatrix[y][x])
148 FindAnother = true;
149 else
150 for (i = x - halfSize ; i <= x + halfSize; i++)
151 for (j = y - halfSize ; j <= y + halfSize; j++)
152 if ((i >= 0) && (i < Height) && (j >= 0) && (j < Width))
153 if (BoolMatrix[j][i])
154 FindAnother = true;
155
156 Counter++;
157 } while (FindAnother && Counter < 10000);
158
159 if (Counter >= 10000) {
160 NumBlurs = number;
161 break;
162 }
163
164 for (i = -1 * halfSize ; i < NewSize - halfSize; i++) {
165 for (j = -1 * halfSize ; j < NewSize - halfSize; j++) {
166 r = sqrt((double)i * i + j * j);
167 a = atan2(static_cast<double>(i), static_cast<double>(j));
168
169 if (r <= Radius) {
170 OldRadius = r;
171 r = (exp(r / s) - 1) / NewfishEyes;
172
173 k = x + (int)(r * sin(a));
174 l = y + (int)(r * cos(a));
175
176 m = x + i;
177 n = y + j;
178
179 if ((k >= 0) && (k < Height) && (l >= 0) && (l < Width)) {
180 if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) {
181 Bright = 0;
182
183 if (OldRadius >= 0.9 * Radius) {
184 if ((a <= 0) && (a > -2.25))
185 Bright = -80;
186 else if ((a <= -2.25) && (a > -2.5))
187 Bright = -40;
188 else if ((a <= 0.25) && (a > 0))
189 Bright = -40;
190 }
191
192 else if (OldRadius >= 0.8 * Radius) {
193 if ((a <= -0.75) && (a > -1.50))
194 Bright = -40;
195 else if ((a <= 0.10) && (a > -0.75))
196 Bright = -30;
197 else if ((a <= -1.50) && (a > -2.35))
198 Bright = -30;
199 }
200
201 else if (OldRadius >= 0.7 * Radius) {
202 if ((a <= -0.10) && (a > -2.0))
203 Bright = -20;
204 else if ((a <= 2.50) && (a > 1.90))
205 Bright = 60;
206 }
207
208 else if (OldRadius >= 0.6 * Radius) {
209 if ((a <= -0.50) && (a > -1.75))
210 Bright = -20;
211 else if ((a <= 0) && (a > -0.25))
212 Bright = 20;
213 else if ((a <= -2.0) && (a > -2.25))
214 Bright = 20;
215 }
216
217 else if (OldRadius >= 0.5 * Radius) {
218 if ((a <= -0.25) && (a > -0.50))
219 Bright = 30;
220 else if ((a <= -1.75) && (a > -2.0))
221 Bright = 30;
222 }
223
224 else if (OldRadius >= 0.4 * Radius) {
225 if ((a <= -0.5) && (a > -1.75))
226 Bright = 40;
227 }
228
229 else if (OldRadius >= 0.3 * Radius) {
230 if ((a <= 0) && (a > -2.25))
231 Bright = 30;
232 }
233
234 else if (OldRadius >= 0.2 * Radius) {
235 if ((a <= -0.5) && (a > -1.75))
236 Bright = 20;
237 }
238
239 BoolMatrix[n][m] = true;
240
241 QColor originalColor;
242
243 dstAccessor->moveTo(srcTopLeft.x() + l, srcTopLeft.y() + k);
244 cs->toQColor(dstAccessor->oldRawData(), &originalColor);
245
246 int newRed = CLAMP(originalColor.red() + Bright, 0, quint8_MAX);
247 int newGreen = CLAMP(originalColor.green() + Bright, 0, quint8_MAX);
248 int newBlue = CLAMP(originalColor.blue() + Bright, 0, quint8_MAX);
249
250 QColor newColor;
251 newColor.setRgb(newRed, newGreen, newBlue);
252
253 dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m);
254 cs->fromQColor(newColor, dstAccessor->rawData());
255 }
256 }
257 }
258 }
259 }
260
261 BlurRadius = NewSize / 25 + 1;
262
263 for (i = -1 * halfSize - BlurRadius ; i < NewSize - halfSize + BlurRadius; i++) {
264 for (j = -1 * halfSize - BlurRadius; j < NewSize - halfSize + BlurRadius; ++j) {
265 r = sqrt((double)i * i + j * j);
266
267 if (r <= Radius * 1.1) {
268 R = G = B = 0;
269 BlurPixels = 0;
270
271 for (k = -1 * BlurRadius; k < BlurRadius + 1; k++)
272 for (l = -1 * BlurRadius; l < BlurRadius + 1; l++) {
273 m = x + i + k;
274 n = y + j + l;
275
276 if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) {
277 QColor color;
278 dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m);
279 cs->toQColor(dstAccessor->rawData(), &color);
280
281 R += color.red();
282 G += color.green();
283 B += color.blue();
284 BlurPixels++;
285 }
286 }
287
288 m = x + i;
289 n = y + j;
290
291 if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) {
292 QColor color;
293
294 if (BlurPixels == 0) {
295 // Coverity complains that it *is* possible
296 // for BlurPixels to be 0, so let's make sure
297 // Krita doesn't crash here
298 BlurPixels = 1;
299 }
300
301 color.setRgb((int)(R / BlurPixels), (int)(G / BlurPixels), (int)(B / BlurPixels));
302 dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m);
303 cs->fromQColor(color, dstAccessor->rawData());
304 }
305 }
306 }
307 }
308
309 progressUpdater->setValue(NumBlurs);
310 }
311
312 FreeBoolArray(BoolMatrix, Width);
313}
314
315// This method have been ported from Pieter Z. Voloshyn algorithm code.
316
317/* Function to free a dynamic boolean array
318 *
319 * lpbArray => Dynamic boolean array
320 * Columns => The number of array columns
321 *
322 * Theory => An easy to understand 'for' statement
323 */
324void KisRainDropsFilter::FreeBoolArray(bool** lpbArray, uint Columns) const
325{
326 for (uint i = 0; i < Columns; ++i)
327 free(lpbArray[i]);
328
329 free(lpbArray);
330}
331
332/* Function to create a bidimensional dynamic boolean array
333 *
334 * Columns => Number of columns
335 * Rows => Number of rows
336 *
337 * Theory => Using 'for' statement, we can alloc multiple dynamic arrays
338 * To create more dimensions, just add some 'for's, ok?
339 */
341{
342 bool** lpbArray = 0;
343 lpbArray = (bool**) malloc(Columns * sizeof(bool*));
344
345 if (lpbArray == 0)
346 return (0);
347
348 for (uint i = 0; i < Columns; ++i) {
349 lpbArray[i] = (bool*) malloc(Rows * sizeof(bool));
350 if (lpbArray[i] == 0) {
351 FreeBoolArray(lpbArray, Columns);
352 return (0);
353 }
354 }
355
356 return (lpbArray);
357}
358
359// This method have been ported from Pieter Z. Voloshyn algorithm code.
360
361/* This function limits the RGB values
362 *
363 * ColorValue => Here, is an RGB value to be analyzed
364 *
365 * Theory => A color is represented in RGB value (e.g. 0xFFFFFF is
366 * white color). But R, G and B values have 256 values to be used
367 * so, this function analyzes the value and limits to this range
368 */
369
370uchar KisRainDropsFilter::LimitValues(int ColorValue) const
371{
372 if (ColorValue > 255) // MAX = 255
373 ColorValue = 255;
374 if (ColorValue < 0) // MIN = 0
375 ColorValue = 0;
376 return ((uchar) ColorValue);
377}
378
380{
382 param.push_back(KisIntegerWidgetParam(1, 200, 80, i18n("Drop size"), "dropsize"));
383 param.push_back(KisIntegerWidgetParam(1, 500, 80, i18n("Number of drops"), "number"));
384 param.push_back(KisIntegerWidgetParam(1, 100, 30, i18n("Fish eyes"), "fishEyes"));
385 KisMultiIntegerFilterWidget * w = new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param);
387 return w;
388}
389
391{
392 KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
393 config->setProperty("dropsize", 80);
394 config->setProperty("number", 80);
395 config->setProperty("fishEyes", 30);
396 config->setProperty("seed", QTime::currentTime().msec());
397
398
399 return config;
400}
Eigen::Matrix< double, 4, 2 > R
unsigned int uint
virtual quint8 * rawData()=0
virtual const quint8 * oldRawData() const =0
static KisResourcesInterfaceSP instance()
const KoColorSpace * colorSpace() const
KisRandomAccessorSP createRandomAccessorNG()
uchar LimitValues(int ColorValue) const
void FreeBoolArray(bool **lpbArray, uint Columns) const
KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
bool ** CreateBoolArray(uint Columns, uint Rows) const
void processImpl(KisPaintDeviceSP device, const QRect &applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override
KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override
virtual void moveTo(qint32 x, qint32 y)=0
virtual void toQColor(const quint8 *src, QColor *c) const =0
virtual void fromQColor(const QColor &color, quint8 *dst) const =0
void setValue(int value) override
Definition KoUpdater.cpp:64
void setRange(int minimum, int maximum) override
Definition KoUpdater.cpp:79
#define CLAMP(x, l, h)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
const KoID FiltersCategoryArtisticId("artistic_filters", ki18nc("The category of artistic filters, like raindrops. Adjective.", "Artistic"))
const quint8 quint8_MAX
Definition kis_global.h:24
std::vector< KisIntegerWidgetParam > vKisIntegerWidgetParam
void setSupportsThreading(bool v)
virtual KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const
void setSupportsAdjustmentLayers(bool v)
void setSupportsPainting(bool v)