OSDN Git Service

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