Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_edge_detection_kernel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8#include "kis_global.h"
12#include <QRect>
13#include <KoColorSpace.h>
14#include <kis_iterator_ng.h>
15#include <QVector3D>
16
21/*
22 * This code is very similar to the gaussian kernel code, except unlike the gaussian code,
23 * edge-detection kernels DO use the diagonals.
24 * Except for the simple mode. We implement the simple mode because it is an analog to
25 * the old sobel filter.
26 */
27
28Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisEdgeDetectionKernel::createHorizontalMatrix(qreal radius,
29 FilterType type,
30 bool reverse)
31{
32 int kernelSize = kernelSizeFromRadius(radius);
33 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
34
35 KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
36 const int center = kernelSize / 2;
37
38 if (type==Prewitt) {
39 for (int x = 0; x < kernelSize; x++) {
40 for (int y=0; y<kernelSize; y++) {
41 qreal xDistance;
42 if (reverse) {
43 xDistance = x - center;
44 } else {
45 xDistance = center - x;
46 }
47 matrix(x, y) = xDistance;
48 }
49 }
50 } else if(type==Simple) {
51 matrix.resize(kernelSize, 1);
52 for (int x = 0; x < kernelSize; x++) {
53 qreal xDistance;
54 if (reverse) {
55 xDistance = x - center;
56 } else {
57 xDistance = center - x;
58 }
59 if (x==center) {
60 matrix(x, 0) = 0;
61 } else {
62 matrix(x, 0) = (1/xDistance);
63 }
64 }
65 } else {
66 for (int x = 0; x < kernelSize; x++) {
67 for (int y=0; y<kernelSize; y++) {
68 if (x==center && y==center) {
69 matrix(x, y) = 0;
70 } else {
71 qreal xD, yD;
72 if (reverse) {
73 xD = x - center;
74 yD = y - center;
75 } else {
76 xD = center - x;
77 yD = center - y;
78 }
79 matrix(x, y) = xD / (xD*xD + yD*yD);
80 }
81 }
82 }
83 }
84 return matrix;
85}
86
87Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisEdgeDetectionKernel::createVerticalMatrix(qreal radius,
88 FilterType type,
89 bool reverse)
90{
91 int kernelSize = kernelSizeFromRadius(radius);
92
93 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
94 KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
95 const int center = kernelSize / 2;
96
97 if (type==Prewitt) {
98 for (int y = 0; y < kernelSize; y++) {
99 for (int x=0; x<kernelSize; x++) {
100 qreal yDistance;
101 if (reverse) {
102 yDistance = y - center;
103 } else {
104 yDistance = center - y;
105 }
106 matrix(x, y) = yDistance;
107 }
108 }
109 } else if(type==Simple) {
110 matrix.resize(1, kernelSize);
111 for (int y = 0; y < kernelSize; y++) {
112 qreal yDistance;
113 if (reverse) {
114 yDistance = y - center;
115 } else {
116 yDistance = center - y;
117 }
118 if (y==center) {
119 matrix(0, y) = 0;
120 } else {
121 matrix(0, y) = (1/yDistance);
122 }
123 }
124 } else {
125 for (int y = 0; y < kernelSize; y++) {
126 for (int x=0; x<kernelSize; x++) {
127 if (x==center && y==center) {
128 matrix(x, y) = 0;
129 } else {
130 qreal xD, yD;
131 if (reverse) {
132 xD = x - center;
133 yD = y - center;
134 } else {
135 xD = center - x;
136 yD = center - y;
137 }
138 matrix(x, y) = yD / (xD*xD + yD*yD);
139 }
140 }
141 }
142 }
143 return matrix;
144}
145
148 bool denormalize,
149 bool reverse)
150{
151 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createHorizontalMatrix(radius, type, reverse);
152 if (denormalize) {
153 return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1);
154 } else {
155 return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
156 }
157}
158
161 bool denormalize,
162 bool reverse)
163{
164 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createVerticalMatrix(radius, type, reverse);
165 if (denormalize) {
166 return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1);
167 } else {
168 return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
169 }
170}
171
173{
174 return qMax((int)(2 * ceil(sigmaFromRadius(radius)) + 1), 3);
175}
176
178{
179 return 0.3 * radius + 0.3;
180}
181
183 const QRect &rect,
184 qreal xRadius,
185 qreal yRadius,
187 const QBitArray &channelFlags,
188 KoUpdater *progressUpdater,
189 FilterOutput output,
190 bool writeToAlpha)
191{
192 QPoint srcTopLeft = rect.topLeft();
193 KisPainter finalPainter(device);
194 finalPainter.setChannelFlags(channelFlags);
195 finalPainter.setProgress(progressUpdater);
196 if (output == pythagorean || output == radian) {
197 KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace());
198 KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace());
199
200 x_denormalised->prepareClone(device);
201 y_denormalised->prepareClone(device);
202
203
205 KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type);
206
207 KisConvolutionPainter horizPainterLR(x_denormalised);
208 horizPainterLR.setChannelFlags(channelFlags);
209 horizPainterLR.setProgress(progressUpdater);
210 horizPainterLR.applyMatrix(kernelHorizLeftRight, device,
211 srcTopLeft,
212 srcTopLeft,
213 rect.size(), BORDER_REPEAT);
214
215
216 KisConvolutionPainter verticalPainterTB(y_denormalised);
217 verticalPainterTB.setChannelFlags(channelFlags);
218 verticalPainterTB.setProgress(progressUpdater);
219 verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device,
220 srcTopLeft,
221 srcTopLeft,
222 rect.size(), BORDER_REPEAT);
223
224 KisSequentialIterator yIterator(y_denormalised, rect);
225 KisSequentialIterator xIterator(x_denormalised, rect);
226 KisSequentialIterator finalIt(device, rect);
227 const int pixelSize = device->colorSpace()->pixelSize();
228 const int channels = device->colorSpace()->channelCount();
229 const int alphaPos = device->colorSpace()->alphaPos();
230 KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0);
231
232 QVector<float> yNormalised(channels);
233 QVector<float> xNormalised(channels);
234 QVector<float> finalNorm(channels);
235
236 while(yIterator.nextPixel() && xIterator.nextPixel() && finalIt.nextPixel()) {
237 device->colorSpace()->normalisedChannelsValue(yIterator.rawData(), yNormalised);
238 device->colorSpace()->normalisedChannelsValue(xIterator.rawData(), xNormalised);
239 device->colorSpace()->normalisedChannelsValue(finalIt.rawData(), finalNorm);
240
241 if (output == pythagorean) {
242 for (int c = 0; c<channels; c++) {
243 finalNorm[c] = 2 * sqrt( ((xNormalised[c]-0.5)*(xNormalised[c]-0.5)) + ((yNormalised[c]-0.5)*(yNormalised[c]-0.5)));
244 }
245 } else { //radian
246 for (int c = 0; c<channels; c++) {
247 finalNorm[c] = atan2(xNormalised[c]-0.5, yNormalised[c]-0.5);
248 }
249 }
250
251 if (writeToAlpha) {
252 KoColor col(finalIt.rawData(), device->colorSpace());
253 qreal alpha = 0;
254
255 for (int c = 0; c<(channels-1); c++) {
256 alpha = alpha+finalNorm[c];
257 }
258
259 alpha = qMin(alpha/(channels-1), col.opacityF());
260 col.setOpacity(alpha);
261 memcpy(finalIt.rawData(), col.data(), pixelSize);
262 } else {
263 quint8* f = finalIt.rawData();
264 finalNorm[alphaPos] = 1.0;
265 device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm);
266 memcpy(finalIt.rawData(), f, pixelSize);
267 }
268
269 }
270 } else {
272 bool denormalize = !writeToAlpha;
273 if (output == xGrowth) {
274 kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize);
275 } else if (output == xFall) {
276 kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize, true);
277 } else if (output == yGrowth) {
278 kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize);
279 } else { //yFall
280 kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize, true);
281 }
282
283 if (writeToAlpha) {
284 KisPaintDeviceSP denormalised = new KisPaintDevice(device->colorSpace());
285 denormalised->prepareClone(device);
286
287 KisConvolutionPainter kernelP(denormalised);
288 kernelP.setChannelFlags(channelFlags);
289 kernelP.setProgress(progressUpdater);
290 kernelP.applyMatrix(kernel, device,
291 srcTopLeft, srcTopLeft,
292 rect.size(), BORDER_REPEAT);
293 KisSequentialIterator iterator(denormalised, rect);
294 KisSequentialIterator finalIt(device, rect);
295 const int pixelSize = device->colorSpace()->pixelSize();
296 const int channels = device->colorSpace()->colorChannelCount();
297 QVector<float> normalised(channels);
298 while (iterator.nextPixel() && finalIt.nextPixel()) {
299 device->colorSpace()->normalisedChannelsValue(iterator.rawData(), normalised);
300 KoColor col(finalIt.rawData(), device->colorSpace());
301 qreal alpha = 0;
302 for (int c = 0; c<channels; c++) {
303 alpha = alpha+normalised[c];
304 }
305 alpha = qMin(alpha/channels, col.opacityF());
306 col.setOpacity(alpha);
307 memcpy(finalIt.rawData(), col.data(), pixelSize);
308
309 }
310
311 } else {
312 KisConvolutionPainter kernelP(device);
313 kernelP.setChannelFlags(channelFlags);
314 kernelP.setProgress(progressUpdater);
315 kernelP.applyMatrix(kernel, device,
316 srcTopLeft, srcTopLeft,
317 rect.size(), BORDER_REPEAT);
318
319 KisSequentialIterator finalIt(device, rect);
320 int numConseqPixels = finalIt.nConseqPixels();
321 while (finalIt.nextPixels(numConseqPixels)) {
322 numConseqPixels = finalIt.nConseqPixels();
323 device->colorSpace()->setOpacity(finalIt.rawData(), 1.0, numConseqPixels);
324 }
325 }
326 }
327}
328
330 const QRect &rect,
331 qreal xRadius,
332 qreal yRadius,
334 int channelToConvert,
335 QVector<int> channelOrder,
336 QVector<bool> channelFlip,
337 const QBitArray &channelFlags,
338 KoUpdater *progressUpdater,
339 boost::optional<bool> useFftw)
340{
342
343 QPoint srcTopLeft = rect.topLeft();
344 KisPainter finalPainter(device);
345 finalPainter.setChannelFlags(channelFlags);
346 finalPainter.setProgress(progressUpdater);
347 KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace());
348 KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace());
349 x_denormalised->prepareClone(device);
350 y_denormalised->prepareClone(device);
351
352 KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]);
353 KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]);
354
355 KisConvolutionPainter horizPainterLR(y_denormalised);
356
357 if (useFftw) {
359 }
360
361 horizPainterLR.setChannelFlags(channelFlags);
362 horizPainterLR.setProgress(progressUpdater);
363 horizPainterLR.applyMatrix(kernelHorizLeftRight, device,
364 srcTopLeft, srcTopLeft,
365 rect.size(), BORDER_REPEAT);
366
367
368 KisConvolutionPainter verticalPainterTB(x_denormalised);
369
370 if (useFftw) {
372 }
373
374 verticalPainterTB.setChannelFlags(channelFlags);
375 verticalPainterTB.setProgress(progressUpdater);
376 verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device,
377 srcTopLeft,
378 srcTopLeft,
379 rect.size(), BORDER_REPEAT);
380
381 KisSequentialIterator yIterator(y_denormalised, rect);
382 KisSequentialIterator xIterator(x_denormalised, rect);
383 KisSequentialIterator finalIt(device, rect);
384 const int pixelSize = device->colorSpace()->pixelSize();
385 const int channels = device->colorSpace()->channelCount();
386 const int alphaPos = device->colorSpace()->alphaPos();
387 KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0);
388
389 QVector<float> yNormalised(channels);
390 QVector<float> xNormalised(channels);
391 QVector<float> finalNorm(channels);
392
393 const QList<KoChannelInfo *> channelInfo = device->colorSpace()->channels();
394
395 while(yIterator.nextPixel() && xIterator.nextPixel() && finalIt.nextPixel()) {
396 device->colorSpace()->normalisedChannelsValue(yIterator.rawData(), yNormalised);
397 device->colorSpace()->normalisedChannelsValue(xIterator.rawData(), xNormalised);
398
399 qreal z = 1.0;
400 if (channelFlip[2]==true){
401 z=-1.0;
402 }
403 QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z);
404 normal.normalize();
405 finalNorm.fill(1.0);
406 for (int c = 0; c < 3; c++) {
407 finalNorm[channelInfo.at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5;
408 }
409
410 finalNorm[alphaPos]= 1.0;
411
412 quint8* pixel = finalIt.rawData();
413 device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm);
414 memcpy(finalIt.rawData(), pixel, pixelSize);
415
416 }
417}
The KisConvolutionPainter class applies a convolution kernel to a paint device.
void applyMatrix(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, KisConvolutionBorderOp borderOp=BORDER_REPEAT)
void setEnginePreference(EnginePreference value)
static int kernelSizeFromRadius(qreal radius)
static void applyEdgeDetection(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, FilterType type, const QBitArray &channelFlags, KoUpdater *progressUpdater, FilterOutput output=pythagorean, bool writeToAlpha=false)
applyEdgeDetection This applies the edge detection filter to the device.
static void convertToNormalMap(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, FilterType type, int channelToConvert, QVector< int > channelOrder, QVector< bool > channelFlip, const QBitArray &channelFlags, KoUpdater *progressUpdater, boost::optional< bool > useFftw=boost::none)
convertToNormalMap Convert a channel of the device to a normal map. The channel will be interpreted a...
static KisConvolutionKernelSP createHorizontalKernel(qreal radius, FilterType type, bool denormalize=true, bool reverse=false)
static Eigen::Matrix< qreal, Eigen::Dynamic, Eigen::Dynamic > createVerticalMatrix(qreal radius, FilterType type, bool reverse=false)
createVerticalMatrix
static Eigen::Matrix< qreal, Eigen::Dynamic, Eigen::Dynamic > createHorizontalMatrix(qreal radius, FilterType type, bool reverse=false)
createHorizontalMatrix
static KisConvolutionKernelSP createVerticalKernel(qreal radius, FilterType type, bool denormalize=true, bool reverse=false)
static qreal sigmaFromRadius(qreal radius)
const KoColorSpace * colorSpace() const
void prepareClone(KisPaintDeviceSP src)
void setProgress(KoUpdater *progressUpdater)
void setChannelFlags(QBitArray channelFlags)
ALWAYS_INLINE quint8 * rawData()
virtual quint32 alphaPos() const =0
virtual quint32 pixelSize() const =0
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
QList< KoChannelInfo * > channels
virtual quint32 channelCount() const =0
virtual void normalisedChannelsValue(const quint8 *pixel, QVector< float > &channels) const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
virtual quint32 colorChannelCount() const =0
qreal opacityF() const
Definition KoColor.cpp:345
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 * data()
Definition KoColor.h:144
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
static KisConvolutionKernelSP fromMatrix(Eigen::Matrix< qreal, Eigen::Dynamic, Eigen::Dynamic > matrix, qreal offset, qreal factor)