Krita Source Code Documentation
Loading...
Searching...
No Matches
MyPaintSurface.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Ashwin Dhakaita <ashwingpdhakaita@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "MyPaintSurface.h"
8
10#include <KoColorSpace.h>
11#include <KoColorSpaceMaths.h>
12#include <QtMath>
13#include <kis_algebra_2d.h>
15#include <kis_image.h>
16#include <kis_node.h>
18#include <kis_selection.h>
19#include <qmath.h>
21#include <KoMixColorsOp.h>
22
23using namespace std;
24
25void destroy_internal_surface_callback(MyPaintSurface *surface)
26{
28 delete ptr;
29}
30
32 : m_painter(painter)
33 , m_imageDevice(paintNode)
34 , m_image(image)
35 , m_precisePainterWrapper(painter->device())
36 , m_dab(m_precisePainterWrapper.createPreciseCompositionSourceDevice())
37 , m_tempPainter(new KisPainter(m_precisePainterWrapper.overlay()))
38 , m_backgroundPainter(new KisPainter(m_precisePainterWrapper.createPreciseCompositionSourceDevice()))
39{
41
42 m_backgroundPainter->setCompositeOpId(COMPOSITE_COPY);
43 m_backgroundPainter->setOpacityToUnit();
44 m_tempPainter->setCompositeOpId(COMPOSITE_COPY);
45 m_tempPainter->setSelection(painter->selection());
46 m_tempPainter->setChannelFlags(painter->channelFlags());
47 m_tempPainter->copyMirrorInformationFrom(painter);
49 mypaint_surface_init(m_surface);
50 m_surface->m_owner = this;
51
52 m_surface->draw_dab = this->draw_dab;
53 m_surface->get_color = this->get_color;
56
57 // devices for mask information
58 static const KoColorSpace *maskCs = KoColorSpaceRegistry::instance()->alpha8();
60}
61
63{
64 mypaint_surface_unref(m_surface);
65}
66
67int KisMyPaintSurface::draw_dab(MyPaintSurface *self, float x, float y, float radius, float color_r, float color_g,
68 float color_b, float opaque, float hardness, float color_a,
69 float aspect_ratio, float angle, float lock_alpha, float colorize) {
70
72
73 if (surface->bitDepth == KoChannelInfo::UINT8) {
74 return surface->m_owner->drawDabImpl<quint8>(self, x, y, radius, color_r, color_g,
75 color_b, opaque, hardness, color_a,
76 aspect_ratio, angle, lock_alpha, colorize);
77 }
78 else if (surface->bitDepth == KoChannelInfo::UINT16) {
79 return surface->m_owner->drawDabImpl<quint16>(self, x, y, radius, color_r, color_g,
80 color_b, opaque, hardness, color_a,
81 aspect_ratio, angle, lock_alpha, colorize);
82 }
83#if defined HAVE_OPENEXR
84 else if (surface->bitDepth == KoChannelInfo::FLOAT16) {
85 return surface->m_owner->drawDabImpl<half>(self, x, y, radius, color_r, color_g,
86 color_b, opaque, hardness, color_a,
87 aspect_ratio, angle, lock_alpha, colorize);
88 }
89#endif
90 else {
91 return surface->m_owner->drawDabImpl<float>(self, x, y, radius, color_r, color_g,
92 color_b, opaque, hardness, color_a,
93 aspect_ratio, angle, lock_alpha, colorize);
94 }
95}
96
97void KisMyPaintSurface::get_color(MyPaintSurface *self, float x, float y, float radius,
98 float * color_r, float * color_g, float * color_b, float * color_a) {
99
101 if (surface->bitDepth == KoChannelInfo::UINT8) {
102 surface->m_owner->getColorImpl<quint8>(self, x, y, radius, color_r, color_g, color_b, color_a);
103 }
104 else if (surface->bitDepth == KoChannelInfo::UINT16) {
105 surface->m_owner->getColorImpl<quint16>(self, x, y, radius, color_r, color_g, color_b, color_a);
106 }
107#if defined HAVE_OPENEXR
108 else if (surface->bitDepth == KoChannelInfo::FLOAT16) {
109 surface->m_owner->getColorImpl<half>(self, x, y, radius, color_r, color_g, color_b, color_a);
110 }
111#endif
112 else {
113 surface->m_owner->getColorImpl<float>(self, x, y, radius, color_r, color_g, color_b, color_a);
114 }
115}
116
117
118/*GIMP's draw_dab and get_color code*/
119template <typename channelType>
120int KisMyPaintSurface::drawDabImpl(MyPaintSurface *self, float x, float y, float radius, float color_r, float color_g,
121 float color_b, float opaque, float hardness, float color_a,
122 float aspect_ratio, float angle, float lock_alpha, float colorize) {
123
124 Q_UNUSED(self);
125 Q_UNUSED(lock_alpha);
126 const float one_over_radius2 = 1.0f / (radius * radius);
127 const double angle_rad = kisDegreesToRadians(angle);
128 const float cs = cos(angle_rad);
129 const float sn = sin(angle_rad);
130 float normal_mode;
131 float segment1_slope;
132 float segment2_slope;
133 float r_aa_start;
134
135 hardness = CLAMP (hardness, 0.0f, 1.0f);
136 segment1_slope = -(1.0f / hardness - 1.0f);
137 segment2_slope = -hardness / (1.0f - hardness);
138 aspect_ratio = max(1.0f, aspect_ratio);
139
140 r_aa_start = radius - 1.0f;
141 r_aa_start = max(r_aa_start, 0.0f);
142 r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio;
143
144 normal_mode = opaque * (1.0f - colorize);
145 colorize = opaque * colorize;
146
147 const QPoint pt = QPoint(x - radius - 1, y - radius - 1);
148 const QSize sz = QSize(2 * (radius+1), 2 * (radius+1));
149
150 const QRect dabRectAligned = QRect(pt, sz);
151 const QPointF center = QPointF(x, y);
152
153 KisAlgebra2D::OuterCircle outer(center, radius);
154 m_precisePainterWrapper.readRects(m_tempPainter->calculateAllMirroredRects(dabRectAligned));
155 m_tempPainter->copyAreaOptimized(dabRectAligned.topLeft(), m_tempPainter->device(), m_dab, dabRectAligned);
156 KisSequentialIterator it(m_dab, dabRectAligned);
157
158 quint8 maskUnitValue = KoColorSpaceMathsTraits<quint8>::unitValue; // because it's alpha8
159
162 bool eraser = painter()->compositeOpId() == COMPOSITE_ERASE;
163
164
165 m_maskDevice->setRect(dabRectAligned);
167
168
169 // Dmitry says that going with the pointer should be in the same order
170 // as using the sequential iterator
171 quint8* maskPointer = m_maskDevice->data();
172
173
174 while(it.nextPixel()) {
175
176 // first initialize to 0;
177 *maskPointer = 0;
178
179 QPoint pt(it.x(), it.y());
180
181 if(outer.fadeSq(pt) > 1.0f) {
182 maskPointer++;
183 continue;
184 }
185
186 float rr, base_alpha, alpha, dst_alpha, r, g, b, a;
187
188 if (radius < 3.0) {
189 rr = calculate_rr_antialiased (it.x(), it.y(), x, y, aspect_ratio, sn, cs, one_over_radius2, r_aa_start);
190 }
191 else {
192 rr = calculate_rr (it.x(), it.y(), x, y, aspect_ratio, sn, cs, one_over_radius2);
193 }
194
195 base_alpha = calculate_alpha_for_rr (rr, hardness, segment1_slope, segment2_slope);
196
197 m_tempPainter->selection();
198 alpha = base_alpha * normal_mode;
199
200 // set alpha to mask
201 if (alpha > minValue) {
202 *maskPointer = (quint8)(maskUnitValue);
203 }
204
205 channelType* nativeArray = reinterpret_cast<channelType*>(it.rawData());
206
207 b = nativeArray[0]/unitValue;
208 g = nativeArray[1]/unitValue;
209 r = nativeArray[2]/unitValue;
210 dst_alpha = nativeArray[3]/unitValue;
211
212 if (unitValue == 1.0f) {
213 swap(b, r);
214 }
215
216 a = alpha * (color_a - dst_alpha) + dst_alpha;
217
218 if (eraser) {
219 alpha = 1 - (opaque*base_alpha);
220 a = dst_alpha * alpha ;
221 } else {
222 if (a > 0.0f) {
223 float src_term = (alpha * color_a) / a;
224 float dst_term = 1.0f - src_term;
225 r = color_r * src_term + r * dst_term;
226 g = color_g * src_term + g * dst_term;
227 b = color_b * src_term + b * dst_term;
228 }
229
230 if (colorize > 0.0f && base_alpha > 0.0f) {
231
232 alpha = base_alpha * colorize;
233 a = alpha + dst_alpha - alpha * dst_alpha;
234
235 if (a > 0.0f) {
236
237 float pixel_h, pixel_s, pixel_l, out_h, out_s, out_l;
238 float out_r = r, out_g = g, out_b = b;
239
240 float src_term = alpha / a;
241 float dst_term = 1.0f - src_term;
242
243 RGBToHSL(color_r, color_g, color_b, &pixel_h, &pixel_s, &pixel_l);
244 RGBToHSL(out_r, out_g, out_b, &out_h, &out_s, &out_l);
245
246 out_h = pixel_h;
247 out_s = pixel_s;
248
249 HSLToRGB(out_h, out_s, out_l, &out_r, &out_g, &out_b);
250
251 r = (float)out_r * src_term + r * dst_term;
252 g = (float)out_g * src_term + g * dst_term;
253 b = (float)out_b * src_term + b * dst_term;
254 }
255 }
256 }
257
258 if (unitValue == 1.0f) {
259 swap(b, r);
260 }
265
266 maskPointer++;
267 }
268
269
270 m_tempPainter->bitBltWithFixedSelection(dabRectAligned.x(), dabRectAligned.y(), m_dab, m_maskDevice, dabRectAligned.x(), dabRectAligned.y(), dabRectAligned.x(), dabRectAligned.y(), dabRectAligned.width(), dabRectAligned.height());
271 m_tempPainter->renderMirrorMask(dabRectAligned, m_dab, dabRectAligned.x(), dabRectAligned.y(), m_maskDevice);
272 const QVector<QRect> dirtyRects = m_tempPainter->takeDirtyRegion();
274 painter()->addDirtyRects(dirtyRects);
275 return 1;
276}
277
278template <typename channelType>
279void KisMyPaintSurface::getColorImpl(MyPaintSurface *self, float x, float y, float radius,
280 float * color_r, float * color_g, float * color_b, float * color_a) {
281 Q_UNUSED(self);
282 if (radius < 1.0f)
283 radius = 1.0f;
284
285 *color_r = 0.0f;
286 *color_g = 0.0f;
287 *color_b = 0.0f;
288 *color_a = 0.0f;
289
290 const QPoint pt = QPoint(x - radius, y - radius);
291 const QSize sz = QSize(2 * radius, 2 * radius);
292
293
294 const QRect dabRectAligned = QRect(pt, sz);
295 const QPointF center = QPointF(x, y);
296 KisAlgebra2D::OuterCircle outer(center, radius);
297
298 const float one_over_radius2 = 1.0f / (radius * radius);
299 quint32 sum_weight = 0.0f;
300
301 m_precisePainterWrapper.readRect(dabRectAligned);
303 if(m_image) {
304 //m_image->blockUpdates();
305 m_backgroundPainter->device()->clear();
306 m_backgroundPainter->bitBlt(dabRectAligned.topLeft(), activeDev, dabRectAligned);
307 activeDev = m_backgroundPainter->device();
308 //m_image->unblockUpdates();
309 } else if (m_imageDevice) {
310 m_backgroundPainter->bitBlt(dabRectAligned.topLeft(), m_imageDevice, dabRectAligned);
311 activeDev = m_backgroundPainter->device();
312 } else {
313 m_precisePainterWrapper.readRect(dabRectAligned);
314 }
315
316 KisSequentialIterator it(activeDev, dabRectAligned);
317 QVector<float> surface_color_vec = {0,0,0,0};
320
321 quint32 size = dabRectAligned.width() * dabRectAligned.height();
322 m_blendDevice->setRect(dabRectAligned);
324
325
326 qint16* weights = new qint16[size];
327 quint32 num_colors = 0;
328
329 activeDev->readBytes(m_blendDevice->data(), dabRectAligned);
330
331 while(it.nextPixel()) {
332
333 QPointF pt(it.x(), it.y());
334
335 float rr = 0.0;
336 if(outer.fadeSq(pt) <= 1.0) {
337 /* pixel_weight == a standard dab with hardness = 0.5, aspect_ratio = 1.0, and angle = 0.0 */
338 float yy = (it.y() + 0.5f - y);
339 float xx = (it.x() + 0.5f - x);
340
341 rr = qMax((yy * yy + xx * xx) * one_over_radius2, 0.0f);
342 }
343
344 weights[num_colors] = qRound((1.0f - rr) * 255);
345 sum_weight += weights[num_colors];
346 num_colors += 1;
347 }
348
349 KoColor color = KoColor::createTransparent(activeDev->colorSpace());
350 activeDev->colorSpace()->mixColorsOp()->mixColors(m_blendDevice->data(), weights, size, color.data(), sum_weight);
351
352 if (sum_weight > 0.0f) {
353 qreal r, g, b, a;
354 channelType* nativeArray = reinterpret_cast<channelType*>(color.data());
355
356 if (unitValue == 1.0f) {
357 *color_r = nativeArray[0];
358 *color_g = nativeArray[1];
359 *color_b = nativeArray[2];
360 *color_a = nativeArray[3];
361 } else {
362 b = nativeArray[0]/maxValue;
363 g = nativeArray[1]/maxValue;
364 r = nativeArray[2]/maxValue;
365 a = nativeArray[3]/maxValue;
366 *color_r = CLAMP(r, 0.0f, 1.0f);
367 *color_g = CLAMP(g, 0.0f, 1.0f);
368 *color_b = CLAMP(b, 0.0f, 1.0f);
369 *color_a = CLAMP(a, 0.0f, 1.0f);
370 }
371 }
372
373 delete [] weights;
374}
375
379
380MyPaintSurface* KisMyPaintSurface::surface() {
381 return m_surface;
382}
383
384/*mypaint code*/
385qreal KisMyPaintSurface::calculateOpacity(float angle, float hardness, float opaque, float x, float y,
386 float xp, float yp, float aspect_ratio, float radius) {
387
388 qreal cs = cos(angle/360*2*M_PI);
389 qreal sn = sin(angle/360*2*M_PI);
390
391 qreal dx = xp - x;
392 qreal dy = yp - y;
393 qreal dyr = (dy*cs-dx*sn)*aspect_ratio;
394 qreal dxr = (dy*sn+dx*cs);
395 qreal dd = (dyr*dyr + dxr*dxr) / (radius*radius);
396 qreal opa;
397
398 if (dd > 1)
399 opa = 0;
400 else if (dd < hardness)
401 opa = dd + 1-(dd/hardness);
402 else
403 opa = hardness/(1-hardness)*(1-dd);
404
405 qreal pixel_opacity = opa * opaque;
406 return pixel_opacity;
407}
408
410 int yp,
411 float x,
412 float y,
413 float aspect_ratio,
414 float sn,
415 float cs,
416 float one_over_radius2) {
417
418 const float yy = (yp + 0.5f - y);
419 const float xx = (xp + 0.5f - x);
420 const float yyr=(yy*cs-xx*sn)*aspect_ratio;
421 const float xxr=yy*sn+xx*cs;
422 const float rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
423 /* rr is in range 0.0..1.0*sqrt(2) */
424 return rr;
425}
426
427static inline float
428calculate_r_sample (float x, float y, float aspect_ratio, float sn, float cs) {
429
430 const float yyr=(y*cs-x*sn)*aspect_ratio;
431 const float xxr=y*sn+x*cs;
432 const float r = (yyr*yyr + xxr*xxr);
433 return r;
434}
435
436static inline float
437sign_point_in_line (float px, float py, float vx, float vy) {
438
439 return (px - vx) * (-vy) - (vx) * (py - vy);
440}
441
442static inline void
443closest_point_to_line (float lx, float ly, float px, float py, float *ox, float *oy) {
444
445 const float l2 = lx*lx + ly*ly;
446 const float ltp_dot = px*lx + py*ly;
447 const float t = ltp_dot / l2;
448 *ox = lx * t;
449 *oy = ly * t;
450}
451
452
453/* This works by taking the visibility at the nearest point
454 * and dividing by 1.0 + delta.
455 *
456 * - nearest point: point where the dab has more influence
457 * - farthest point: point at a fixed distance away from
458 * the nearest point
459 * - delta: how much occluded is the farthest point relative
460 * to the nearest point
461 */
462inline float KisMyPaintSurface::calculate_rr_antialiased (int xp, int yp, float x, float y,
463 float aspect_ratio, float sn, float cs, float one_over_radius2,
464 float r_aa_start) {
465
466 /* calculate pixel position and borders in a way
467 * that the dab's center is always at zero */
468 float pixel_right = x - (float)xp;
469 float pixel_bottom = y - (float)yp;
470 float pixel_center_x = pixel_right - 0.5f;
471 float pixel_center_y = pixel_bottom - 0.5f;
472 float pixel_left = pixel_right - 1.0f;
473 float pixel_top = pixel_bottom - 1.0f;
474
475 float nearest_x, nearest_y; /* nearest to origin, but still inside pixel */
476 float farthest_x, farthest_y; /* farthest from origin, but still inside pixel */
477 float r_near, r_far, rr_near, rr_far;
478 float center_sign, rad_area_1, visibilityNear, delta, delta2;
479
480 /* Dab's center is inside pixel? */
481 if( pixel_left<0 && pixel_right>0 &&
482 pixel_top<0 && pixel_bottom>0 )
483 {
484 nearest_x = 0;
485 nearest_y = 0;
486 r_near = rr_near = 0;
487 }
488 else
489 {
490 closest_point_to_line( cs, sn, pixel_center_x, pixel_center_y, &nearest_x, &nearest_y );
491 nearest_x = CLAMP( nearest_x, pixel_left, pixel_right );
492 nearest_y = CLAMP( nearest_y, pixel_top, pixel_bottom );
493 /* XXX: precision of "nearest" values could be improved
494 * by intersecting the line that goes from nearest_x/Y to 0
495 * with the pixel's borders here, however the improvements
496 * would probably not justify the performance cost.
497 */
498 r_near = calculate_r_sample( nearest_x, nearest_y, aspect_ratio, sn, cs );
499 rr_near = r_near * one_over_radius2;
500 }
501
502 /* out of dab's reach? */
503 if( rr_near > 1.0f )
504 return rr_near;
505
506 /* check on which side of the dab's line is the pixel center */
507 center_sign = sign_point_in_line( pixel_center_x, pixel_center_y, cs, -sn );
508
509 /* radius of a circle with area=1
510 * A = pi * r * r
511 * r = sqrt(1/pi)
512 */
513 rad_area_1 = sqrtf( 1.0f / M_PI );
514
515 /* center is below dab */
516 if( center_sign < 0 )
517 {
518 farthest_x = nearest_x - sn*rad_area_1;
519 farthest_y = nearest_y + cs*rad_area_1;
520 }
521 /* above dab */
522 else
523 {
524 farthest_x = nearest_x + sn*rad_area_1;
525 farthest_y = nearest_y - cs*rad_area_1;
526 }
527
528 r_far = calculate_r_sample( farthest_x, farthest_y, aspect_ratio, sn, cs );
529 rr_far = r_far * one_over_radius2;
530
531 /* check if we can skip heavier AA */
532 if( r_far < r_aa_start )
533 return (rr_far+rr_near) * 0.5f;
534
535 /* calculate AA approximate */
536 visibilityNear = 1.0f - rr_near;
537 delta = rr_far - rr_near;
538 delta2 = 1.0f + delta;
539 visibilityNear /= delta2;
540
541 return 1.0f - visibilityNear;
542}
543/* -- end mypaint code */
544
545inline float KisMyPaintSurface::calculate_alpha_for_rr (float rr, float hardness, float slope1, float slope2) {
546
547 if (rr > 1.0f)
548 return 0.0f;
549 else if (rr <= hardness)
550 return 1.0f + rr * slope1;
551 else
552 return rr * slope2 - slope2;
553}
void RGBToHSL(float r, float g, float b, float *h, float *s, float *l)
void HSLToRGB(float h, float sl, float l, float *r, float *g, float *b)
const QString COMPOSITE_COPY
const QString COMPOSITE_ERASE
static void closest_point_to_line(float lx, float ly, float px, float py, float *ox, float *oy)
void destroy_internal_surface_callback(MyPaintSurface *surface)
static float sign_point_in_line(float px, float py, float vx, float vy)
static float calculate_r_sample(float x, float y, float aspect_ratio, float sn, float cs)
qreal fadeSq(const QPointF &pt) const
void setRect(const QRect &rc)
KisPaintDeviceSP m_imageDevice
float calculate_alpha_for_rr(float rr, float hardness, float slope1, float slope2)
float calculate_rr_antialiased(int xp, int yp, float x, float y, float aspect_ratio, float sn, float cs, float one_over_radius2, float r_aa_start)
qreal calculateOpacity(float angle, float hardness, float opaque, float x, float y, float xp, float yp, float aspect_ratio, float radius)
MyPaintSurface * surface()
void getColorImpl(MyPaintSurface *self, float x, float y, float radius, float *color_r, float *color_g, float *color_b, float *color_a)
static void get_color(MyPaintSurface *self, float x, float y, float radius, float *color_r, float *color_g, float *color_b, float *color_a)
KisPainter * m_painter
KisOverlayPaintDeviceWrapper m_precisePainterWrapper
KisFixedPaintDeviceSP m_maskDevice
KisPainter * painter()
KisFixedPaintDeviceSP m_blendDevice
QScopedPointer< KisPainter > m_tempPainter
QScopedPointer< KisPainter > m_backgroundPainter
static int draw_dab(MyPaintSurface *self, float x, float y, float radius, float color_r, float color_g, float color_b, float opaque, float hardness, float color_a, float aspect_ratio, float angle, float lock_alpha, float colorize)
KisPaintDeviceSP m_dab
KisMyPaintSurface(KisPainter *painter, KisPaintDeviceSP paintNode=nullptr, KisImageSP image=nullptr)
float calculate_rr(int xp, int yp, float x, float y, float aspect_ratio, float sn, float cs, float one_over_radius2)
MyPaintSurfaceInternal * m_surface
int drawDabImpl(MyPaintSurface *self, float x, float y, float radius, float color_r, float color_g, float color_b, float opaque, float hardness, float color_a, float aspect_ratio, float angle, float lock_alpha, float colorize)
void writeRects(const QVector< QRect > &rects, int index=0)
KisPaintDeviceSP overlay(int index=0) const
void readRects(const QVector< QRect > &rects)
const KoColorSpace * overlayColorSpace() const
virtual void clear()
const KoColorSpace * colorSpace() const
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
QString compositeOpId
void addDirtyRects(const QVector< QRect > &rects)
QBitArray channelFlags()
KisSelectionSP selection
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE int y() const
@ UINT8
use this for an unsigned integer 8bits channel
@ UINT16
use this for an integer 16bits channel
@ FLOAT16
use this for a float 16bits channel
static _Tdst scaleToA(_T a)
QList< KoChannelInfo * > channels
KoMixColorsOp * mixColorsOp
static KoColor createTransparent(const KoColorSpace *cs)
Definition KoColor.cpp:681
quint8 * data()
Definition KoColor.h:144
virtual void mixColors(const quint8 *const *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum=255) const =0
#define CLAMP(x, l, h)
T kisDegreesToRadians(T degrees)
Definition kis_global.h:176
#define M_PI
Definition kis_global.h:111
KisSharedPtr< KisFixedPaintDevice > KisFixedPaintDeviceSP
Definition kis_types.h:79
KoChannelInfo::enumChannelValueType bitDepth
static KoColorSpaceRegistry * instance()
const KoColorSpace * alpha8()