1 /* This file is part of MyPaint.
2 * Copyright (C) 2008-2011 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];
31 TiledSurface(PyObject * self_) {
32 self = self_; // no need to incref
41 assert(dirty_bbox.w == 0);
42 assert(tileMemoryValid == 0);
46 PyObject* end_atomic() {
52 Rect bbox = dirty_bbox; // copy to safety before calling python
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;
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;
71 PyObject* rgba = PyObject_CallMethod(self, "get_tile_memory", "(iii)", tx, ty, readonly);
73 printf("Python exception during get_tile_memory()! The next traceback might be wrong.\n");
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);
84 // tiledsurface.py will keep a reference in its tiledict, at least until the final end_atomic()
86 uint16_t * rgba_p = (uint16_t*)((PyArrayObject*)rgba)->data;
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.
91 if (tileMemoryValid < TILE_MEMORY_SIZE) {
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;
104 void render_dab_mask (uint16_t * mask,
108 float aspect_ratio, float angle
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
118 float one_over_radius2;
120 r_fringe = radius + 1;
122 one_over_radius2 = 1.0/rr;
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
134 // +-----------*> rr = (distance_from_center/radius)^2
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
143 float angle_rad=angle/360*2*M_PI;
144 float cs=cos(angle_rad);
145 float sn=sin(angle_rad);
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);
153 if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
154 if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
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;
162 skip += y0*TILE_SIZE;
163 for (yp = y0; yp <= y1; yp++) {
166 for (xp = x0; xp <= x1; xp++) {
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)
177 if (rr <= hardness) {
178 opa = segment1_offset;
179 fac = segment1_slope;
181 opa = segment2_offset;
182 fac = segment2_slope;
187 assert(isfinite(opa));
188 assert(opa >= 0.0 && opa <= 1.0);
194 uint16_t opa_ = opa * (1<<15);
206 skip += TILE_SIZE-xp;
212 // returns true if the surface was modified
213 bool draw_dab (float x, float y,
215 float color_r, float color_g, float color_b,
216 float opaque, float hardness = 0.5,
218 float aspect_ratio = 1.0, float angle = 0.0,
219 float lock_alpha = 0.0
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;
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);
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);
239 // blending mode preparation
242 normal *= 1.0-lock_alpha;
244 if (aspect_ratio<1.0) aspect_ratio=1.0;
246 float r_fringe = radius + 1;
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);
253 for (ty = ty1; ty <= ty2; ty++) {
254 for (tx = tx1; tx <= tx2; tx++) {
256 uint16_t * rgba_p = get_tile_memory(tx, ty, false);
258 printf("Python exception during draw_dab()!\n");
262 // first, we calculate the mask (opacity for each pixel)
263 static uint16_t mask[TILE_SIZE*TILE_SIZE+2*TILE_SIZE];
265 render_dab_mask(mask,
273 // second, we use the mask to stamp a dab for each activated blend mode
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));
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));
287 draw_dab_pixels_BlendMode_LockAlpha(mask, rgba_p,
288 color_r_, color_g_, color_b_, lock_alpha*opaque*(1<<15));
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));
303 ExpandRectToIncludePoint (&dirty_bbox, bb_x, bb_y);
304 ExpandRectToIncludePoint (&dirty_bbox, bb_x+bb_w-1, bb_y+bb_h-1);
310 void get_color (float x, float y,
312 float * color_r, float * color_g, float * color_b, float * color_a
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;
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;
325 // in case we return with an error
330 // WARNING: some code duplication with draw_dab
332 r_fringe = radius + 1;
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);
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);
343 printf("Python exception during get_color()!\n");
347 // first, we calculate the mask (opacity for each pixel)
348 static uint16_t mask[TILE_SIZE*TILE_SIZE+2*TILE_SIZE];
350 render_dab_mask(mask,
358 get_color_pixels_accumulate (mask, rgba_p,
359 &sum_weight, &sum_r, &sum_g, &sum_b, &sum_a);
364 assert(sum_weight > 0.0);
371 // now un-premultiply the alpha
373 *color_r = sum_r / sum_a;
374 *color_g = sum_g / sum_a;
375 *color_b = sum_b / sum_a;
377 // it is all transparent, so don't care about the colors
378 // (let's make them ugly so bugs will be visible)
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);
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);