1 /* This file is part of MyPaint.
2 * Copyright (C) 2008 by Martin Renold <martinxyz@gmx.ch>
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.
12 class TiledSurface : public Surface {
13 // the Python half of this class is in tiledsurface.py
19 // caching tile memory location (optimization)
20 #define TILE_MEMORY_SIZE 8
25 TileMemory tileMemory[TILE_MEMORY_SIZE];
30 TiledSurface(PyObject * self_) {
31 self = self_; // no need to incref
40 assert(dirty_bbox.w == 0);
41 assert(tileMemoryValid == 0);
51 Rect bbox = dirty_bbox; // copy to safety before calling python
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");
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;
70 PyObject* rgba = PyObject_CallMethod(self, "get_tile_memory", "(iii)", tx, ty, readonly);
72 printf("Python exception during get_tile_memory()! The next traceback might be wrong.\n");
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);
83 // tiledsurface.py will keep a reference in its tiledict, at least until the final end_atomic()
85 uint16_t * rgba_p = (uint16_t*)((PyArrayObject*)rgba)->data;
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.
90 if (tileMemoryValid < TILE_MEMORY_SIZE) {
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;
103 // returns true if the surface was modified
104 bool draw_dab (float x, float y,
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) {
111 if (aspect_ratio<1.0) aspect_ratio=1.0;
116 float one_over_radius2;
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));
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
134 r_fringe = radius + 1;
136 one_over_radius2 = 1.0/rr;
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);
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);
147 printf("Python exception during draw_dab()!\n");
151 float xc = x - tx*TILE_SIZE;
152 float yc = y - ty*TILE_SIZE;
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);
160 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
161 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
163 float angle_rad=angle*M_PI/180.0;
164 float cs=cos(angle_rad);
165 float sn=sin(angle_rad);
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)
178 if (hardness < 1.0) {
180 opa *= rr + 1-(rr/hardness);
181 // hardness == 0 is nonsense, excluded above
183 opa *= hardness/(hardness-1)*(rr-1);
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.
192 // resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
193 // resultColor = topColor + (1.0 - topAlpha) * bottomColor
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?
199 //assert(opa >= 0.0 && opa <= 1.0);
200 //assert(eraser_target_alpha >= 0.0 && eraser_target_alpha <= 1.0);
202 uint32_t opa_a = (1<<15)*opa; // topAlpha
203 uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
205 // only for eraser, or for painting with translucent-making colors
206 opa_a *= eraser_target_alpha;
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);
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));
228 ExpandRectToIncludePoint (&dirty_bbox, bb_x, bb_y);
229 ExpandRectToIncludePoint (&dirty_bbox, bb_x+bb_w-1, bb_y+bb_h-1);
235 void get_color (float x, float y,
237 float * color_r, float * color_g, float * color_b, float * color_a
243 float one_over_radius2;
245 if (radius < 1.0) radius = 1.0;
246 const float hardness = 0.5;
247 const float opaque = 1.0;
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;
252 // in case we return with an error
257 // WARNING: some code duplication with draw_dab
259 r_fringe = radius + 1;
261 one_over_radius2 = 1.0/rr;
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);
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);
272 printf("Python exception during get_color()!\n");
276 float xc = x - tx*TILE_SIZE;
277 float yc = y - ty*TILE_SIZE;
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);
285 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
286 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
288 for (yp = y0; yp <= y1; yp++) {
289 yy = (yp + 0.5 - yc);
291 for (xp = x0; xp <= x1; xp++) {
292 xx = (xp + 0.5 - xc);
294 rr = (yy + xx) * one_over_radius2;
295 // rr is in range 0.0..1.0*sqrt(2)
299 if (hardness < 1.0) {
301 opa *= rr + 1-(rr/hardness);
302 // hardness == 0 is nonsense, excluded above
304 opa *= hardness/(hardness-1)*(rr-1);
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;
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);
322 assert(sum_weight > 0.0);
329 // now un-premultiply the alpha
331 *color_r = sum_r / sum_a;
332 *color_g = sum_g / sum_a;
333 *color_b = sum_b / sum_a;
335 // it is all transparent, so don't care about the colors
336 // (let's make them ugly so bugs will be visible)
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);
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);