OSDN Git Service

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