MyPaintSurface.cpp 18 KB
Newer Older
Ashwin  Dhakaita's avatar
Ashwin Dhakaita committed
1
/*
2
 * SPDX-FileCopyrightText: 2020 Ashwin Dhakaita <ashwingpdhakaita@gmail.com>
Ashwin  Dhakaita's avatar
Ashwin Dhakaita committed
3
 *
4
5
6
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

7
#include "MyPaintSurface.h"
Ashwin  Dhakaita's avatar
Ashwin Dhakaita committed
8

9
#include <KoColorConversions.h>
10
#include <KoColorSpace.h>
11
#include <KoColorSpaceMaths.h>
12
13
#include <QtMath>
#include <kis_algebra_2d.h>
14
#include <kis_cross_device_color_sampler.h>
15
16
17
#include <kis_image.h>
#include <kis_node.h>
#include <kis_sequential_iterator.h>
18
#include <kis_selection.h>
19
#include <qmath.h>
20
#include <KoCompositeOpRegistry.h>
21
#include <KoMixColorsOp.h>
22
23
24

using namespace std;

25
26
27
28
29
void destroy_internal_surface_callback(MyPaintSurface *surface)
{
    KisMyPaintSurface::MyPaintSurfaceInternal *ptr = static_cast<KisMyPaintSurface::MyPaintSurfaceInternal*>(surface);
    delete ptr;
}
30

31
KisMyPaintSurface::KisMyPaintSurface(KisPainter *painter, KisPaintDeviceSP paintNode, KisImageSP image)
32
33
34
    : m_painter(painter)
    , m_imageDevice(paintNode)
    , m_image(image)
35
    , m_precisePainterWrapper(painter->device())
36
    , m_dab(m_precisePainterWrapper.createPreciseCompositionSourceDevice())
Agata Cacko's avatar
Agata Cacko committed
37
    , m_tempPainter(new KisPainter(m_precisePainterWrapper.overlay()))
38
39
    , m_backgroundPainter(new KisPainter(m_precisePainterWrapper.createPreciseCompositionSourceDevice()))

40
{
Agata Cacko's avatar
Agata Cacko committed
41
    m_blendDevice = KisFixedPaintDeviceSP(new KisFixedPaintDevice(m_precisePainterWrapper.overlayColorSpace()));
42

43
44
    m_backgroundPainter->setCompositeOp(COMPOSITE_COPY);
    m_backgroundPainter->setOpacity(OPACITY_OPAQUE_U8);
45
    m_tempPainter->setCompositeOp(COMPOSITE_COPY);
46
47
48
    m_tempPainter->setSelection(painter->selection());
    m_tempPainter->setChannelFlags(painter->channelFlags());
    m_tempPainter->copyMirrorInformationFrom(painter);
49
    m_surface = new MyPaintSurfaceInternal();
50
    mypaint_surface_init(m_surface);
51
    m_surface->m_owner = this;
52
53
54

    m_surface->draw_dab = this->draw_dab;
    m_surface->get_color = this->get_color;
55
    m_surface->destroy = destroy_internal_surface_callback;
Agata Cacko's avatar
Agata Cacko committed
56
    m_surface->bitDepth = m_precisePainterWrapper.overlayColorSpace()->channels()[0]->channelValueType();
57
58
}

59
60
61
KisMyPaintSurface::~KisMyPaintSurface()
{
    mypaint_surface_unref(m_surface);
62
63
}

64
65
66
67
int KisMyPaintSurface::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) {

68
    MyPaintSurfaceInternal *surface = static_cast<MyPaintSurfaceInternal*>(self);
69

70
    if (surface->bitDepth == KoChannelInfo::UINT8) {
71
72
73
74
        return surface->m_owner->drawDabImpl<quint8>(self, x, y, radius, color_r, color_g,
                 color_b, opaque, hardness, color_a,
                aspect_ratio, angle, lock_alpha,  colorize);
    }
75
    else if (surface->bitDepth == KoChannelInfo::UINT16) {
76
77
78
79
80
        return surface->m_owner->drawDabImpl<quint16>(self, x, y, radius, color_r, color_g,
                 color_b, opaque, hardness, color_a,
                aspect_ratio, angle, lock_alpha,  colorize);
    }
#if defined HAVE_OPENEXR
81
    else if (surface->bitDepth == KoChannelInfo::FLOAT16) {
82
83
84
85
86
87
88
89
90
91
        return surface->m_owner->drawDabImpl<half>(self, x, y, radius, color_r, color_g,
                 color_b, opaque, hardness, color_a,
                aspect_ratio, angle, lock_alpha,  colorize);
    }
#endif
    else {
        return surface->m_owner->drawDabImpl<float>(self, x, y, radius, color_r, color_g,
                 color_b, opaque, hardness, color_a,
                aspect_ratio, angle, lock_alpha,  colorize);
    }
92
93
94
95
96
97
}

void KisMyPaintSurface::get_color(MyPaintSurface *self, float x, float y, float radius,
                            float * color_r, float * color_g, float * color_b, float * color_a) {

    MyPaintSurfaceInternal *surface = static_cast<MyPaintSurfaceInternal*>(self);
98
    if (surface->bitDepth == KoChannelInfo::UINT8) {
99
100
        surface->m_owner->getColorImpl<quint8>(self, x, y, radius, color_r, color_g, color_b, color_a);
    }
101
    else if (surface->bitDepth == KoChannelInfo::UINT16) {
102
103
104
        surface->m_owner->getColorImpl<quint16>(self, x, y, radius, color_r, color_g, color_b, color_a);
    }
#if defined HAVE_OPENEXR
105
    else if (surface->bitDepth == KoChannelInfo::FLOAT16) {
106
107
108
109
110
111
        surface->m_owner->getColorImpl<half>(self, x, y, radius, color_r, color_g, color_b, color_a);
    }
#endif
    else {
        surface->m_owner->getColorImpl<float>(self, x, y, radius, color_r, color_g, color_b, color_a);
    }
112
113
114
115
}


/*GIMP's draw_dab and get_color code*/
116
template <typename channelType>
117
118
119
120
int KisMyPaintSurface::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) {

121
    Q_UNUSED(self);
122
    Q_UNUSED(lock_alpha);
123
    const float one_over_radius2 = 1.0f / (radius * radius);
Ashwin  Dhakaita's avatar
Ashwin Dhakaita committed
124
    const double angle_rad = kisDegreesToRadians(angle);
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    const float cs = cos(angle_rad);
    const float sn = sin(angle_rad);
    float normal_mode;
    float segment1_slope;
    float segment2_slope;
    float r_aa_start;

    hardness = CLAMP (hardness, 0.0f, 1.0f);
    segment1_slope = -(1.0f / hardness - 1.0f);
    segment2_slope = -hardness / (1.0f - hardness);
    aspect_ratio = max(1.0f, aspect_ratio);

    r_aa_start = radius - 1.0f;
    r_aa_start = max(r_aa_start, 0.0f);
    r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio;

    normal_mode = opaque * (1.0f - colorize);
    colorize = opaque * colorize;

Ashwin  Dhakaita's avatar
Ashwin Dhakaita committed
144
145
    const QPoint pt = QPoint(x - radius - 1, y - radius - 1);
    const QSize sz = QSize(2 * (radius+1), 2 * (radius+1));
146
147
148
149
150

    const QRect dabRectAligned = QRect(pt, sz);
    const QPointF center = QPointF(x, y);

    KisAlgebra2D::OuterCircle outer(center, radius);
151
    m_precisePainterWrapper.readRects(m_tempPainter->calculateAllMirroredRects(dabRectAligned));
152
    m_tempPainter->copyAreaOptimized(dabRectAligned.topLeft(), m_tempPainter->device(), m_dab, dabRectAligned);
153

154
    KisSequentialIterator it(m_dab, dabRectAligned);
155
156
157
158
    float unitValue = KoColorSpaceMathsTraits<channelType>::unitValue;
    float minValue = KoColorSpaceMathsTraits<channelType>::min;
    float maxValue = KoColorSpaceMathsTraits<channelType>::max;
    bool eraser = painter()->compositeOp()->id() == COMPOSITE_ERASE;
159
160
161
162
163

    while(it.nextPixel()) {

        QPoint pt(it.x(), it.y());

Ashwin  Dhakaita's avatar
Ashwin Dhakaita committed
164
        if(outer.fadeSq(pt) > 1.0f)
165
166
            continue;

167
        float rr, base_alpha, alpha, dst_alpha, r, g, b, a;
168
169
170
171
172
173
174
175
176

        if (radius < 3.0) {
            rr = calculate_rr_antialiased (it.x(), it.y(), x, y, aspect_ratio, sn, cs, one_over_radius2, r_aa_start);
        }
        else {
            rr = calculate_rr (it.x(), it.y(), x, y, aspect_ratio, sn, cs, one_over_radius2);
        }

        base_alpha = calculate_alpha_for_rr (rr, hardness, segment1_slope, segment2_slope);
177
        m_tempPainter->selection();
178
179
        alpha = base_alpha * normal_mode;

180
        channelType* nativeArray = reinterpret_cast<channelType*>(it.rawData());
181

182
183
184
185
        b = nativeArray[0]/unitValue;
        g = nativeArray[1]/unitValue;
        r = nativeArray[2]/unitValue;
        dst_alpha = nativeArray[3]/unitValue;
186

187
188
        if (unitValue == 1.0f) {
            swap(b, r);
189
        }
190
191
192

        a = alpha * (color_a - dst_alpha) + dst_alpha;

193
194
195
196
197
198
199
200
201
202
203
        if (eraser) {
            alpha = 1 - (opaque*base_alpha);
            a = dst_alpha * alpha ;
        } else {
            if (a > 0.0f) {
                float src_term = (alpha * color_a) / a;
                float dst_term = 1.0f - src_term;
                r = color_r * src_term + r * dst_term;
                g = color_g * src_term + g * dst_term;
                b = color_b * src_term + b * dst_term;
            }
204

205
            if (colorize > 0.0f && base_alpha > 0.0f) {
206

207
208
                alpha = base_alpha * colorize;
                a = alpha + dst_alpha - alpha * dst_alpha;
209

210
                if (a > 0.0f) {
211

212
213
                    float pixel_h, pixel_s, pixel_l, out_h, out_s, out_l;
                    float out_r = r, out_g = g, out_b = b;
214

215
216
                    float src_term = alpha / a;
                    float dst_term = 1.0f - src_term;
217

218
219
                    RGBToHSL(color_r, color_g, color_b, &pixel_h, &pixel_s, &pixel_l);
                    RGBToHSL(out_r, out_g, out_b, &out_h, &out_s, &out_l);
220

221
222
                    out_h = pixel_h;
                    out_s = pixel_s;
223

224
                    HSLToRGB(out_h, out_s, out_l, &out_r, &out_g, &out_b);
225

226
227
228
229
                    r = (float)out_r * src_term + r * dst_term;
                    g = (float)out_g * src_term + g * dst_term;
                    b = (float)out_b * src_term + b * dst_term;
                }
230
231
232
            }
        }

233
234
        if (unitValue == 1.0f) {
            swap(b, r);
235
        }
236
237
238
239
        nativeArray[0] = qBound(minValue, b * unitValue, maxValue);
        nativeArray[1] = qBound(minValue, g * unitValue, maxValue);
        nativeArray[2] = qBound(minValue, r * unitValue, maxValue);
        nativeArray[3] = qBound(minValue, a * unitValue, maxValue);
240
    }
241
    m_tempPainter->bitBlt(dabRectAligned.topLeft(), m_dab, dabRectAligned);
242
    // Mirror mode is missing because I cannot figure out how to make a mask for the fixed paintdevice.
243
244
245
    const QVector<QRect> dirtyRects = m_tempPainter->takeDirtyRegion();
    m_precisePainterWrapper.writeRects(dirtyRects);
    painter()->addDirtyRects(dirtyRects);
246
247
248
    return 1;
}

249
template <typename channelType>
250
void KisMyPaintSurface::getColorImpl(MyPaintSurface *self, float x, float y, float radius,
251
                            float * color_r, float * color_g, float * color_b, float * color_a) {
252
    Q_UNUSED(self);
253
254
255
256
257
258
259
260
261
262
263
    if (radius < 1.0f)
        radius = 1.0f;

    *color_r = 0.0f;
    *color_g = 0.0f;
    *color_b = 0.0f;
    *color_a = 0.0f;

    const QPoint pt = QPoint(x - radius, y - radius);
    const QSize sz = QSize(2 * radius, 2 * radius);

264

265
266
267
268
269
    const QRect dabRectAligned = QRect(pt, sz);
    const QPointF center = QPointF(x, y);
    KisAlgebra2D::OuterCircle outer(center, radius);

    const float one_over_radius2 = 1.0f / (radius * radius);
270
    quint32 sum_weight = 0.0f;
271

272
    m_precisePainterWrapper.readRect(dabRectAligned);
Agata Cacko's avatar
Agata Cacko committed
273
    KisPaintDeviceSP activeDev = m_precisePainterWrapper.overlay();
274
    if(m_image) {
275
276
277
        //m_image->blockUpdates();
        m_backgroundPainter->device()->clear();
        m_backgroundPainter->bitBlt(dabRectAligned.topLeft(), activeDev, dabRectAligned);
278
        activeDev = m_backgroundPainter->device();
279
        //m_image->unblockUpdates();
280
    } else if (m_imageDevice) {
281
        m_backgroundPainter->bitBlt(dabRectAligned.topLeft(), m_imageDevice, dabRectAligned);
282
        activeDev = m_backgroundPainter->device();
283
    } else {
284
        m_precisePainterWrapper.readRect(dabRectAligned);
285
286
    }

287
    KisSequentialIterator it(activeDev, dabRectAligned);
288
    QVector<float> surface_color_vec = {0,0,0,0};
289
290
291
292
    float unitValue = KoColorSpaceMathsTraits<channelType>::unitValue;
    float maxValue = KoColorSpaceMathsTraits<channelType>::max;

    quint32 size = dabRectAligned.width() * dabRectAligned.height();
293
294
295
296
297
    m_blendDevice->setRect(dabRectAligned);
    m_blendDevice->lazyGrowBufferWithoutInitialization();


    qint16* weights = new qint16[size];
298
    quint32 num_colors = 0;
299

300
301
    activeDev->readBytes(m_blendDevice->data(), dabRectAligned);

302
303
304
305
    while(it.nextPixel()) {

        QPointF pt(it.x(), it.y());

306
307
308
309
310
        float rr = 0.0;
        if(outer.fadeSq(pt) <= 1.0) {
            /* pixel_weight == a standard dab with hardness = 0.5, aspect_ratio = 1.0, and angle = 0.0 */
            float yy = (it.y() + 0.5f - y);
            float xx = (it.x() + 0.5f - x);
311

312
            rr = qMax((yy * yy + xx * xx) * one_over_radius2, 0.0f);
313
        }
314

315
316
317
        weights[num_colors] = qRound((1.0f - rr) * 255);
        sum_weight += weights[num_colors];
        num_colors += 1;
318
319
    }

320
    KoColor color(Qt::transparent, activeDev->colorSpace());
321
    activeDev->colorSpace()->mixColorsOp()->mixColors(m_blendDevice->data(), weights, size, color.data(), sum_weight);
322

323
324
325
    if (sum_weight > 0.0f) {
        qreal r, g, b, a;
        channelType* nativeArray = reinterpret_cast<channelType*>(color.data());
326

327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
        if (unitValue == 1.0f) {
            *color_r = nativeArray[0];
            *color_g = nativeArray[1];
            *color_b = nativeArray[2];
            *color_a = nativeArray[3];
        } else {
            b = nativeArray[0]/maxValue;
            g = nativeArray[1]/maxValue;
            r = nativeArray[2]/maxValue;
            a = nativeArray[3]/maxValue;
            *color_r = CLAMP(r, 0.0f, 1.0f);
            *color_g = CLAMP(g, 0.0f, 1.0f);
            *color_b = CLAMP(b, 0.0f, 1.0f);
            *color_a = CLAMP(a, 0.0f, 1.0f);
        }
342
    }
343
344

    delete [] weights;
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
}

KisPainter* KisMyPaintSurface::painter() {
    return m_painter;
}

MyPaintSurface* KisMyPaintSurface::surface() {
    return m_surface;
}

/*mypaint code*/
qreal KisMyPaintSurface::calculateOpacity(float angle, float hardness, float opaque, float x, float y,
                                        float xp, float yp, float aspect_ratio, float radius) {

    qreal cs = cos(angle/360*2*M_PI);
    qreal sn = sin(angle/360*2*M_PI);

    qreal dx = xp - x;
    qreal dy = yp - y;
    qreal dyr = (dy*cs-dx*sn)*aspect_ratio;
    qreal dxr = (dy*sn+dx*cs);
    qreal dd = (dyr*dyr + dxr*dxr) / (radius*radius);
    qreal opa;

    if (dd > 1)
        opa = 0;
    else if (dd < hardness)
        opa = dd + 1-(dd/hardness);
    else
        opa = hardness/(1-hardness)*(1-dd);

    qreal pixel_opacity = opa * opaque;
    return pixel_opacity;
}

inline float KisMyPaintSurface::calculate_rr (int  xp,
              int   yp,
              float x,
              float y,
              float aspect_ratio,
              float sn,
              float cs,
              float one_over_radius2) {

    const float yy = (yp + 0.5f - y);
    const float xx = (xp + 0.5f - x);
    const float yyr=(yy*cs-xx*sn)*aspect_ratio;
    const float xxr=yy*sn+xx*cs;
    const float rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
    /* rr is in range 0.0..1.0*sqrt(2) */
    return rr;
}

static inline float
calculate_r_sample (float x, float y, float aspect_ratio, float sn, float cs) {

    const float yyr=(y*cs-x*sn)*aspect_ratio;
    const float xxr=y*sn+x*cs;
    const float r = (yyr*yyr + xxr*xxr);
    return r;
}

static inline float
sign_point_in_line (float px, float py, float vx, float vy) {

    return (px - vx) * (-vy) - (vx) * (py - vy);
}

static inline void
closest_point_to_line (float  lx, float  ly, float  px, float  py, float *ox, float *oy) {

    const float l2 = lx*lx + ly*ly;
    const float ltp_dot = px*lx + py*ly;
    const float t = ltp_dot / l2;
    *ox = lx * t;
    *oy = ly * t;
}


/* This works by taking the visibility at the nearest point
 * and dividing by 1.0 + delta.
 *
 * - nearest point: point where the dab has more influence
 * - farthest point: point at a fixed distance away from
 *                   the nearest point
 * - delta: how much occluded is the farthest point relative
 *          to the nearest point
 */
inline float KisMyPaintSurface::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) {

    /* calculate pixel position and borders in a way
     * that the dab's center is always at zero */
    float pixel_right = x - (float)xp;
    float pixel_bottom = y - (float)yp;
    float pixel_center_x = pixel_right - 0.5f;
    float pixel_center_y = pixel_bottom - 0.5f;
    float pixel_left = pixel_right - 1.0f;
    float pixel_top = pixel_bottom - 1.0f;

    float nearest_x, nearest_y; /* nearest to origin, but still inside pixel */
    float farthest_x, farthest_y; /* farthest from origin, but still inside pixel */
    float r_near, r_far, rr_near, rr_far;
    float center_sign, rad_area_1, visibilityNear, delta, delta2;

    /* Dab's center is inside pixel? */
    if( pixel_left<0 && pixel_right>0 &&
        pixel_top<0 && pixel_bottom>0 )
    {
        nearest_x = 0;
        nearest_y = 0;
        r_near = rr_near = 0;
    }
    else
    {
        closest_point_to_line( cs, sn, pixel_center_x, pixel_center_y, &nearest_x, &nearest_y );
        nearest_x = CLAMP( nearest_x, pixel_left, pixel_right );
        nearest_y = CLAMP( nearest_y, pixel_top, pixel_bottom );
        /* XXX: precision of "nearest" values could be improved
         * by intersecting the line that goes from nearest_x/Y to 0
         * with the pixel's borders here, however the improvements
         * would probably not justify the perdormance cost.
         */
        r_near = calculate_r_sample( nearest_x, nearest_y, aspect_ratio, sn, cs );
        rr_near = r_near * one_over_radius2;
    }

    /* out of dab's reach? */
    if( rr_near > 1.0f )
        return rr_near;

    /* check on which side of the dab's line is the pixel center */
    center_sign = sign_point_in_line( pixel_center_x, pixel_center_y, cs, -sn );

    /* radius of a circle with area=1
     *   A = pi * r * r
     *   r = sqrt(1/pi)
     */
    rad_area_1 = sqrtf( 1.0f / M_PI );

    /* center is below dab */
    if( center_sign < 0 )
    {
        farthest_x = nearest_x - sn*rad_area_1;
        farthest_y = nearest_y + cs*rad_area_1;
    }
    /* above dab */
    else
    {
        farthest_x = nearest_x + sn*rad_area_1;
        farthest_y = nearest_y - cs*rad_area_1;
    }

    r_far = calculate_r_sample( farthest_x, farthest_y, aspect_ratio, sn, cs );
    rr_far = r_far * one_over_radius2;

    /* check if we can skip heavier AA */
    if( r_far < r_aa_start )
        return (rr_far+rr_near) * 0.5f;

    /* calculate AA approximate */
    visibilityNear = 1.0f - rr_near;
    delta = rr_far - rr_near;
    delta2 = 1.0f + delta;
    visibilityNear /= delta2;

    return 1.0f - visibilityNear;
}
/* -- end mypaint code */

inline float KisMyPaintSurface::calculate_alpha_for_rr (float rr, float hardness, float slope1, float slope2) {

  if (rr > 1.0f)
    return 0.0f;
  else if (rr <= hardness)
    return 1.0f + rr * slope1;
  else
    return rr * slope2 - slope2;
}