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 void draw_dab_pixels_BlendMode_Normal (uint16_t * mask,
20 for (; mask[0]; mask++, rgba+=4) {
21 uint32_t opa_a = mask[0]*opacity2; // topAlpha
22 uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
23 rgba[3] = opa_a + (opa_b*rgba[3])/(1<<15);
24 rgba[0] = (opa_a*color_r_ + opa_b*rgba[0])/(1<<15);
25 rgba[1] = (opa_a*color_g_ + opa_b*rgba[1])/(1<<15);
26 rgba[2] = (opa_a*color_b_ + opa_b*rgba[2])/(1<<15);
34 void draw_dab_pixels_BlendMode_Eraser (uint16_t * mask,
41 for (; mask[0]; mask++, rgba+=4) {
42 uint32_t opa_b = mask[0]*opacity2; // topAlpha
43 opa_b = (1<<15)-opa_b;
44 rgba[3] = (opa_b*rgba[3])/(1<<15);
45 rgba[0] = (opa_b*rgba[0])/(1<<15);
46 rgba[1] = (opa_b*rgba[1])/(1<<15);
47 rgba[2] = (opa_b*rgba[2])/(1<<15);
55 void draw_dab_pixels_BlendMode_LockAlpha (uint16_t * mask,
63 for (; mask[0]; mask++, rgba+=4) {
64 uint32_t opa_a = mask[0]*opacity2; // topAlpha
65 uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
68 rgba[0] = (opa_a*color_r_ + opa_b*rgba[0])/(1<<15);
69 rgba[1] = (opa_a*color_g_ + opa_b*rgba[1])/(1<<15);
70 rgba[2] = (opa_a*color_b_ + opa_b*rgba[2])/(1<<15);
78 class TiledSurface : public Surface {
79 // the Python half of this class is in tiledsurface.py
85 // caching tile memory location (optimization)
86 #define TILE_MEMORY_SIZE 8
91 TileMemory tileMemory[TILE_MEMORY_SIZE];
96 TiledSurface(PyObject * self_) {
97 self = self_; // no need to incref
104 void begin_atomic() {
106 assert(dirty_bbox.w == 0);
107 assert(tileMemoryValid == 0);
117 Rect bbox = dirty_bbox; // copy to safety before calling python
121 // OPTIMIZE: send a list tiles for minimal compositing? (but profile the code first)
122 res = PyObject_CallMethod(self, "notify_observers", "(iiii)", bbox.x, bbox.y, bbox.w, bbox.h);
123 if (!res) printf("Python exception during notify_observers! FIXME: Traceback will not be accurate.\n");
129 uint16_t * get_tile_memory(int tx, int ty, bool readonly) {
130 // We assume that the memory location does not change between begin_atomic() and end_atomic().
131 for (int i=0; i<tileMemoryValid; i++) {
132 if (tileMemory[i].tx == tx and tileMemory[i].ty == ty) {
133 return tileMemory[i].rgba_p;
136 PyObject* rgba = PyObject_CallMethod(self, "get_tile_memory", "(iii)", tx, ty, readonly);
138 printf("Python exception during get_tile_memory()! The next traceback might be wrong.\n");
142 assert(PyArray_NDIM(rgba) == 3);
143 assert(PyArray_DIM(rgba, 0) == TILE_SIZE);
144 assert(PyArray_DIM(rgba, 1) == TILE_SIZE);
145 assert(PyArray_DIM(rgba, 2) == 4);
146 assert(PyArray_ISCARRAY(rgba));
147 assert(PyArray_TYPE(rgba) == NPY_UINT16);
149 // tiledsurface.py will keep a reference in its tiledict, at least until the final end_atomic()
151 uint16_t * rgba_p = (uint16_t*)((PyArrayObject*)rgba)->data;
153 // Cache tiles to speed up small brush strokes with lots of dabs, like charcoal.
154 // Not caching readonly requests; they are alternated with write requests anyway.
156 if (tileMemoryValid < TILE_MEMORY_SIZE) {
159 // We always overwrite the oldest cache entry.
160 // We are mainly optimizing for strokes with radius smaller than one tile.
161 tileMemory[tileMemoryWrite].tx = tx;
162 tileMemory[tileMemoryWrite].ty = ty;
163 tileMemory[tileMemoryWrite].rgba_p = rgba_p;
164 tileMemoryWrite = (tileMemoryWrite + 1) % TILE_MEMORY_SIZE;
169 // returns true if the surface was modified
170 bool draw_dab (float x, float y,
172 float color_r, float color_g, float color_b,
173 float opaque, float hardness = 0.5,
174 float eraser_target_alpha = 1.0,
175 float aspect_ratio = 1.0, float angle = 0.0,
176 float lock_alpha = 0.0
179 opaque = CLAMP(opaque, 0.0, 1.0);
180 hardness = CLAMP(hardness, 0.0, 1.0);
181 if (opaque == 0.0) return false;
182 if (radius < 0.1) return false;
183 if (hardness == 0.0) return false; // infintly small point, rest transparent
188 float eraser = CLAMP(1.0 - eraser_target_alpha, 0.0, 1.0);
189 normal *= 1.0-eraser;
191 lock_alpha = CLAMP(lock_alpha, 0.0, 1.0);
192 normal *= 1.0-lock_alpha;
193 eraser *= 1.0-lock_alpha;
195 if (!(normal || eraser || lock_alpha)) {
200 if (aspect_ratio<1.0) aspect_ratio=1.0;
205 float one_over_radius2;
207 uint16_t c_r = color_r * (1<<15);
208 uint16_t c_g = color_g * (1<<15);
209 uint16_t c_b = color_b * (1<<15);
210 c_r = CLAMP(c_r, 0, (1<<15));
211 c_g = CLAMP(c_g, 0, (1<<15));
212 c_b = CLAMP(c_b, 0, (1<<15));
214 r_fringe = radius + 1;
216 one_over_radius2 = 1.0/rr;
218 int tx1 = floor(floor(x - r_fringe) / TILE_SIZE);
219 int tx2 = floor(floor(x + r_fringe) / TILE_SIZE);
220 int ty1 = floor(floor(y - r_fringe) / TILE_SIZE);
221 int ty2 = floor(floor(y + r_fringe) / TILE_SIZE);
223 for (ty = ty1; ty <= ty2; ty++) {
224 for (tx = tx1; tx <= tx2; tx++) {
225 float xc = x - tx*TILE_SIZE;
226 float yc = y - ty*TILE_SIZE;
228 int x0 = floor (xc - r_fringe);
229 int y0 = floor (yc - r_fringe);
230 int x1 = ceil (xc + r_fringe);
231 int y1 = ceil (yc + r_fringe);
234 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
235 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
237 float angle_rad=angle/360*2*M_PI;
238 float cs=cos(angle_rad);
239 float sn=sin(angle_rad);
241 // first, we calculate the mask (opacity for each pixel)
242 static uint16_t mask[TILE_SIZE*TILE_SIZE+2*TILE_SIZE];
243 // we do run length encoding: if opacity is zero, the next
244 // value in the mask is the number of pixels that can be skipped.
245 uint16_t * mask_p = mask;
248 skip += y0*TILE_SIZE;
249 for (yp = y0; yp <= y1; yp++) {
250 yy = (yp + 0.5 - yc);
252 for (xp = x0; xp <= x1; xp++) {
253 xx = (xp + 0.5 - xc);
254 // code duplication, see brush::count_dabs_to()
255 float yyr=(yy*cs-xx*sn)*aspect_ratio;
256 float xxr=yy*sn+xx*cs;
257 rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
258 // rr is in range 0.0..1.0*sqrt(2)
263 if (hardness < 1.0) {
265 opa *= rr + 1-(rr/hardness);
266 // hardness == 0 is nonsense, excluded above
268 opa *= hardness/(1-hardness)*(1-rr);
272 // We are manipulating pixels with premultiplied alpha directly.
273 // This is an "over" operation (opa = topAlpha).
274 // In the formula below, topColor is assumed to be premultiplied.
277 // resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
278 // resultColor = topColor + (1.0 - topAlpha) * bottomColor
280 // OPTIMIZE: don't use floats here in the inner loop?
283 assert(opa >= 0.0 && opa <= 1.0);
284 assert(eraser_target_alpha >= 0.0 && eraser_target_alpha <= 1.0);
287 uint16_t opa_ = opa * (1<<15);
299 skip += TILE_SIZE-xp;
304 // second, we use the mask to stamp a dab for each activated blend mode
306 uint16_t * rgba_p = get_tile_memory(tx, ty, false);
308 printf("Python exception during draw_dab()!\n");
312 if (normal > 0.00001)
313 draw_dab_pixels_BlendMode_Normal(mask, rgba_p,
314 c_r, c_g, c_b, normal);
315 if (eraser > 0.00001)
316 draw_dab_pixels_BlendMode_Eraser(mask, rgba_p,
317 c_r, c_g, c_b, eraser);
318 if (lock_alpha > 0.00001)
319 draw_dab_pixels_BlendMode_LockAlpha(mask, rgba_p,
320 c_r, c_g, c_b, lock_alpha);
326 // expand the bounding box to include the region we just drawed
327 int bb_x, bb_y, bb_w, bb_h;
328 bb_x = floor (x - (radius+1));
329 bb_y = floor (y - (radius+1));
330 /* FIXME: think about it exactly */
331 bb_w = ceil (2*(radius+1));
332 bb_h = ceil (2*(radius+1));
334 ExpandRectToIncludePoint (&dirty_bbox, bb_x, bb_y);
335 ExpandRectToIncludePoint (&dirty_bbox, bb_x+bb_w-1, bb_y+bb_h-1);
341 void get_color (float x, float y,
343 float * color_r, float * color_g, float * color_b, float * color_a
349 float one_over_radius2;
351 if (radius < 1.0) radius = 1.0;
352 const float hardness = 0.5;
353 const float opaque = 1.0;
355 float sum_r, sum_g, sum_b, sum_a, sum_weight;
356 sum_r = sum_g = sum_b = sum_a = sum_weight = 0.0;
358 // in case we return with an error
363 // WARNING: some code duplication with draw_dab
365 r_fringe = radius + 1;
367 one_over_radius2 = 1.0/rr;
369 int tx1 = floor(floor(x - r_fringe) / TILE_SIZE);
370 int tx2 = floor(floor(x + r_fringe) / TILE_SIZE);
371 int ty1 = floor(floor(y - r_fringe) / TILE_SIZE);
372 int ty2 = floor(floor(y + r_fringe) / TILE_SIZE);
374 for (ty = ty1; ty <= ty2; ty++) {
375 for (tx = tx1; tx <= tx2; tx++) {
376 uint16_t * rgba_p = get_tile_memory(tx, ty, true);
378 printf("Python exception during get_color()!\n");
382 float xc = x - tx*TILE_SIZE;
383 float yc = y - ty*TILE_SIZE;
385 int x0 = floor (xc - r_fringe);
386 int y0 = floor (yc - r_fringe);
387 int x1 = ceil (xc + r_fringe);
388 int y1 = ceil (yc + r_fringe);
391 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
392 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
394 for (yp = y0; yp <= y1; yp++) {
395 yy = (yp + 0.5 - yc);
397 for (xp = x0; xp <= x1; xp++) {
398 xx = (xp + 0.5 - xc);
400 rr = (yy + xx) * one_over_radius2;
401 // rr is in range 0.0..1.0*sqrt(2)
405 if (hardness < 1.0) {
407 opa *= rr + 1-(rr/hardness);
408 // hardness == 0 is nonsense, excluded above
410 opa *= hardness/(1-hardness)*(1-rr);
414 // note that we are working on premultiplied alpha
415 // we do not un-premultiply it yet, so colors are weighted with their alpha
416 int idx = (yp*TILE_SIZE + xp)*4;
418 sum_r += opa*rgba_p[idx+0]/(1<<15);
419 sum_g += opa*rgba_p[idx+1]/(1<<15);
420 sum_b += opa*rgba_p[idx+2]/(1<<15);
421 sum_a += opa*rgba_p[idx+3]/(1<<15);
428 assert(sum_weight > 0.0);
435 // now un-premultiply the alpha
437 *color_r = sum_r / sum_a;
438 *color_g = sum_g / sum_a;
439 *color_b = sum_b / sum_a;
441 // it is all transparent, so don't care about the colors
442 // (let's make them ugly so bugs will be visible)
448 // fix rounding problems that do happen due to floating point math
449 *color_r = CLAMP(*color_r, 0.0, 1.0);
450 *color_g = CLAMP(*color_g, 0.0, 1.0);
451 *color_b = CLAMP(*color_b, 0.0, 1.0);
452 *color_a = CLAMP(*color_a, 0.0, 1.0);
455 float get_alpha (float x, float y, float radius) {
456 float color_r, color_g, color_b, color_a;
457 get_color (x, y, radius, &color_r, &color_g, &color_b, &color_a);