1 /* libmypaint - The MyPaint Brush Library
2  * Copyright (C) 2007-2014 Martin Renold <martinxyz@gmx.ch> et. al.
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "config.h"
18 
19 #include <math.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <assert.h>
24 
25 #ifdef _OPENMP
26 #include <omp.h>
27 #endif
28 
29 #include "mypaint-config.h"
30 #include "mypaint-tiled-surface.h"
31 #include "tiled-surface-private.h"
32 #include "helpers.h"
33 #include "brushmodes.h"
34 #include "operationqueue.h"
35 
36 #define NUM_BBOXES_DEFAULT 32
37 
38 
39 /**
40  * MyPaintTiledSurface:
41  *
42  * Testing if this comment ends up in the gir.
43  */
44 struct MyPaintTiledSurface;
45 
46 void tiled_surface_process_tile(MyPaintTiledSurface *self, int tx, int ty);
47 
48 void process_tile_internal(
49     void* tiled_surface, void (*request_start)(void*, void*), void (*request_end)(void*, void*),
50     OperationQueue* op_queue, int tx, int ty);
51 
52 static void
begin_atomic_default(MyPaintSurface * surface)53 begin_atomic_default(MyPaintSurface *surface)
54 {
55     mypaint_tiled_surface_begin_atomic((MyPaintTiledSurface *)surface);
56 }
57 
58 static void
end_atomic_default(MyPaintSurface * surface,MyPaintRectangle * roi)59 end_atomic_default(MyPaintSurface *surface, MyPaintRectangle *roi)
60 {
61     mypaint_tiled_surface_end_atomic((MyPaintTiledSurface *)surface, roi);
62 }
63 
64 /**
65  * mypaint_tiled_surface_begin_atomic: (skip)
66  */
67 void
mypaint_tiled_surface_begin_atomic(MyPaintTiledSurface * self)68 mypaint_tiled_surface_begin_atomic(MyPaintTiledSurface *self)
69 {
70   self->dirty_bbox.x = 0;
71   self->dirty_bbox.y = 0;
72   self->dirty_bbox.width = 0;
73   self->dirty_bbox.height = 0;
74 }
75 
76 /**
77  * mypaint_tiled_surface_end_atomic: (skip)
78  *
79  * Implementation of #MyPaintSurface::end_atomic vfunc
80  * Note: Only intended to be used from #MyPaintTiledSurface subclasses, which should chain up to this
81  * if implementing their own #MyPaintSurface::end_atomic vfunc.
82  * Application code should only use mypaint_surface_end_atomic().
83  */
84 void
mypaint_tiled_surface_end_atomic(MyPaintTiledSurface * self,MyPaintRectangle * roi)85 mypaint_tiled_surface_end_atomic(MyPaintTiledSurface *self, MyPaintRectangle *roi)
86 {
87     // Process tiles
88     TileIndex *tiles;
89     int tiles_n = operation_queue_get_dirty_tiles(self->operation_queue, &tiles);
90 
91     #pragma omp parallel for schedule(static) if(self->threadsafe_tile_requests && tiles_n > 3)
92     for (int i = 0; i < tiles_n; i++) {
93       tiled_surface_process_tile(self, tiles[i].x, tiles[i].y);
94     }
95 
96     operation_queue_clear_dirty_tiles(self->operation_queue);
97 
98     if (roi) {
99         *roi = self->dirty_bbox;
100     }
101 }
102 
103 
104 /**
105  * mypaint_tiled_surface_tile_request_start:
106  */
mypaint_tiled_surface_tile_request_start(MyPaintTiledSurface * self,MyPaintTileRequest * request)107 void mypaint_tiled_surface_tile_request_start(MyPaintTiledSurface *self, MyPaintTileRequest *request)
108 {
109     assert(self->tile_request_start);
110     self->tile_request_start(self, request);
111 }
112 
113 /**
114  * mypaint_tiled_surface_tile_request_end:
115  */
mypaint_tiled_surface_tile_request_end(MyPaintTiledSurface * self,MyPaintTileRequest * request)116 void mypaint_tiled_surface_tile_request_end(MyPaintTiledSurface *self, MyPaintTileRequest *request)
117 {
118     assert(self->tile_request_end);
119     self->tile_request_end(self, request);
120 }
121 
122 /**
123  * mypaint_tiled_surface_set_symmetry_state:
124  * @active: TRUE to enable, FALSE to disable.
125  * @center_x: X axis to mirror events across.
126  *
127  * Enable/Disable symmetric brush painting across an X axis.
128  */
129 void
mypaint_tiled_surface_set_symmetry_state(MyPaintTiledSurface * self,gboolean active,float center_x)130 mypaint_tiled_surface_set_symmetry_state(MyPaintTiledSurface *self, gboolean active, float center_x)
131 {
132     self->surface_do_symmetry = active;
133     self->surface_center_x = center_x;
134 }
135 
136 /**
137  * mypaint_tile_request_init:
138  *
139  * Initialize a request for use with mypaint_tiled_surface_tile_request_start()
140  * and mypaint_tiled_surface_tile_request_end()
141  */
142 void
mypaint_tile_request_init(MyPaintTileRequest * data,int level,int tx,int ty,gboolean readonly)143 mypaint_tile_request_init(MyPaintTileRequest *data, int level,
144                           int tx, int ty, gboolean readonly)
145 {
146     data->tx = tx;
147     data->ty = ty;
148     data->readonly = readonly;
149     data->buffer = NULL;
150     data->context = NULL;
151 #ifdef _OPENMP
152     data->thread_id = omp_get_thread_num();
153 #else
154     data->thread_id = -1;
155 #endif
156     data->mipmap_level = level;
157 }
158 
159 // Must be threadsafe
160 static inline float
calculate_r_sample(float x,float y,float aspect_ratio,float sn,float cs)161 calculate_r_sample(float x, float y, float aspect_ratio,
162                       float sn, float cs)
163 {
164     const float yyr=(y*cs-x*sn)*aspect_ratio;
165     const float xxr=y*sn+x*cs;
166     const float r = (yyr*yyr + xxr*xxr);
167     return r;
168 }
169 
170 static inline float
calculate_rr(int xp,int yp,float x,float y,float aspect_ratio,float sn,float cs,float one_over_radius2)171 calculate_rr(int xp, int yp, float x, float y, float aspect_ratio,
172                       float sn, float cs, float one_over_radius2)
173 {
174     // code duplication, see brush::count_dabs_to()
175     const float yy = (yp + 0.5f - y);
176     const float xx = (xp + 0.5f - x);
177     const float yyr=(yy*cs-xx*sn)*aspect_ratio;
178     const float xxr=yy*sn+xx*cs;
179     const float rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
180     // rr is in range 0.0..1.0*sqrt(2)
181     return rr;
182 }
183 
184 static inline float
sign_point_in_line(float px,float py,float vx,float vy)185 sign_point_in_line( float px, float py, float vx, float vy )
186 {
187     return (px - vx) * (-vy) - (vx) * (py - vy);
188 }
189 
190 static inline void
closest_point_to_line(float lx,float ly,float px,float py,float * ox,float * oy)191 closest_point_to_line( float lx, float ly, float px, float py, float *ox, float *oy )
192 {
193     const float l2 = lx*lx + ly*ly;
194     const float ltp_dot = px*lx + py*ly;
195     const float t = ltp_dot / l2;
196     *ox = lx * t;
197     *oy = ly * t;
198 }
199 
200 // Must be threadsafe
201 //
202 // This works by taking the visibility at the nearest point
203 // and dividing by 1.0 + delta.
204 //
205 // - nearest point: point where the dab has more influence
206 // - farthest point: point at a fixed distance away from
207 //                   the nearest point
208 // - delta: how much occluded is the farthest point relative
209 //          to the nearest point
210 static inline 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)211 calculate_rr_antialiased(int xp, int yp, float x, float y, float aspect_ratio,
212                       float sn, float cs, float one_over_radius2,
213                       float r_aa_start)
214 {
215     // calculate pixel position and borders in a way
216     // that the dab's center is always at zero
217     float pixel_right = x - (float)xp;
218     float pixel_bottom = y - (float)yp;
219     float pixel_center_x = pixel_right - 0.5f;
220     float pixel_center_y = pixel_bottom - 0.5f;
221     float pixel_left = pixel_right - 1.0f;
222     float pixel_top = pixel_bottom - 1.0f;
223 
224     float nearest_x, nearest_y; // nearest to origin, but still inside pixel
225     float farthest_x, farthest_y; // farthest from origin, but still inside pixel
226     float r_near, r_far, rr_near, rr_far;
227     // Dab's center is inside pixel?
228     if( pixel_left<0 && pixel_right>0 &&
229         pixel_top<0 && pixel_bottom>0 )
230     {
231         nearest_x = 0;
232         nearest_y = 0;
233         r_near = rr_near = 0;
234     }
235     else
236     {
237         closest_point_to_line( cs, sn, pixel_center_x, pixel_center_y, &nearest_x, &nearest_y );
238         nearest_x = CLAMP( nearest_x, pixel_left, pixel_right );
239         nearest_y = CLAMP( nearest_y, pixel_top, pixel_bottom );
240         // XXX: precision of "nearest" values could be improved
241         // by intersecting the line that goes from nearest_x/Y to 0
242         // with the pixel's borders here, however the improvements
243         // would probably not justify the perdormance cost.
244         r_near = calculate_r_sample( nearest_x, nearest_y, aspect_ratio, sn, cs );
245         rr_near = r_near * one_over_radius2;
246     }
247 
248     // out of dab's reach?
249     if( rr_near > 1.0f )
250         return rr_near;
251 
252     // check on which side of the dab's line is the pixel center
253     float center_sign = sign_point_in_line( pixel_center_x, pixel_center_y, cs, -sn );
254 
255     // radius of a circle with area=1
256     //   A = pi * r * r
257     //   r = sqrt(1/pi)
258     const float rad_area_1 = sqrtf( 1.0f / M_PI );
259 
260     // center is below dab
261     if( center_sign < 0 )
262     {
263         farthest_x = nearest_x - sn*rad_area_1;
264         farthest_y = nearest_y + cs*rad_area_1;
265     }
266     // above dab
267     else
268     {
269         farthest_x = nearest_x + sn*rad_area_1;
270         farthest_y = nearest_y - cs*rad_area_1;
271     }
272 
273     r_far = calculate_r_sample( farthest_x, farthest_y, aspect_ratio, sn, cs );
274     rr_far = r_far * one_over_radius2;
275 
276     // check if we can skip heavier AA
277     if( r_far < r_aa_start )
278         return (rr_far+rr_near) * 0.5f;
279 
280     // calculate AA approximate
281     float visibilityNear = 1.0f - rr_near;
282     float delta = rr_far - rr_near;
283     float delta2 = 1.0f + delta;
284     visibilityNear /= delta2;
285 
286     return 1.0f - visibilityNear;
287 }
288 
289 static inline float
calculate_opa(float rr,float hardness,float segment1_offset,float segment1_slope,float segment2_offset,float segment2_slope)290 calculate_opa(float rr, float hardness,
291               float segment1_offset, float segment1_slope,
292               float segment2_offset, float segment2_slope) {
293 
294     const float fac = rr <= hardness ? segment1_slope : segment2_slope;
295     float opa = rr <= hardness ? segment1_offset : segment2_offset;
296     opa += rr*fac;
297 
298     if (rr > 1.0f) {
299         opa = 0.0f;
300     }
301     #ifdef HEAVY_DEBUG
302     assert(isfinite(opa));
303     assert(opa >= 0.0f && opa <= 1.0f);
304     #endif
305     return opa;
306 }
307 
308 // Must be threadsafe
render_dab_mask(uint16_t * mask,float x,float y,float radius,float hardness,float aspect_ratio,float angle)309 void render_dab_mask (uint16_t * mask,
310                         float x, float y,
311                         float radius,
312                         float hardness,
313                         float aspect_ratio, float angle
314                         )
315 {
316 
317     hardness = CLAMP(hardness, 0.0, 1.0);
318     if (aspect_ratio<1.0) aspect_ratio=1.0;
319     assert(hardness != 0.0); // assured by caller
320 
321     // For a graphical explanation, see:
322     // http://wiki.mypaint.info/Development/Documentation/Brushlib
323     //
324     // The hardness calculation is explained below:
325     //
326     // Dab opacity gradually fades out from the center (rr=0) to
327     // fringe (rr=1) of the dab. How exactly depends on the hardness.
328     // We use two linear segments, for which we pre-calculate slope
329     // and offset here.
330     //
331     // opa
332     // ^
333     // *   .
334     // |        *
335     // |          .
336     // +-----------*> rr = (distance_from_center/radius)^2
337     // 0           1
338     //
339     float segment1_offset = 1.0f;
340     float segment1_slope  = -(1.0f/hardness - 1.0f);
341     float segment2_offset = hardness/(1.0f-hardness);
342     float segment2_slope  = -hardness/(1.0f-hardness);
343     // for hardness == 1.0, segment2 will never be used
344 
345     float angle_rad=angle/360*2*M_PI;
346     float cs=cos(angle_rad);
347     float sn=sin(angle_rad);
348 
349     const float r_fringe = radius + 1.0f; // +1.0 should not be required, only to be sure
350     int x0 = floor (x - r_fringe);
351     int y0 = floor (y - r_fringe);
352     int x1 = floor (x + r_fringe);
353     int y1 = floor (y + r_fringe);
354     if (x0 < 0) x0 = 0;
355     if (y0 < 0) y0 = 0;
356     if (x1 > MYPAINT_TILE_SIZE-1) x1 = MYPAINT_TILE_SIZE-1;
357     if (y1 > MYPAINT_TILE_SIZE-1) y1 = MYPAINT_TILE_SIZE-1;
358     const float one_over_radius2 = 1.0f/(radius*radius);
359 
360     // Pre-calculate rr and put it in the mask.
361     // This an optimization that makes use of auto-vectorization
362     // OPTIMIZE: if using floats for the brush engine, store these directly in the mask
363     float rr_mask[MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE+2*MYPAINT_TILE_SIZE];
364 
365     if (radius < 3.0f)
366     {
367       const float aa_border = 1.0f;
368       float r_aa_start = ((radius>aa_border) ? (radius-aa_border) : 0);
369       r_aa_start *= r_aa_start / aspect_ratio;
370 
371       for (int yp = y0; yp <= y1; yp++) {
372         for (int xp = x0; xp <= x1; xp++) {
373           const float rr = calculate_rr_antialiased(xp, yp,
374                                   x, y, aspect_ratio,
375                                   sn, cs, one_over_radius2,
376                                   r_aa_start);
377           rr_mask[(yp*MYPAINT_TILE_SIZE)+xp] = rr;
378         }
379       }
380     }
381     else
382     {
383       for (int yp = y0; yp <= y1; yp++) {
384         for (int xp = x0; xp <= x1; xp++) {
385           const float rr = calculate_rr(xp, yp,
386                                   x, y, aspect_ratio,
387                                   sn, cs, one_over_radius2);
388           rr_mask[(yp*MYPAINT_TILE_SIZE)+xp] = rr;
389         }
390       }
391     }
392 
393     // we do run length encoding: if opacity is zero, the next
394     // value in the mask is the number of pixels that can be skipped.
395     uint16_t * mask_p = mask;
396     int skip=0;
397 
398     skip += y0*MYPAINT_TILE_SIZE;
399     for (int yp = y0; yp <= y1; yp++) {
400       skip += x0;
401 
402       int xp;
403       for (xp = x0; xp <= x1; xp++) {
404         const float rr = rr_mask[(yp*MYPAINT_TILE_SIZE)+xp];
405         const float opa = calculate_opa(rr, hardness,
406                                   segment1_offset, segment1_slope,
407                                   segment2_offset, segment2_slope);
408         const uint16_t opa_ = opa * (1<<15);
409         if (!opa_) {
410           skip++;
411         } else {
412           if (skip) {
413             *mask_p++ = 0;
414             *mask_p++ = skip*4;
415             skip = 0;
416           }
417           *mask_p++ = opa_;
418         }
419       }
420       skip += MYPAINT_TILE_SIZE-xp;
421     }
422     *mask_p++ = 0;
423     *mask_p++ = 0;
424   }
425 
426 // Must be threadsafe
427 void
process_op(uint16_t * rgba_p,uint16_t * mask,int tx,int ty,OperationDataDrawDab * op)428 process_op(uint16_t *rgba_p, uint16_t *mask,
429            int tx, int ty, OperationDataDrawDab *op)
430 {
431 
432     // first, we calculate the mask (opacity for each pixel)
433     render_dab_mask(mask,
434                     op->x - tx*MYPAINT_TILE_SIZE,
435                     op->y - ty*MYPAINT_TILE_SIZE,
436                     op->radius,
437                     op->hardness,
438                     op->aspect_ratio, op->angle
439                     );
440 
441     // second, we use the mask to stamp a dab for each activated blend mode
442     if (op->paint < 1.0) {
443       if (op->normal) {
444         if (op->color_a == 1.0) {
445           draw_dab_pixels_BlendMode_Normal(mask, rgba_p,
446                                            op->color_r, op->color_g, op->color_b, op->normal*op->opaque*(1 - op->paint)*(1<<15));
447         } else {
448           // normal case for brushes that use smudging (eg. watercolor)
449           draw_dab_pixels_BlendMode_Normal_and_Eraser(mask, rgba_p,
450                                                       op->color_r, op->color_g, op->color_b, op->color_a*(1<<15),
451                                                       op->normal*op->opaque*(1 - op->paint)*(1<<15));
452         }
453       }
454 
455       if (op->lock_alpha && op->color_a != 0) {
456         draw_dab_pixels_BlendMode_LockAlpha(mask, rgba_p,
457                                             op->color_r, op->color_g, op->color_b,
458                                             op->lock_alpha*op->opaque*(1 - op->colorize)*(1 - op->posterize)*(1 - op->paint)*(1<<15));
459       }
460     }
461 
462     if (op->paint > 0.0) {
463       if (op->normal) {
464         if (op->color_a == 1.0) {
465           draw_dab_pixels_BlendMode_Normal_Paint(mask, rgba_p,
466                                            op->color_r, op->color_g, op->color_b, op->normal*op->opaque*op->paint*(1<<15));
467         } else {
468           // normal case for brushes that use smudging (eg. watercolor)
469           draw_dab_pixels_BlendMode_Normal_and_Eraser_Paint(mask, rgba_p,
470                                                       op->color_r, op->color_g, op->color_b, op->color_a*(1<<15),
471                                                       op->normal*op->opaque*op->paint*(1<<15));
472         }
473       }
474 
475       if (op->lock_alpha && op->color_a != 0) {
476         draw_dab_pixels_BlendMode_LockAlpha_Paint(mask, rgba_p,
477                                             op->color_r, op->color_g, op->color_b,
478                                             op->lock_alpha*op->opaque*(1 - op->colorize)*(1 - op->posterize)*op->paint*(1<<15));
479       }
480     }
481 
482     if (op->colorize) {
483       draw_dab_pixels_BlendMode_Color(mask, rgba_p,
484                                       op->color_r, op->color_g, op->color_b,
485                                       op->colorize*op->opaque*(1<<15));
486     }
487     if (op->posterize) {
488       draw_dab_pixels_BlendMode_Posterize(mask, rgba_p,
489                                       op->posterize*op->opaque*(1<<15),
490                                       op->posterize_num);
491     }
492 }
493 
494 // Must be threadsafe
495 void
process_tile_internal(void * tiled_surface,void (* request_start)(void *,void *),void (* request_end)(void *,void *),OperationQueue * op_queue,int tx,int ty)496 process_tile_internal(
497   void *tiled_surface,
498   void (*request_start) (void*, void*),
499   void (*request_end) (void*, void*),
500   OperationQueue* op_queue, int tx, int ty)
501 {
502     TileIndex tile_index = {tx, ty};
503     OperationDataDrawDab *op = operation_queue_pop(op_queue, tile_index);
504     if (!op) {
505         return;
506     }
507 
508     MyPaintTileRequest request_data;
509     const int mipmap_level = 0;
510     mypaint_tile_request_init(&request_data, mipmap_level, tx, ty, FALSE);
511 
512     request_start(tiled_surface, &request_data);
513     uint16_t * rgba_p = request_data.buffer;
514     if (!rgba_p) {
515         printf("Warning: Unable to get tile!\n");
516         return;
517     }
518 
519     uint16_t mask[MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE+2*MYPAINT_TILE_SIZE];
520 
521     while (op) {
522         process_op(rgba_p, mask, tile_index.x, tile_index.y, op);
523         free(op);
524         op = operation_queue_pop(op_queue, tile_index);
525     }
526     request_end(tiled_surface, &request_data);
527 }
528 
529 void
update_dirty_bbox(MyPaintRectangle * bbox,OperationDataDrawDab * op)530 update_dirty_bbox(MyPaintRectangle *bbox, OperationDataDrawDab *op)
531 {
532     int bb_x, bb_y, bb_w, bb_h;
533     float r_fringe = op->radius + 1.0f; // +1.0 should not be required, only to be sure
534     bb_x = floor (op->x - r_fringe);
535     bb_y = floor (op->y - r_fringe);
536     bb_w = floor (op->x + r_fringe) - bb_x + 1;
537     bb_h = floor (op->y + r_fringe) - bb_y + 1;
538 
539     mypaint_rectangle_expand_to_include_point(bbox, bb_x, bb_y);
540     mypaint_rectangle_expand_to_include_point(bbox, bb_x+bb_w-1, bb_y+bb_h-1);
541 }
542 
543 // returns TRUE if the surface was modified
draw_dab_internal(OperationQueue * op_queue,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,float posterize,float posterize_num,float paint,MyPaintRectangle * bbox)544 gboolean draw_dab_internal (
545   OperationQueue *op_queue, float x, float y,
546   float radius,
547   float color_r, float color_g, float color_b,
548   float opaque, float hardness,
549   float color_a,
550   float aspect_ratio, float angle,
551   float lock_alpha,
552   float colorize,
553   float posterize,
554   float posterize_num,
555   float paint,
556   MyPaintRectangle *bbox
557   )
558 
559 {
560     OperationDataDrawDab op_struct;
561     OperationDataDrawDab *op = &op_struct;
562 
563     op->x = x;
564     op->y = y;
565     op->radius = radius;
566     op->aspect_ratio = aspect_ratio;
567     op->angle = angle;
568     op->opaque = CLAMP(opaque, 0.0f, 1.0f);
569     op->hardness = CLAMP(hardness, 0.0f, 1.0f);
570     op->lock_alpha = CLAMP(lock_alpha, 0.0f, 1.0f);
571     op->colorize = CLAMP(colorize, 0.0f, 1.0f);
572     op->posterize = CLAMP(posterize, 0.0f, 1.0f);
573     op->posterize_num= CLAMP(ROUND(posterize_num * 100.0), 1, 128);
574     op->paint = CLAMP(paint, 0.0f, 1.0f);
575     if (op->radius < 0.1f) return FALSE; // don't bother with dabs smaller than 0.1 pixel
576     if (op->hardness == 0.0f) return FALSE; // infintly small center point, fully transparent outside
577     if (op->opaque == 0.0f) return FALSE;
578 
579     color_r = CLAMP(color_r, 0.0f, 1.0f);
580     color_g = CLAMP(color_g, 0.0f, 1.0f);
581     color_b = CLAMP(color_b, 0.0f, 1.0f);
582     color_a = CLAMP(color_a, 0.0f, 1.0f);
583 
584     op->color_r = color_r * (1<<15);
585     op->color_g = color_g * (1<<15);
586     op->color_b = color_b * (1<<15);
587     op->color_a = color_a;
588 
589     // blending mode preparation
590     op->normal = 1.0f;
591 
592     op->normal *= 1.0f-op->lock_alpha;
593     op->normal *= 1.0f-op->colorize;
594     op->normal *= 1.0f-op->posterize;
595 
596     if (op->aspect_ratio<1.0f) op->aspect_ratio=1.0f;
597 
598     // Determine the tiles influenced by operation, and queue it for processing for each tile
599     float r_fringe = radius + 1.0f; // +1.0 should not be required, only to be sure
600 
601     int tx1 = floor(floor(x - r_fringe) / MYPAINT_TILE_SIZE);
602     int tx2 = floor(floor(x + r_fringe) / MYPAINT_TILE_SIZE);
603     int ty1 = floor(floor(y - r_fringe) / MYPAINT_TILE_SIZE);
604     int ty2 = floor(floor(y + r_fringe) / MYPAINT_TILE_SIZE);
605 
606     for (int ty = ty1; ty <= ty2; ty++) {
607         for (int tx = tx1; tx <= tx2; tx++) {
608             const TileIndex tile_index = {tx, ty};
609             OperationDataDrawDab *op_copy = (OperationDataDrawDab *)malloc(sizeof(OperationDataDrawDab));
610             *op_copy = *op;
611             operation_queue_add(op_queue, tile_index, op_copy);
612         }
613     }
614 
615     update_dirty_bbox(bbox, op);
616 
617     return TRUE;
618 }
619 
620 // returns TRUE if the surface was modified
draw_dab(MyPaintSurface * surface,float x,float y,float radius,float r,float g,float b,float opaque,float hardness,float color_a,float aspect_ratio,float angle,float lock_alpha,float colorize)621 int draw_dab (MyPaintSurface *surface, float x, float y,
622                float radius,
623                float r, float g, float b,
624                float opaque, float hardness,
625                float color_a,
626                float aspect_ratio, float angle,
627                float lock_alpha,
628                float colorize)
629 {
630     MyPaintTiledSurface* self = (MyPaintTiledSurface*)surface;
631     // Normal pass
632     gboolean surface_modified = (draw_dab_internal(
633         self->operation_queue, x, y, radius, r, g, b, opaque, hardness, color_a, aspect_ratio, angle, lock_alpha,
634         colorize, 0.0, 0.0, 0.0, &self->dirty_bbox));
635     // Symmetry pass
636     if (surface_modified && self->surface_do_symmetry) {
637         const float symm_x = self->surface_center_x + (self->surface_center_x - x);
638         draw_dab_internal(
639             self->operation_queue, symm_x, y, radius, r, g, b, opaque, hardness, color_a, aspect_ratio, -angle,
640             lock_alpha, colorize, 0.0, 0.0, 0.0, &self->dirty_bbox);
641     }
642     return surface_modified;
643 }
644 
645 
get_color_internal(void * tiled_surface,void (* request_start)(void *,void *),void (* request_end)(void *,void *),gboolean threadsafe_tile_requests,OperationQueue * op_queue,float x,float y,float radius,float * color_r,float * color_g,float * color_b,float * color_a,float paint)646 void get_color_internal
647 (
648  void *tiled_surface,
649  void (*request_start) (void*, void*),
650  void (*request_end) (void*, void*),
651  gboolean threadsafe_tile_requests,
652  OperationQueue *op_queue,
653  float x, float y,
654  float radius,
655  float * color_r, float * color_g, float * color_b, float * color_a,
656  float paint
657   )
658 {
659     if (radius < 1.0f) radius = 1.0f;
660     const float hardness = 0.5f;
661     const float aspect_ratio = 1.0f;
662     const float angle = 0.0f;
663 
664     float sum_weight, sum_r, sum_g, sum_b, sum_a;
665     sum_weight = sum_r = sum_g = sum_b = sum_a = 0.0f;
666 
667     // in case we return with an error
668     *color_r = 0.0f;
669     *color_g = 1.0f;
670     *color_b = 0.0f;
671 
672     // WARNING: some code duplication with draw_dab
673 
674     float r_fringe = radius + 1.0f; // +1 should not be required, only to be sure
675 
676     int tx1 = floor(floor(x - r_fringe) / MYPAINT_TILE_SIZE);
677     int tx2 = floor(floor(x + r_fringe) / MYPAINT_TILE_SIZE);
678     int ty1 = floor(floor(y - r_fringe) / MYPAINT_TILE_SIZE);
679     int ty2 = floor(floor(y + r_fringe) / MYPAINT_TILE_SIZE);
680     #ifdef _OPENMP
681     int tiles_n = (tx2 - tx1) * (ty2 - ty1);
682     #endif
683 
684     // Calculate the `guaranteed sample` interval and
685     // the percentage of pixels to sample for the dab.
686     // The basic idea is to have larger intervals and
687     // lower percentages for really large dabs, to
688     // avoid accumulated rounding errors and heavier
689     // calculations.
690     //
691     // The values are set so that the number of pixels
692     // sampled is _bounded_ linearly by the radius.
693     //
694     // The constant factor 7 is chosen through manual
695     // evaluation of results and gives us a total sample
696     // rate bounded by '1/(r * 3.5)'
697     // Other models may have better properties, some
698     // more thinking needed here.
699     //
700     // For really small radii we'll sample every pixel
701     // in the dab to avoid biasing.
702     const int sample_interval = radius <= 2.0f ? 1 : (int)(radius * 7);
703     const float random_sample_rate = 1.0f / (7 * radius);
704 
705     #ifdef _OPENMP
706     #pragma omp parallel for schedule(static) if(threadsafe_tile_requests && tiles_n > 3)
707     #endif
708     for (int ty = ty1; ty <= ty2; ty++) {
709       for (int tx = tx1; tx <= tx2; tx++) {
710 
711         // Flush queued draw_dab operations
712         process_tile_internal(tiled_surface, request_start, request_end, op_queue, tx, ty);
713 
714         MyPaintTileRequest request_data;
715         const int mipmap_level = 0;
716         mypaint_tile_request_init(&request_data, mipmap_level, tx, ty, TRUE);
717         request_start(tiled_surface, &request_data);
718         uint16_t * rgba_p = request_data.buffer;
719         if (!rgba_p) {
720           printf("Warning: Unable to get tile!\n");
721           break;
722         }
723 
724         // first, we calculate the mask (opacity for each pixel)
725         uint16_t mask[MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE+2*MYPAINT_TILE_SIZE];
726 
727         render_dab_mask(mask,
728                         x - tx*MYPAINT_TILE_SIZE,
729                         y - ty*MYPAINT_TILE_SIZE,
730                         radius,
731                         hardness,
732                         aspect_ratio, angle
733                         );
734 
735         // TODO: try atomic operations instead
736         #pragma omp critical
737         {
738         get_color_pixels_accumulate (
739           mask, rgba_p, &sum_weight, &sum_r, &sum_g, &sum_b, &sum_a, paint,
740           sample_interval, random_sample_rate);
741         }
742 
743         request_end(tiled_surface, &request_data);
744       }
745     }
746 
747     assert(sum_weight > 0.0f);
748     sum_a /= sum_weight;
749 
750     // For legacy sampling, we need to divide
751     // by the total after the accumulation.
752     if (paint < 0.0) {
753         sum_r /= sum_weight;
754         sum_g /= sum_weight;
755         sum_b /= sum_weight;
756     }
757 
758     *color_a = CLAMP(sum_a, 0.0f, 1.0f);
759     if (sum_a > 0.0f) {
760       // Straighten the color channels if using legacy sampling.
761       // Clamp to guard against rounding errors.
762       const float demul = paint < 0.0 ? sum_a : 1.0;
763       *color_r = CLAMP(sum_r / demul, 0.0f, 1.0f);
764       *color_g = CLAMP(sum_g / demul, 0.0f, 1.0f);
765       *color_b = CLAMP(sum_b / demul, 0.0f, 1.0f);
766     } else {
767       // it is all transparent, so don't care about the colors
768       // (let's make them ugly so bugs will be visible)
769       *color_r = 0.0f;
770       *color_g = 1.0f;
771       *color_b = 0.0f;
772     }
773 }
774 
775 /* Go-betweens for more clarity  */
tsf1_request_start(void * surface,void * request)776 void tsf1_request_start(void* surface, void* request) {
777   MyPaintTiledSurface *self = (MyPaintTiledSurface*) surface;
778   self->tile_request_start(self, (MyPaintTileRequest*) request);
779 }
tsf1_request_end(void * surface,void * request)780 void tsf1_request_end(void* surface, void* request) {
781   MyPaintTiledSurface *self = (MyPaintTiledSurface*) surface;
782   self->tile_request_end(self, (MyPaintTileRequest*) request);
783 }
784 
785 void
get_color(MyPaintSurface * surface,float x,float y,float radius,float * color_r,float * color_g,float * color_b,float * color_a)786 get_color(
787     MyPaintSurface* surface, float x, float y, float radius, float* color_r, float* color_g, float* color_b,
788     float* color_a)
789 {
790     MyPaintTiledSurface* self = (MyPaintTiledSurface*)surface;
791     get_color_internal(
792       surface, tsf1_request_start, tsf1_request_end, self->threadsafe_tile_requests, self->operation_queue, x, y,
793       radius, color_r, color_g, color_b, color_a, -1.0);
794 }
795 
796 
797 float
mypaint_tiled_surface_get_alpha(MyPaintTiledSurface * self,float x,float y,float radius)798 mypaint_tiled_surface_get_alpha (MyPaintTiledSurface *self, float x, float y, float radius) {
799   float r, g, b, a;
800   get_color(&self->parent, x, y, radius, &r, &g, &b, &a);
801   return a;
802 }
803 
tiled_surface_process_tile(MyPaintTiledSurface * self,int tx,int ty)804 void tiled_surface_process_tile(MyPaintTiledSurface *self, int tx, int ty) {
805   process_tile_internal(self, tsf1_request_start, tsf1_request_end, self->operation_queue, tx, ty);
806 }
807 
808 /**
809  * mypaint_tiled_surface_init: (skip)
810  *
811  * Initialize the surface, passing in implementations of the tile backend.
812  * Note: Only intended to be called from subclasses of #MyPaintTiledSurface
813  **/
814 void
mypaint_tiled_surface_init(MyPaintTiledSurface * self,MyPaintTileRequestStartFunction tile_request_start,MyPaintTileRequestEndFunction tile_request_end)815 mypaint_tiled_surface_init(MyPaintTiledSurface *self,
816                            MyPaintTileRequestStartFunction tile_request_start,
817                            MyPaintTileRequestEndFunction tile_request_end)
818 {
819     mypaint_surface_init(&self->parent);
820     self->parent.draw_dab = draw_dab;
821     self->parent.get_color = get_color;
822     self->parent.begin_atomic = begin_atomic_default;
823     self->parent.end_atomic = end_atomic_default;
824 
825     self->tile_request_end = tile_request_end;
826     self->tile_request_start = tile_request_start;
827 
828     self->tile_size = MYPAINT_TILE_SIZE;
829     self->threadsafe_tile_requests = FALSE;
830 
831     self->dirty_bbox.x = 0;
832     self->dirty_bbox.y = 0;
833     self->dirty_bbox.width = 0;
834     self->dirty_bbox.height = 0;
835     self->surface_do_symmetry = FALSE;
836     self->surface_center_x = 0.0f;
837     self->operation_queue = operation_queue_new();
838 }
839 
840 
841 /**
842  * mypaint_tiled_surface_destroy: (skip)
843  *
844  * Deallocate resources set up by mypaint_tiled_surface_init()
845  * Does not free the #MyPaintTiledSurface itself.
846  * Note: Only intended to be called from subclasses of #MyPaintTiledSurface
847  */
848 void
mypaint_tiled_surface_destroy(MyPaintTiledSurface * self)849 mypaint_tiled_surface_destroy(MyPaintTiledSurface *self)
850 {
851     operation_queue_free(self->operation_queue);
852 }
853 
854 /* -- Extended interface -- */
855 
856 /**
857   * MyPaintTiledSurface2: (skip)
858   */
859 struct MyPaintTiledSurface2;
860 
861 
862 /**
863  * mypaint_tiled_surface2_tile_request_start: (skip)
864  */
mypaint_tiled_surface2_tile_request_start(MyPaintTiledSurface2 * self,MyPaintTileRequest * request)865 void mypaint_tiled_surface2_tile_request_start(MyPaintTiledSurface2 *self, MyPaintTileRequest *request)
866 {
867     assert(self->tile_request_start);
868     self->tile_request_start(self, request);
869 }
870 
871 /**
872  * mypaint_tiled_surface2_tile_request_end: (skip)
873  */
mypaint_tiled_surface2_tile_request_end(MyPaintTiledSurface2 * self,MyPaintTileRequest * request)874 void mypaint_tiled_surface2_tile_request_end(MyPaintTiledSurface2 *self, MyPaintTileRequest *request)
875 {
876     assert(self->tile_request_end);
877     self->tile_request_end(self, request);
878 }
879 
880 /* Go-betweens for more clarity  */
tsf2_request_start(void * surface,void * request)881 void tsf2_request_start(void* surface, void* request) {
882   MyPaintTiledSurface2 *self = (MyPaintTiledSurface2*) surface;
883   self->tile_request_start(self, (MyPaintTileRequest*) request);
884 }
885 
tsf2_request_end(void * surface,void * request)886 void tsf2_request_end(void* surface, void* request) {
887   MyPaintTiledSurface2 *self = (MyPaintTiledSurface2*) surface;
888   self->tile_request_end(self, (MyPaintTileRequest*) request);
889 }
890 
tiled_surface2_process_tile(MyPaintTiledSurface2 * self,int tx,int ty)891 void tiled_surface2_process_tile(MyPaintTiledSurface2 *self, int tx, int ty) {
892   process_tile_internal(self, tsf2_request_start, tsf2_request_end, self->operation_queue, tx, ty);
893 }
894 
895 void
get_color_pigment(MyPaintSurface2 * surface,float x,float y,float radius,float * color_r,float * color_g,float * color_b,float * color_a,float paint)896 get_color_pigment(
897     MyPaintSurface2* surface, float x, float y, float radius, float* color_r, float* color_g, float* color_b,
898     float* color_a, float paint)
899 {
900     MyPaintTiledSurface2* self = (MyPaintTiledSurface2*)surface;
901     get_color_internal(
902         surface, tsf2_request_start, tsf2_request_end, self->threadsafe_tile_requests, self->operation_queue, x, y,
903         radius, color_r, color_g, color_b, color_a, paint);
904 }
905 
906 static void
begin_atomic_default_2(MyPaintSurface * surface)907 begin_atomic_default_2(MyPaintSurface *surface)
908 {
909   mypaint_tiled_surface2_begin_atomic((MyPaintTiledSurface2 *)surface);
910 }
911 
912 static void
end_atomic_default_2(MyPaintSurface2 * surface,MyPaintRectangles * roi)913 end_atomic_default_2(MyPaintSurface2 *surface, MyPaintRectangles *roi)
914 {
915     mypaint_tiled_surface2_end_atomic((MyPaintTiledSurface2 *)surface, roi);
916 }
917 
918 void
prepare_bounding_boxes(MyPaintTiledSurface2 * self)919 prepare_bounding_boxes(MyPaintTiledSurface2 *self) {
920     MyPaintSymmetryState symm_state = self->symmetry_data.state_current;
921     const gboolean snowflake = symm_state.type == MYPAINT_SYMMETRY_TYPE_SNOWFLAKE;
922     const int num_bboxes_desired = symm_state.num_lines * (snowflake ? 2 : 1);
923     // If the bounding box array cannot fit one rectangle per symmetry dab,
924     // try to allocate enough space for that to be possible.
925     // Failure is ok, as the bounding box assignments will be functional anyway.
926     if (num_bboxes_desired > self->num_bboxes) {
927         const int margin = 10; // Add margin to avoid unnecessary reallocations.
928         const int num_to_allocate = num_bboxes_desired + margin;
929         int bytes_to_allocate = num_to_allocate * sizeof(MyPaintRectangle);
930         MyPaintRectangle* new_bboxes = malloc(bytes_to_allocate);
931         if (new_bboxes) {
932             free(self->bboxes);
933             // Initialize memory
934             memset(new_bboxes, 0, bytes_to_allocate);
935             self->bboxes = new_bboxes;
936             self->num_bboxes = num_to_allocate;
937             // No need to clear anything after the memset, so reset counter
938             self->num_bboxes_dirtied = 0;
939         }
940     }
941     // Clean up any previously populated bounding boxes and reset the counter
942     for (int i = 0; i < MIN(self->num_bboxes, self->num_bboxes_dirtied); ++i) {
943         self->bboxes[i].height = 0;
944         self->bboxes[i].width = 0;
945         self->bboxes[i].x = 0;
946         self->bboxes[i].y = 0;
947     }
948     self->num_bboxes_dirtied = 0;
949 }
950 
951 // returns TRUE if the surface was modified
draw_dab_2(MyPaintSurface2 * surface,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,float posterize,float posterize_num,float paint)952 int draw_dab_2 (MyPaintSurface2 *surface, float x, float y,
953                float radius,
954                float color_r, float color_g, float color_b,
955                float opaque, float hardness,
956                float color_a,
957                float aspect_ratio, float angle,
958                float lock_alpha,
959                float colorize,
960                float posterize,
961                float posterize_num,
962                float paint)
963 {
964     MyPaintTiledSurface2* self = (MyPaintTiledSurface2*)surface;
965 
966     // These calls are repeated enough to warrant a local macro, for both readability and correctness.
967 #define DDI(x, y, angle, bb_idx) (draw_dab_internal(\
968         self->operation_queue, (x), (y), radius, color_r, color_g, color_b, opaque, \
969         hardness, color_a, aspect_ratio, (angle), \
970         lock_alpha, colorize, posterize, posterize_num, paint, &self->bboxes[(bb_idx)]))
971 
972     // Normal pass
973     gboolean surface_modified = DDI(x, y, angle, 0);
974 
975     int num_bboxes_used = surface_modified ? 1 : 0;
976 
977     // Symmetry pass
978 
979     // OPTIMIZATION: skip the symmetry pass if surface was not modified by the initial dab;
980     // at current if the initial dab does not modify the surface, none of the symmetry dabs
981     // will either. If/when selection masks are added, this optimization _must_ be removed,
982     // and `surface_modified` must be or'ed with the result of each call to draw_dab_internal.
983     MyPaintSymmetryData *symm_data = &self->symmetry_data;
984     if (surface_modified && symm_data->active && symm_data->num_symmetry_matrices) {
985         const MyPaintSymmetryState symm = symm_data->state_current;
986         const int num_bboxes = self->num_bboxes;
987         const float rot_angle = 360.0 / symm.num_lines;
988         const MyPaintTransform* const matrices = symm_data->symmetry_matrices;
989         float x_out, y_out;
990 
991         switch (symm.type) {
992         case MYPAINT_SYMMETRY_TYPE_VERTICAL: {
993             mypaint_transform_point(&matrices[0], x, y, &x_out, &y_out);
994             DDI(x_out, y_out, -2.0 * (90 + symm.angle) - angle, 1);
995             num_bboxes_used = 2;
996             break;
997         }
998         case MYPAINT_SYMMETRY_TYPE_HORIZONTAL: {
999             mypaint_transform_point(&matrices[0], x, y, &x_out, &y_out);
1000             DDI(x_out, y_out, -2.0 * symm.angle - angle, 1);
1001             num_bboxes_used = 2;
1002             break;
1003         }
1004         case MYPAINT_SYMMETRY_TYPE_VERTHORZ: {
1005             // Reflect across horizontal line
1006             mypaint_transform_point(&matrices[0], x, y, &x_out, &y_out);
1007             DDI(x_out, y_out, -2.0 * symm.angle - angle, 1);
1008             // Then across the vertical line (diagonal)
1009             mypaint_transform_point(&matrices[1], x, y, &x_out, &y_out);
1010             DDI(x_out, y_out, angle, 2);
1011             // Then back across the horizontal line
1012             mypaint_transform_point(&matrices[2], x, y, &x_out, &y_out);
1013             DDI(x_out, y_out, -2.0 * symm.angle - angle, 3);
1014             num_bboxes_used = 4;
1015             break;
1016         }
1017         case MYPAINT_SYMMETRY_TYPE_SNOWFLAKE: {
1018 
1019             // These dabs will occupy the bboxes after the last bbox used by the rotational dabs.
1020             const int offset = MIN(num_bboxes / 2, symm.num_lines);
1021             const float dabs_per_bbox = MAX(1, (float)symm.num_lines * 2.0 / num_bboxes);
1022             const int base_idx = symm.num_lines - 1;
1023             const float base_angle = -2 * symm.angle - angle;
1024             // draw snowflake dabs for _all_ symmetry lines as we need to reflect the initial dab.
1025             for (int dab_count = 0; dab_count < symm.num_lines; dab_count++) {
1026                 // If the number of bboxes cannot fit all snowflake dabs, use half for the rotational dabs
1027                 // and the other half for the reflected dabs. This is not always optimal, but seldom bad.
1028                 const int bbox_idx = offset + MIN(roundf(dab_count / dabs_per_bbox), num_bboxes - 1);
1029                 mypaint_transform_point(&matrices[base_idx + dab_count], x, y, &x_out, &y_out);
1030                 DDI(x_out, y_out, base_angle - dab_count * rot_angle, bbox_idx);
1031             }
1032             num_bboxes_used = MIN(self->num_bboxes, symm.num_lines * 2);
1033             // fall through to rotational to finish the process
1034         }
1035         case MYPAINT_SYMMETRY_TYPE_ROTATIONAL: {
1036 
1037             // Set the dab bbox distribution factor based on whether the pass is only
1038             // rotational, or following a snowflake pass. For the latter, we compress
1039             // the available range (unimportant if there are enough bboxes to go around).
1040             const gboolean snowflake = symm.type == MYPAINT_SYMMETRY_TYPE_SNOWFLAKE;
1041             float dabs_per_bbox = MAX(1, (float)(symm.num_lines * (snowflake ? 2 : 1)) / num_bboxes);
1042 
1043             // draw self->rot_symmetry_lines - 1 rotational dabs since initial pass handles the first dab
1044             for (int dab_count = 1; dab_count < symm.num_lines; dab_count++) {
1045                 const int bbox_index = MIN(roundf(dab_count / dabs_per_bbox), num_bboxes - 1);
1046                 mypaint_transform_point(&matrices[dab_count - 1], x, y, &x_out, &y_out);
1047                 DDI(x_out, y_out, angle - dab_count * rot_angle, bbox_index);
1048             }
1049 
1050             // Use existing (larger) number of bboxes if it was set (in a snowflake pass)
1051             num_bboxes_used = MIN(self->num_bboxes, MAX(symm.num_lines, num_bboxes_used));
1052             break;
1053         }
1054         default:
1055             fprintf(stderr, "Warning: Unhandled symmetry type: %d\n", symm.type);
1056             break;
1057         }
1058     }
1059     self->num_bboxes_dirtied = MIN(self->num_bboxes, num_bboxes_used);
1060     return surface_modified;
1061 #undef DDI
1062 }
1063 
1064 int
draw_dab_wrapper(MyPaintSurface * surface,float x,float y,float radius,float r,float g,float b,float opaque,float hardness,float color_a,float aspect_ratio,float angle,float lock_alpha,float colorize)1065 draw_dab_wrapper(
1066     MyPaintSurface* surface, float x, float y, float radius, float r, float g, float b, float opaque, float hardness,
1067     float color_a, float aspect_ratio, float angle, float lock_alpha, float colorize)
1068 {
1069     const float posterize = 0.0;
1070     const float posterize_num = 1.0;
1071     const float pigment = 0.0;
1072     return draw_dab_2(
1073         (MyPaintSurface2*)surface, x, y, radius, r, g, b, opaque, hardness, color_a, aspect_ratio, angle, lock_alpha,
1074         colorize, posterize, posterize_num, pigment);
1075 }
1076 
1077 void
get_color_wrapper(MyPaintSurface * surface,float x,float y,float radius,float * color_r,float * color_g,float * color_b,float * color_a)1078 get_color_wrapper(
1079     MyPaintSurface* surface, float x, float y, float radius, float* color_r, float* color_g, float* color_b,
1080     float* color_a)
1081 {
1082     MyPaintTiledSurface2* self = (MyPaintTiledSurface2*)surface;
1083     return get_color_internal(
1084         surface, tsf2_request_start, tsf2_request_end, self->threadsafe_tile_requests, self->operation_queue, x, y,
1085         radius, color_r, color_g, color_b, color_a, -1.0);
1086 }
1087 
1088 static void
end_atomic_wrapper(MyPaintSurface * surface,MyPaintRectangle * roi)1089 end_atomic_wrapper(MyPaintSurface *surface, MyPaintRectangle *roi)
1090 {
1091   MyPaintRectangles rois = {1, roi};
1092   mypaint_tiled_surface2_end_atomic((MyPaintTiledSurface2*)surface, &rois);
1093 }
1094 
1095 /**
1096  * mypaint_tiled_surface2_init: (skip)
1097  *
1098  * Initialize the surface, passing in implementations of the tile backend.
1099  * Note: Only intended to be called from subclasses of #MyPaintTiledSurface
1100  **/
1101 void
mypaint_tiled_surface2_init(MyPaintTiledSurface2 * self,MyPaintTileRequestStartFunction2 tile_request_start,MyPaintTileRequestEndFunction2 tile_request_end)1102 mypaint_tiled_surface2_init(MyPaintTiledSurface2 *self,
1103                            MyPaintTileRequestStartFunction2 tile_request_start,
1104                            MyPaintTileRequestEndFunction2 tile_request_end)
1105 {
1106     mypaint_surface_init(&self->parent.parent);
1107 
1108     self->tile_request_end = tile_request_end;
1109     self->tile_request_start = tile_request_start;
1110     self->tile_size = MYPAINT_TILE_SIZE;
1111     self->threadsafe_tile_requests = FALSE;
1112     self->operation_queue = operation_queue_new();
1113 
1114     MyPaintSurface2 *s = &self->parent;
1115 
1116     s->draw_dab_pigment = draw_dab_2;
1117     s->get_color_pigment = get_color_pigment;
1118     s->end_atomic_multi = end_atomic_default_2;
1119     s->parent.begin_atomic = begin_atomic_default_2;
1120 
1121     // Adapters supporting the base interface
1122     s->parent.draw_dab = draw_dab_wrapper;
1123     s->parent.get_color = get_color_wrapper;
1124     s->parent.end_atomic = end_atomic_wrapper;
1125 
1126     self->num_bboxes = NUM_BBOXES_DEFAULT;
1127     self->bboxes = malloc(sizeof(MyPaintRectangle) * NUM_BBOXES_DEFAULT);
1128     memset(self->bboxes, 0, sizeof(MyPaintRectangle) * NUM_BBOXES_DEFAULT);
1129     self->symmetry_data = mypaint_default_symmetry_data();
1130 }
1131 
1132 void
mypaint_tiled_surface2_begin_atomic(MyPaintTiledSurface2 * self)1133 mypaint_tiled_surface2_begin_atomic(MyPaintTiledSurface2 *self)
1134 {
1135     mypaint_update_symmetry_state(&self->symmetry_data);
1136     prepare_bounding_boxes(self);
1137 }
1138 
1139 /**
1140  * mypaint_tiled_surface_end_atomic_2: (skip)
1141  *
1142  * Implementation of #MyPaintSurface::end_atomic vfunc
1143  * Note: Only intended to be used from #MyPaintTiledSurface subclasses, which should chain up to this
1144  * if implementing their own #MyPaintSurface::end_atomic vfunc.
1145  * Application code should only use mypaint_surface_end_atomic().
1146  */
1147 void
mypaint_tiled_surface2_end_atomic(MyPaintTiledSurface2 * self,MyPaintRectangles * roi)1148 mypaint_tiled_surface2_end_atomic(MyPaintTiledSurface2 *self, MyPaintRectangles *roi)
1149 {
1150     // Process tiles
1151     TileIndex *tiles;
1152     int tiles_n = operation_queue_get_dirty_tiles(self->operation_queue, &tiles);
1153 
1154     #pragma omp parallel for schedule(static) if(self->threadsafe_tile_requests && tiles_n > 3)
1155     for (int i = 0; i < tiles_n; i++) {
1156       tiled_surface2_process_tile(self, tiles[i].x, tiles[i].y);
1157     }
1158 
1159     operation_queue_clear_dirty_tiles(self->operation_queue);
1160 
1161     if (roi) {
1162         const int roi_rects = roi->num_rectangles;
1163         const int num_dirty = self->num_bboxes_dirtied;
1164         // Clear out the input rectangles that will be overwritten
1165         for (int i = 0; i < MIN(roi_rects, num_dirty); ++i) {
1166             roi->rectangles[i].x = 0;
1167             roi->rectangles[i].y = 0;
1168             roi->rectangles[i].width = 0;
1169             roi->rectangles[i].height = 0;
1170         }
1171         // Write bounding box rectangles to the output array
1172         const float bboxes_per_output = MAX(1, (float)num_dirty / roi_rects);
1173         for (int i = 0; i < num_dirty; ++i) {
1174             int out_index;
1175             // If there is not enough space for all rectangles in the output,
1176             // merge some of the rectangles with their list-adjacent neighbours.
1177             if (num_dirty > roi_rects) {
1178                 out_index = (int)MIN(roi_rects - 1, roundf((float)i / bboxes_per_output));
1179             } else {
1180                 out_index = i;
1181             }
1182             mypaint_rectangle_expand_to_include_rect(&(roi->rectangles[out_index]), &(self->bboxes[i]));
1183         }
1184         // Set the number of rectangles written to, so the caller knows which ones to act on.
1185         roi->num_rectangles = MIN(roi_rects, num_dirty);
1186     }
1187 }
1188 
1189 /**
1190  * mypaint_tiled_surface_set_symmetry_state_2: (skip)
1191  * @active: TRUE to enable, FALSE to disable.
1192  * @center_x: X axis to mirror events across.
1193  * @center_y: Y axis to mirror events across.
1194  * @symmetry_angle: Angle to rotate the symmetry lines
1195  * @symmetry_type: Symmetry type to activate.
1196  * @rot_symmetry_lines: Number of rotational symmetry lines.
1197  *
1198  * Enable/Disable symmetric brush painting across an X axis.
1199  *
1200  */
1201 void
mypaint_tiled_surface2_set_symmetry_state(MyPaintTiledSurface2 * self,gboolean active,float center_x,float center_y,float symmetry_angle,MyPaintSymmetryType symmetry_type,int rot_symmetry_lines)1202 mypaint_tiled_surface2_set_symmetry_state(MyPaintTiledSurface2 *self, gboolean active,
1203                                          float center_x, float center_y,
1204                                          float symmetry_angle,
1205                                          MyPaintSymmetryType symmetry_type,
1206                                          int rot_symmetry_lines)
1207 {
1208     mypaint_symmetry_set_pending( // Only write to the pending new state, nothing gets recalculated here
1209         &self->symmetry_data, active, center_x, center_y, symmetry_angle, symmetry_type, rot_symmetry_lines);
1210 }
1211 
1212 /**
1213  * mypaint_tiled_surface2_destroy: (skip)
1214  *
1215  * Deallocate resources set up by mypaint_tiled_surface2_init()
1216  * Does not free the #MyPaintTiledSurface itself.
1217  * Note: Only intended to be called from subclasses of #MyPaintTiledSurface
1218  */
1219 void
mypaint_tiled_surface2_destroy(MyPaintTiledSurface2 * self)1220 mypaint_tiled_surface2_destroy(MyPaintTiledSurface2 *self)
1221 {
1222     operation_queue_free(self->operation_queue);
1223     free(self->bboxes);
1224     mypaint_symmetry_data_destroy(&self->symmetry_data);
1225 }
1226