OSDN Git Service

2a7092539be5a05e91dab921262d2bf465672ce8
[mypaint-anime/master.git] / lib / tiledsurface.hpp
1 /* This file is part of MyPaint.
2  * Copyright (C) 2008-2011 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 public:
31   TiledSurface(PyObject * self_) {
32     self = self_; // no need to incref
33     atomic = 0;
34     dirty_bbox.w = 0;
35     tileMemoryValid = 0;
36     tileMemoryWrite = 0;
37   }
38
39   void begin_atomic() {
40     if (atomic == 0) {
41       assert(dirty_bbox.w == 0);
42       assert(tileMemoryValid == 0);
43     }
44     atomic++;
45   }
46   PyObject* end_atomic() {
47     assert(atomic > 0);
48     atomic--;
49     if (atomic == 0) {
50       tileMemoryValid = 0;
51       tileMemoryWrite = 0;
52       Rect bbox = dirty_bbox; // copy to safety before calling python
53       dirty_bbox.w = 0;
54       if (bbox.w > 0) {
55         PyObject* res;
56         // OPTIMIZE: send a list tiles for minimal compositing? (but profile the code first)
57         res = PyObject_CallMethod(self, "notify_observers", "(iiii)", bbox.x, bbox.y, bbox.w, bbox.h);
58         if (!res) return NULL;
59       }
60     }
61     Py_RETURN_NONE;
62   }
63
64   uint16_t * get_tile_memory(int tx, int ty, bool readonly) {
65     // We assume that the memory location does not change between begin_atomic() and end_atomic().
66     for (int i=0; i<tileMemoryValid; i++) {
67       if (tileMemory[i].tx == tx and tileMemory[i].ty == ty) {
68         return tileMemory[i].rgba_p;
69       }
70     }
71     PyObject* rgba = PyObject_CallMethod(self, "get_tile_memory", "(iii)", tx, ty, readonly);
72     if (rgba == NULL) {
73       printf("Python exception during get_tile_memory()! The next traceback might be wrong.\n");
74       return NULL;
75     }
76 #ifdef HEAVY_DEBUG
77        assert(PyArray_NDIM(rgba) == 3);
78        assert(PyArray_DIM(rgba, 0) == TILE_SIZE);
79        assert(PyArray_DIM(rgba, 1) == TILE_SIZE);
80        assert(PyArray_DIM(rgba, 2) == 4);
81        assert(PyArray_ISCARRAY(rgba));
82        assert(PyArray_TYPE(rgba) == NPY_UINT16);
83 #endif
84     // tiledsurface.py will keep a reference in its tiledict, at least until the final end_atomic()
85     Py_DECREF(rgba);
86     uint16_t * rgba_p = (uint16_t*)((PyArrayObject*)rgba)->data;
87
88     // Cache tiles to speed up small brush strokes with lots of dabs, like charcoal.
89     // Not caching readonly requests; they are alternated with write requests anyway.
90     if (!readonly) {
91       if (tileMemoryValid < TILE_MEMORY_SIZE) {
92         tileMemoryValid++;
93       }
94       // We always overwrite the oldest cache entry.
95       // We are mainly optimizing for strokes with radius smaller than one tile.
96       tileMemory[tileMemoryWrite].tx = tx;
97       tileMemory[tileMemoryWrite].ty = ty;
98       tileMemory[tileMemoryWrite].rgba_p = rgba_p;
99       tileMemoryWrite = (tileMemoryWrite + 1) % TILE_MEMORY_SIZE;
100     }
101     return rgba_p;
102   }
103
104   void render_dab_mask (uint16_t * mask,
105                         float x, float y,
106                         float radius,
107                         float hardness,
108                         float aspect_ratio, float angle
109                         ) {
110
111     hardness = CLAMP(hardness, 0.0, 1.0);
112         if (aspect_ratio<1.0) aspect_ratio=1.0;
113     assert(hardness != 0.0); // assured by caller
114
115     float r_fringe;
116     int xp, yp;
117     float xx, yy, rr;
118     float one_over_radius2;
119
120     r_fringe = radius + 1;
121     rr = radius*radius;
122     one_over_radius2 = 1.0/rr;
123
124     // Dab opacity gradually fades out from the center (rr=0) to
125     // fringe (rr=1) of the dab. How exactly depends on the hardness.
126     // We use two linear segments, for which we pre-calculate slope
127     // and offset here.
128     //
129     // opa
130     // ^
131     // *   .
132     // |        *
133     // |          .
134     // +-----------*> rr = (distance_from_center/radius)^2
135     // 0           1
136     //
137     float segment1_offset = 1.0;
138     float segment1_slope  = -(1.0/hardness - 1.0);
139     float segment2_offset = hardness/(1.0-hardness);
140     float segment2_slope  = -hardness/(1.0-hardness);
141     // for hardness == 1.0, segment2 will never be used
142
143     float angle_rad=angle/360*2*M_PI;
144     float cs=cos(angle_rad);
145     float sn=sin(angle_rad);
146
147     int x0 = floor (x - r_fringe);
148     int y0 = floor (y - r_fringe);
149     int x1 = ceil (x + r_fringe);
150     int y1 = ceil (y + r_fringe);
151     if (x0 < 0) x0 = 0;
152     if (y0 < 0) y0 = 0;
153     if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
154     if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
155     
156     
157     // we do run length encoding: if opacity is zero, the next
158     // value in the mask is the number of pixels that can be skipped.
159     uint16_t * mask_p = mask;
160     int skip=0;
161     
162     skip += y0*TILE_SIZE;
163     for (yp = y0; yp <= y1; yp++) {
164       yy = (yp + 0.5 - y);
165       skip += x0;
166       for (xp = x0; xp <= x1; xp++) {
167         xx = (xp + 0.5 - x);
168         // code duplication, see brush::count_dabs_to()
169         float yyr=(yy*cs-xx*sn)*aspect_ratio;
170         float xxr=yy*sn+xx*cs;
171         rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
172         // rr is in range 0.0..1.0*sqrt(2)
173         
174         float opa;
175         if (rr <= 1.0) {
176           float fac;
177           if (rr <= hardness) {
178             opa = segment1_offset;
179             fac = segment1_slope;
180           } else {
181             opa = segment2_offset;
182             fac = segment2_slope;
183           }
184           opa += rr*fac;
185           
186 #ifdef HEAVY_DEBUG
187           assert(isfinite(opa));
188           assert(opa >= 0.0 && opa <= 1.0);
189 #endif
190         } else {
191           opa = 0.0;
192         }
193
194         uint16_t opa_ = opa * (1<<15);
195         if (!opa_) {
196           skip++;
197         } else {
198           if (skip) {
199             *mask_p++ = 0;
200             *mask_p++ = skip*4;
201             skip = 0;
202           }
203           *mask_p++ = opa_;
204         }
205       }
206       skip += TILE_SIZE-xp;
207     }
208     *mask_p++ = 0;
209     *mask_p++ = 0;
210   }
211   
212   // returns true if the surface was modified
213   bool draw_dab (float x, float y, 
214                  float radius, 
215                  float color_r, float color_g, float color_b,
216                  float opaque, float hardness = 0.5,
217                  float color_a = 1.0,
218                  float aspect_ratio = 1.0, float angle = 0.0,
219                  float lock_alpha = 0.0
220                  ) {
221
222     opaque = CLAMP(opaque, 0.0, 1.0);
223     hardness = CLAMP(hardness, 0.0, 1.0);
224     lock_alpha = CLAMP(lock_alpha, 0.0, 1.0);
225     if (radius < 0.1) return false; // don't bother with dabs smaller than 0.1 pixel
226     if (hardness == 0.0) return false; // infintly small center point, fully transparent outside
227     if (opaque == 0.0) return false;
228     assert(atomic > 0);
229
230     color_r = CLAMP(color_r, 0.0, 1.0);
231     color_g = CLAMP(color_g, 0.0, 1.0);
232     color_b = CLAMP(color_b, 0.0, 1.0);
233     color_a = CLAMP(color_a, 0.0, 1.0);
234
235     uint16_t color_r_ = color_r * (1<<15);
236     uint16_t color_g_ = color_g * (1<<15);
237     uint16_t color_b_ = color_b * (1<<15);
238
239     // blending mode preparation
240     float normal = 1.0;
241
242     normal *= 1.0-lock_alpha;
243
244         if (aspect_ratio<1.0) aspect_ratio=1.0;
245
246     float r_fringe = radius + 1;
247
248     int tx1 = floor(floor(x - r_fringe) / TILE_SIZE);
249     int tx2 = floor(floor(x + r_fringe) / TILE_SIZE);
250     int ty1 = floor(floor(y - r_fringe) / TILE_SIZE);
251     int ty2 = floor(floor(y + r_fringe) / TILE_SIZE);
252     int tx, ty;
253     for (ty = ty1; ty <= ty2; ty++) {
254       for (tx = tx1; tx <= tx2; tx++) {
255
256         uint16_t * rgba_p = get_tile_memory(tx, ty, false);
257         if (!rgba_p) {
258           printf("Python exception during draw_dab()!\n");
259           return true;
260         }
261
262         // first, we calculate the mask (opacity for each pixel)
263         static uint16_t mask[TILE_SIZE*TILE_SIZE+2*TILE_SIZE];
264
265         render_dab_mask(mask,
266                         x - tx*TILE_SIZE,
267                         y - ty*TILE_SIZE,
268                         radius,
269                         hardness,
270                         aspect_ratio, angle
271                         );
272
273         // second, we use the mask to stamp a dab for each activated blend mode
274
275         if (normal) {
276           if (color_a == 1.0) {
277             draw_dab_pixels_BlendMode_Normal(mask, rgba_p,
278                                              color_r_, color_g_, color_b_, normal*opaque*(1<<15));
279           } else {
280             // normal case for brushes that use smudging (eg. watercolor)
281             draw_dab_pixels_BlendMode_Normal_and_Eraser(mask, rgba_p,
282                                                         color_r_, color_g_, color_b_, color_a*(1<<15), normal*opaque*(1<<15));
283           }
284         }
285
286         if (lock_alpha) {
287           draw_dab_pixels_BlendMode_LockAlpha(mask, rgba_p,
288                                               color_r_, color_g_, color_b_, lock_alpha*opaque*(1<<15));
289         }
290       }
291     }
292
293
294     {
295       // expand the bounding box to include the region we just drawed
296       int bb_x, bb_y, bb_w, bb_h;
297       bb_x = floor (x - (radius+1));
298       bb_y = floor (y - (radius+1));
299       /* FIXME: think about it exactly */
300       bb_w = ceil (2*(radius+1));
301       bb_h = ceil (2*(radius+1));
302
303       ExpandRectToIncludePoint (&dirty_bbox, bb_x, bb_y);
304       ExpandRectToIncludePoint (&dirty_bbox, bb_x+bb_w-1, bb_y+bb_h-1);
305     }
306
307     return true;
308   }
309
310   void get_color (float x, float y, 
311                   float radius, 
312                   float * color_r, float * color_g, float * color_b, float * color_a
313                   ) {
314
315     float r_fringe;
316
317     if (radius < 1.0) radius = 1.0;
318     const float hardness = 0.5;
319     const float aspect_ratio = 1.0;
320     const float angle = 0.0;
321
322     float sum_weight, sum_r, sum_g, sum_b, sum_a;
323     sum_weight = sum_r = sum_g = sum_b = sum_a = 0.0;
324
325     // in case we return with an error
326     *color_r = 0.0;
327     *color_g = 1.0;
328     *color_b = 0.0;
329
330     // WARNING: some code duplication with draw_dab
331
332     r_fringe = radius + 1;
333
334     int tx1 = floor(floor(x - r_fringe) / TILE_SIZE);
335     int tx2 = floor(floor(x + r_fringe) / TILE_SIZE);
336     int ty1 = floor(floor(y - r_fringe) / TILE_SIZE);
337     int ty2 = floor(floor(y + r_fringe) / TILE_SIZE);
338     int tx, ty;
339     for (ty = ty1; ty <= ty2; ty++) {
340       for (tx = tx1; tx <= tx2; tx++) {
341         uint16_t * rgba_p = get_tile_memory(tx, ty, true);
342         if (!rgba_p) {
343           printf("Python exception during get_color()!\n");
344           return;
345         }
346
347         // first, we calculate the mask (opacity for each pixel)
348         static uint16_t mask[TILE_SIZE*TILE_SIZE+2*TILE_SIZE];
349
350         render_dab_mask(mask,
351                         x - tx*TILE_SIZE,
352                         y - ty*TILE_SIZE,
353                         radius,
354                         hardness,
355                         aspect_ratio, angle
356                         );
357
358         get_color_pixels_accumulate (mask, rgba_p,
359                                      &sum_weight, &sum_r, &sum_g, &sum_b, &sum_a);
360
361       }
362     }
363
364     assert(sum_weight > 0.0);
365     sum_a /= sum_weight;
366     sum_r /= sum_weight;
367     sum_g /= sum_weight;
368     sum_b /= sum_weight;
369
370     *color_a = sum_a;
371     // now un-premultiply the alpha
372     if (sum_a > 0.0) {
373       *color_r = sum_r / sum_a;
374       *color_g = sum_g / sum_a;
375       *color_b = sum_b / sum_a;
376     } else {
377       // it is all transparent, so don't care about the colors
378       // (let's make them ugly so bugs will be visible)
379       *color_r = 0.0;
380       *color_g = 1.0;
381       *color_b = 0.0;
382     }
383
384     // fix rounding problems that do happen due to floating point math
385     *color_r = CLAMP(*color_r, 0.0, 1.0);
386     *color_g = CLAMP(*color_g, 0.0, 1.0);
387     *color_b = CLAMP(*color_b, 0.0, 1.0);
388     *color_a = CLAMP(*color_a, 0.0, 1.0);
389   }
390
391   float get_alpha (float x, float y, float radius) {
392     float color_r, color_g, color_b, color_a;
393     get_color (x, y, radius, &color_r, &color_g, &color_b, &color_a);
394     return color_a;
395   }
396 };
397