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.
11 #define MAX_MIPMAP_LEVEL 3
13 class TiledSurface : public Surface {
14 // the Python half of this class is in tiledsurface.py
20 // caching tile memory location (optimization)
21 #define TILE_MEMORY_SIZE 8
26 TileMemory tileMemory[TILE_MEMORY_SIZE];
30 void draw_dab_pixels_BlendMode_Normal (uint16_t * mask,
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
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);
51 mask += 1*(TILE_SIZE-w);
52 rgba += 4*(TILE_SIZE-w);
56 void draw_dab_pixels_BlendMode_Eraser (uint16_t * mask,
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;
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);
77 mask += 1*(TILE_SIZE-w);
78 rgba += 4*(TILE_SIZE-w);
82 void draw_dab_pixels_BlendMode_LockAlpha (uint16_t * mask,
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
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);
106 mask += 1*(TILE_SIZE-w);
107 rgba += 4*(TILE_SIZE-w);
112 TiledSurface(PyObject * self_) {
113 self = self_; // no need to incref
120 void begin_atomic() {
122 assert(dirty_bbox.w == 0);
123 assert(tileMemoryValid == 0);
133 Rect bbox = dirty_bbox; // copy to safety before calling python
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");
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;
152 PyObject* rgba = PyObject_CallMethod(self, "get_tile_memory", "(iii)", tx, ty, readonly);
154 printf("Python exception during get_tile_memory()! The next traceback might be wrong.\n");
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);
165 // tiledsurface.py will keep a reference in its tiledict, at least until the final end_atomic()
167 uint16_t * rgba_p = (uint16_t*)((PyArrayObject*)rgba)->data;
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.
172 if (tileMemoryValid < TILE_MEMORY_SIZE) {
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;
185 // returns true if the surface was modified
186 bool draw_dab (float x, float y,
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
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
204 float eraser = CLAMP(1.0 - eraser_target_alpha, 0.0, 1.0);
205 normal *= 1.0-eraser;
207 lock_alpha = CLAMP(lock_alpha, 0.0, 1.0);
208 normal *= 1.0-lock_alpha;
209 eraser *= 1.0-lock_alpha;
211 if (!(normal || eraser || lock_alpha)) {
216 if (aspect_ratio<1.0) aspect_ratio=1.0;
221 float one_over_radius2;
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));
230 r_fringe = radius + 1;
232 one_over_radius2 = 1.0/rr;
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);
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);
243 printf("Python exception during draw_dab()!\n");
247 float xc = x - tx*TILE_SIZE;
248 float yc = y - ty*TILE_SIZE;
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);
256 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
257 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
259 float angle_rad=angle/360*2*M_PI;
260 float cs=cos(angle_rad);
261 float sn=sin(angle_rad);
263 // first, we calculate the mask (opacity for each pixel)
264 static uint16_t mask_p[TILE_SIZE*TILE_SIZE];
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)
279 if (hardness < 1.0) {
281 opa *= rr + 1-(rr/hardness);
282 // hardness == 0 is nonsense, excluded above
284 opa *= hardness/(1-hardness)*(1-rr);
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.
293 // resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
294 // resultColor = topColor + (1.0 - topAlpha) * bottomColor
296 // OPTIMIZE: don't use floats here in the inner loop?
299 assert(opa >= 0.0 && opa <= 1.0);
300 assert(eraser_target_alpha >= 0.0 && eraser_target_alpha <= 1.0);
303 int idx = yp*TILE_SIZE + xp;
304 mask_p[idx] = (1<<15)*opa;
308 // second, we use the mask to stamp a dab for each activated blend mode
310 uint16_t * mask_start = mask_p + 1*(y0*TILE_SIZE + x0);
311 uint16_t * rgba_start = rgba_p + 4*(y0*TILE_SIZE + x0);
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);
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));
337 ExpandRectToIncludePoint (&dirty_bbox, bb_x, bb_y);
338 ExpandRectToIncludePoint (&dirty_bbox, bb_x+bb_w-1, bb_y+bb_h-1);
344 void get_color (float x, float y,
346 float * color_r, float * color_g, float * color_b, float * color_a
352 float one_over_radius2;
354 if (radius < 1.0) radius = 1.0;
355 const float hardness = 0.5;
356 const float opaque = 1.0;
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;
361 // in case we return with an error
366 // WARNING: some code duplication with draw_dab
368 r_fringe = radius + 1;
370 one_over_radius2 = 1.0/rr;
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);
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);
381 printf("Python exception during get_color()!\n");
385 float xc = x - tx*TILE_SIZE;
386 float yc = y - ty*TILE_SIZE;
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);
394 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
395 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
397 for (yp = y0; yp <= y1; yp++) {
398 yy = (yp + 0.5 - yc);
400 for (xp = x0; xp <= x1; xp++) {
401 xx = (xp + 0.5 - xc);
403 rr = (yy + xx) * one_over_radius2;
404 // rr is in range 0.0..1.0*sqrt(2)
408 if (hardness < 1.0) {
410 opa *= rr + 1-(rr/hardness);
411 // hardness == 0 is nonsense, excluded above
413 opa *= hardness/(1-hardness)*(1-rr);
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;
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);
431 assert(sum_weight > 0.0);
438 // now un-premultiply the alpha
440 *color_r = sum_r / sum_a;
441 *color_g = sum_g / sum_a;
442 *color_b = sum_b / sum_a;
444 // it is all transparent, so don't care about the colors
445 // (let's make them ugly so bugs will be visible)
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);
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);