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