OSDN Git Service

draw_dab: small speedup, update lock_alpha
[mypaint-anime/master.git] / lib / tiledsurface.hpp
1 /* This file is part of MyPaint.
2  * Copyright (C) 2008 by Martin Renold <martinxyz@gmx.ch>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  */
9
10 #define TILE_SIZE 64
11 #define MAX_MIPMAP_LEVEL 3
12
13   void draw_dab_pixels_BlendMode_Normal (uint16_t * mask,
14                                          uint16_t * rgba,
15                                          uint16_t color_r_,
16                                          uint16_t color_g_,
17                                          uint16_t color_b_,
18                                          float opacity2) {
19     while (1) {
20       for (; mask[0]; mask++, rgba+=4) {
21         uint32_t opa_a = mask[0]*opacity2; // topAlpha
22         uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
23         rgba[3] = opa_a + (opa_b*rgba[3])/(1<<15);
24         rgba[0] = (opa_a*color_r_ + opa_b*rgba[0])/(1<<15);
25         rgba[1] = (opa_a*color_g_ + opa_b*rgba[1])/(1<<15);
26         rgba[2] = (opa_a*color_b_ + opa_b*rgba[2])/(1<<15);
27       }
28       if (!mask[1]) break;
29       rgba += mask[1];
30       mask += 2;
31     }
32   };
33
34   void draw_dab_pixels_BlendMode_Eraser (uint16_t * mask,
35                                          uint16_t * rgba,
36                                          uint16_t color_r_,
37                                          uint16_t color_g_,
38                                          uint16_t color_b_,
39                                          float opacity2) {
40     while (1) {
41       for (; mask[0]; mask++, rgba+=4) {
42         uint32_t opa_b = mask[0]*opacity2; // topAlpha
43         opa_b = (1<<15)-opa_b;
44         rgba[3] = (opa_b*rgba[3])/(1<<15);
45         rgba[0] = (opa_b*rgba[0])/(1<<15);
46         rgba[1] = (opa_b*rgba[1])/(1<<15);
47         rgba[2] = (opa_b*rgba[2])/(1<<15);
48       }
49       if (!mask[1]) break;
50       rgba += mask[1];
51       mask += 2;
52     }
53   };
54
55   void draw_dab_pixels_BlendMode_LockAlpha (uint16_t * mask,
56                                             uint16_t * rgba,
57                                             uint16_t color_r_,
58                                             uint16_t color_g_,
59                                             uint16_t color_b_,
60                                             float opacity2) {
61
62     while (1) {
63       for (; mask[0]; mask++, rgba+=4) {
64         uint32_t opa_a = mask[0]*opacity2; // topAlpha
65         uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
66         opa_a *= rgba[3];
67         opa_a /= (1<<15);
68         rgba[0] = (opa_a*color_r_ + opa_b*rgba[0])/(1<<15);
69         rgba[1] = (opa_a*color_g_ + opa_b*rgba[1])/(1<<15);
70         rgba[2] = (opa_a*color_b_ + opa_b*rgba[2])/(1<<15);
71       }
72       if (!mask[1]) break;
73       rgba += mask[1];
74       mask += 2;
75     }
76   };
77
78 class TiledSurface : public Surface {
79   // the Python half of this class is in tiledsurface.py
80 private:
81   PyObject * self;
82   Rect dirty_bbox;
83   int atomic;
84
85   // caching tile memory location (optimization)
86   #define TILE_MEMORY_SIZE 8
87   typedef struct {
88     int tx, ty;
89     uint16_t * rgba_p;
90   } TileMemory;
91   TileMemory tileMemory[TILE_MEMORY_SIZE];
92   int tileMemoryValid;
93   int tileMemoryWrite;
94   
95 public:
96   TiledSurface(PyObject * self_) {
97     self = self_; // no need to incref
98     atomic = 0;
99     dirty_bbox.w = 0;
100     tileMemoryValid = 0;
101     tileMemoryWrite = 0;
102   }
103
104   void begin_atomic() {
105     if (atomic == 0) {
106       assert(dirty_bbox.w == 0);
107       assert(tileMemoryValid == 0);
108     }
109     atomic++;
110   }
111   void end_atomic() {
112     assert(atomic > 0);
113     atomic--;
114     if (atomic == 0) {
115       tileMemoryValid = 0;
116       tileMemoryWrite = 0;
117       Rect bbox = dirty_bbox; // copy to safety before calling python
118       dirty_bbox.w = 0;
119       if (bbox.w > 0) {
120         PyObject* res;
121         // OPTIMIZE: send a list tiles for minimal compositing? (but profile the code first)
122         res = PyObject_CallMethod(self, "notify_observers", "(iiii)", bbox.x, bbox.y, bbox.w, bbox.h);
123         if (!res) printf("Python exception during notify_observers! FIXME: Traceback will not be accurate.\n");
124         Py_DECREF(res);
125       }
126     }
127   }
128
129   uint16_t * get_tile_memory(int tx, int ty, bool readonly) {
130     // We assume that the memory location does not change between begin_atomic() and end_atomic().
131     for (int i=0; i<tileMemoryValid; i++) {
132       if (tileMemory[i].tx == tx and tileMemory[i].ty == ty) {
133         return tileMemory[i].rgba_p;
134       }
135     }
136     PyObject* rgba = PyObject_CallMethod(self, "get_tile_memory", "(iii)", tx, ty, readonly);
137     if (rgba == NULL) {
138       printf("Python exception during get_tile_memory()! The next traceback might be wrong.\n");
139       return NULL;
140     }
141 #ifdef HEAVY_DEBUG
142        assert(PyArray_NDIM(rgba) == 3);
143        assert(PyArray_DIM(rgba, 0) == TILE_SIZE);
144        assert(PyArray_DIM(rgba, 1) == TILE_SIZE);
145        assert(PyArray_DIM(rgba, 2) == 4);
146        assert(PyArray_ISCARRAY(rgba));
147        assert(PyArray_TYPE(rgba) == NPY_UINT16);
148 #endif
149     // tiledsurface.py will keep a reference in its tiledict, at least until the final end_atomic()
150     Py_DECREF(rgba);
151     uint16_t * rgba_p = (uint16_t*)((PyArrayObject*)rgba)->data;
152
153     // Cache tiles to speed up small brush strokes with lots of dabs, like charcoal.
154     // Not caching readonly requests; they are alternated with write requests anyway.
155     if (!readonly) {
156       if (tileMemoryValid < TILE_MEMORY_SIZE) {
157         tileMemoryValid++;
158       }
159       // We always overwrite the oldest cache entry.
160       // We are mainly optimizing for strokes with radius smaller than one tile.
161       tileMemory[tileMemoryWrite].tx = tx;
162       tileMemory[tileMemoryWrite].ty = ty;
163       tileMemory[tileMemoryWrite].rgba_p = rgba_p;
164       tileMemoryWrite = (tileMemoryWrite + 1) % TILE_MEMORY_SIZE;
165     }
166     return rgba_p;
167   }
168
169   // returns true if the surface was modified
170   bool draw_dab (float x, float y, 
171                  float radius, 
172                  float color_r, float color_g, float color_b,
173                  float opaque, float hardness = 0.5,
174                  float eraser_target_alpha = 1.0,
175                  float aspect_ratio = 1.0, float angle = 0.0,
176                  float lock_alpha = 0.0
177                  ) {
178
179     opaque = CLAMP(opaque, 0.0, 1.0);
180     hardness = CLAMP(hardness, 0.0, 1.0);
181     if (opaque == 0.0) return false;
182     if (radius < 0.1) return false;
183     if (hardness == 0.0) return false; // infintly small point, rest transparent
184     assert(atomic > 0);
185
186     float normal = 1.0;
187
188     float eraser = CLAMP(1.0 - eraser_target_alpha, 0.0, 1.0);
189     normal *= 1.0-eraser;
190
191     lock_alpha = CLAMP(lock_alpha, 0.0, 1.0);
192     normal *= 1.0-lock_alpha;
193     eraser *= 1.0-lock_alpha;
194
195     if (!(normal || eraser || lock_alpha)) {
196       // nothing to do
197       return false;
198     }
199
200         if (aspect_ratio<1.0) aspect_ratio=1.0;
201
202     float r_fringe;
203     int xp, yp;
204     float xx, yy, rr;
205     float one_over_radius2;
206
207     uint16_t c_r = color_r * (1<<15);
208     uint16_t c_g = color_g * (1<<15);
209     uint16_t c_b = color_b * (1<<15);
210     c_r = CLAMP(c_r, 0, (1<<15));
211     c_g = CLAMP(c_g, 0, (1<<15));
212     c_b = CLAMP(c_b, 0, (1<<15));
213
214     r_fringe = radius + 1;
215     rr = radius*radius;
216     one_over_radius2 = 1.0/rr;
217
218     int tx1 = floor(floor(x - r_fringe) / TILE_SIZE);
219     int tx2 = floor(floor(x + r_fringe) / TILE_SIZE);
220     int ty1 = floor(floor(y - r_fringe) / TILE_SIZE);
221     int ty2 = floor(floor(y + r_fringe) / TILE_SIZE);
222     int tx, ty;
223     for (ty = ty1; ty <= ty2; ty++) {
224       for (tx = tx1; tx <= tx2; tx++) {
225         float xc = x - tx*TILE_SIZE;
226         float yc = y - ty*TILE_SIZE;
227
228         int x0 = floor (xc - r_fringe);
229         int y0 = floor (yc - r_fringe);
230         int x1 = ceil (xc + r_fringe);
231         int y1 = ceil (yc + r_fringe);
232         if (x0 < 0) x0 = 0;
233         if (y0 < 0) y0 = 0;
234         if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
235         if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
236
237                 float angle_rad=angle/360*2*M_PI;
238                 float cs=cos(angle_rad);
239                 float sn=sin(angle_rad);
240
241         // first, we calculate the mask (opacity for each pixel)
242         static uint16_t mask[TILE_SIZE*TILE_SIZE+2*TILE_SIZE];
243         // we do run length encoding: if opacity is zero, the next
244         // value in the mask is the number of pixels that can be skipped.
245         uint16_t * mask_p = mask;
246         int skip=0;
247
248         skip += y0*TILE_SIZE;
249         for (yp = y0; yp <= y1; yp++) {
250           yy = (yp + 0.5 - yc);
251           skip += x0;
252           for (xp = x0; xp <= x1; xp++) {
253             xx = (xp + 0.5 - xc);
254             // code duplication, see brush::count_dabs_to()
255                 float yyr=(yy*cs-xx*sn)*aspect_ratio;
256                         float xxr=yy*sn+xx*cs;
257             rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
258             // rr is in range 0.0..1.0*sqrt(2)
259
260             float opa = 0;
261             if (rr <= 1.0) {
262               opa = opaque;
263               if (hardness < 1.0) {
264                 if (rr < hardness) {
265                   opa *= rr + 1-(rr/hardness);
266                   // hardness == 0 is nonsense, excluded above
267                 } else {
268                   opa *= hardness/(1-hardness)*(1-rr);
269                 }
270               }
271
272               // We are manipulating pixels with premultiplied alpha directly.
273               // This is an "over" operation (opa = topAlpha).
274               // In the formula below, topColor is assumed to be premultiplied.
275               //
276               //               opa_a      <   opa_b      >
277               // resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
278               // resultColor = topColor + (1.0 - topAlpha) * bottomColor
279               //
280               // OPTIMIZE: don't use floats here in the inner loop?
281
282 #ifdef HEAVY_DEBUG
283               assert(opa >= 0.0 && opa <= 1.0);
284               assert(eraser_target_alpha >= 0.0 && eraser_target_alpha <= 1.0);
285 #endif
286             }
287             uint16_t opa_ = opa * (1<<15);
288             if (!opa_) {
289               skip++;
290             } else {
291               if (skip) {
292                 *mask_p++ = 0;
293                 *mask_p++ = skip*4;
294                 skip = 0;
295               }
296               *mask_p++ = opa_;
297             }
298           }
299           skip += TILE_SIZE-xp;
300         }
301         *mask_p++ = 0;
302         *mask_p++ = 0;
303
304         // second, we use the mask to stamp a dab for each activated blend mode
305
306         uint16_t * rgba_p = get_tile_memory(tx, ty, false);
307         if (!rgba_p) {
308           printf("Python exception during draw_dab()!\n");
309           return true;
310         }
311
312         if (normal > 0.00001)
313           draw_dab_pixels_BlendMode_Normal(mask, rgba_p,
314                                            c_r, c_g, c_b, normal);
315         if (eraser > 0.00001)
316           draw_dab_pixels_BlendMode_Eraser(mask, rgba_p,
317                                            c_r, c_g, c_b, eraser);
318         if (lock_alpha > 0.00001)
319           draw_dab_pixels_BlendMode_LockAlpha(mask, rgba_p,
320                                               c_r, c_g, c_b, lock_alpha);
321       }
322     }
323
324
325     {
326       // expand the bounding box to include the region we just drawed
327       int bb_x, bb_y, bb_w, bb_h;
328       bb_x = floor (x - (radius+1));
329       bb_y = floor (y - (radius+1));
330       /* FIXME: think about it exactly */
331       bb_w = ceil (2*(radius+1));
332       bb_h = ceil (2*(radius+1));
333
334       ExpandRectToIncludePoint (&dirty_bbox, bb_x, bb_y);
335       ExpandRectToIncludePoint (&dirty_bbox, bb_x+bb_w-1, bb_y+bb_h-1);
336     }
337
338     return true;
339   }
340
341   void get_color (float x, float y, 
342                   float radius, 
343                   float * color_r, float * color_g, float * color_b, float * color_a
344                   ) {
345
346     float r_fringe;
347     int xp, yp;
348     float xx, yy, rr;
349     float one_over_radius2;
350
351     if (radius < 1.0) radius = 1.0;
352     const float hardness = 0.5;
353     const float opaque = 1.0;
354
355     float sum_r, sum_g, sum_b, sum_a, sum_weight;
356     sum_r = sum_g = sum_b = sum_a = sum_weight = 0.0;
357
358     // in case we return with an error
359     *color_r = 0.0;
360     *color_g = 1.0;
361     *color_b = 0.0;
362
363     // WARNING: some code duplication with draw_dab
364
365     r_fringe = radius + 1;
366     rr = radius*radius;
367     one_over_radius2 = 1.0/rr;
368
369     int tx1 = floor(floor(x - r_fringe) / TILE_SIZE);
370     int tx2 = floor(floor(x + r_fringe) / TILE_SIZE);
371     int ty1 = floor(floor(y - r_fringe) / TILE_SIZE);
372     int ty2 = floor(floor(y + r_fringe) / TILE_SIZE);
373     int tx, ty;
374     for (ty = ty1; ty <= ty2; ty++) {
375       for (tx = tx1; tx <= tx2; tx++) {
376         uint16_t * rgba_p = get_tile_memory(tx, ty, true);
377         if (!rgba_p) {
378           printf("Python exception during get_color()!\n");
379           return;
380         }
381
382         float xc = x - tx*TILE_SIZE;
383         float yc = y - ty*TILE_SIZE;
384
385         int x0 = floor (xc - r_fringe);
386         int y0 = floor (yc - r_fringe);
387         int x1 = ceil (xc + r_fringe);
388         int y1 = ceil (yc + r_fringe);
389         if (x0 < 0) x0 = 0;
390         if (y0 < 0) y0 = 0;
391         if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
392         if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
393
394         for (yp = y0; yp <= y1; yp++) {
395           yy = (yp + 0.5 - yc);
396           yy *= yy;
397           for (xp = x0; xp <= x1; xp++) {
398             xx = (xp + 0.5 - xc);
399             xx *= xx;
400             rr = (yy + xx) * one_over_radius2;
401             // rr is in range 0.0..1.0*sqrt(2)
402
403             if (rr <= 1.0) {
404               float opa = opaque;
405               if (hardness < 1.0) {
406                 if (rr < hardness) {
407                   opa *= rr + 1-(rr/hardness);
408                   // hardness == 0 is nonsense, excluded above
409                 } else {
410                   opa *= hardness/(1-hardness)*(1-rr);
411                 }
412               }
413
414               // note that we are working on premultiplied alpha
415               // we do not un-premultiply it yet, so colors are weighted with their alpha
416               int idx = (yp*TILE_SIZE + xp)*4;
417               sum_weight += opa;
418               sum_r      += opa*rgba_p[idx+0]/(1<<15);
419               sum_g      += opa*rgba_p[idx+1]/(1<<15);
420               sum_b      += opa*rgba_p[idx+2]/(1<<15);
421               sum_a      += opa*rgba_p[idx+3]/(1<<15);
422             }
423           }
424         }
425       }
426     }
427
428     assert(sum_weight > 0.0);
429     sum_a /= sum_weight;
430     sum_r /= sum_weight;
431     sum_g /= sum_weight;
432     sum_b /= sum_weight;
433
434     *color_a = sum_a;
435     // now un-premultiply the alpha
436     if (sum_a > 0.0) {
437       *color_r = sum_r / sum_a;
438       *color_g = sum_g / sum_a;
439       *color_b = sum_b / sum_a;
440     } else {
441       // it is all transparent, so don't care about the colors
442       // (let's make them ugly so bugs will be visible)
443       *color_r = 0.0;
444       *color_g = 1.0;
445       *color_b = 0.0;
446     }
447
448     // fix rounding problems that do happen due to floating point math
449     *color_r = CLAMP(*color_r, 0.0, 1.0);
450     *color_g = CLAMP(*color_g, 0.0, 1.0);
451     *color_b = CLAMP(*color_b, 0.0, 1.0);
452     *color_a = CLAMP(*color_a, 0.0, 1.0);
453   }
454
455   float get_alpha (float x, float y, float radius) {
456     float color_r, color_g, color_b, color_a;
457     get_color (x, y, radius, &color_r, &color_g, &color_b, &color_a);
458     return color_a;
459   }
460 };
461