static int gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface, float x, float y, float radius, float color_r, float color_g, float color_b, float opaque, float hardness, float color_a, float aspect_ratio, float angle, float lock_alpha, float colorize) { GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface; GeglBufferIterator *iter; GeglRectangle dabRect; GimpComponentMask component_mask = surface->component_mask; const float one_over_radius2 = 1.0f / (radius * radius); const double angle_rad = angle / 360 * 2 * M_PI; const float cs = cos(angle_rad); const float sn = sin(angle_rad); float normal_mode; float segment1_slope; float segment2_slope; float r_aa_start; hardness = CLAMP (hardness, 0.0f, 1.0f); segment1_slope = -(1.0f / hardness - 1.0f); segment2_slope = -hardness / (1.0f - hardness); aspect_ratio = MAX (1.0f, aspect_ratio); r_aa_start = radius - 1.0f; r_aa_start = MAX (r_aa_start, 0); r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio; normal_mode = opaque * (1.0f - colorize); colorize = opaque * colorize; /* FIXME: This should use the real matrix values to trim aspect_ratio dabs */ dabRect = calculate_dab_roi (x, y, radius); gegl_rectangle_intersect (&dabRect, &dabRect, gegl_buffer_get_extent (surface->buffer)); if (dabRect.width <= 0 || dabRect.height <= 0) return 0; gegl_rectangle_bounding_box (&surface->dirty, &surface->dirty, &dabRect); iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0, babl_format ("R'G'B'A float"), GEGL_BUFFER_READWRITE, GEGL_ABYSS_NONE); if (surface->paint_mask) { GeglRectangle mask_roi = dabRect; mask_roi.x -= surface->paint_mask_x; mask_roi.y -= surface->paint_mask_y; gegl_buffer_iterator_add (iter, surface->paint_mask, &mask_roi, 0, babl_format ("Y float"), GEGL_ACCESS_READ, GEGL_ABYSS_NONE); } while (gegl_buffer_iterator_next (iter)) { float *pixel = (float *)iter->data[0]; float *mask; int iy, ix; if (surface->paint_mask) mask = iter->data[1]; else mask = NULL; for (iy = iter->roi[0].y; iy < iter->roi[0].y + iter->roi[0].height; iy++) { for (ix = iter->roi[0].x; ix < iter->roi[0].x + iter->roi[0].width; ix++) { float rr, base_alpha, alpha, dst_alpha, r, g, b, a; if (radius < 3.0f) rr = calculate_rr_antialiased (ix, iy, x, y, aspect_ratio, sn, cs, one_over_radius2, r_aa_start); else rr = calculate_rr (ix, iy, x, y, aspect_ratio, sn, cs, one_over_radius2); base_alpha = calculate_alpha_for_rr (rr, hardness, segment1_slope, segment2_slope); alpha = base_alpha * normal_mode; if (mask) alpha *= *mask; dst_alpha = pixel[ALPHA]; /* a = alpha * color_a + dst_alpha * (1.0f - alpha); * which converts to: */ a = alpha * (color_a - dst_alpha) + dst_alpha; r = pixel[RED]; g = pixel[GREEN]; b = pixel[BLUE]; if (a > 0.0f) { /* By definition the ratio between each color[] and pixel[] component in a non-pre-multipled blend always sums to 1.0f. * Originaly this would have been "(color[n] * alpha * color_a + pixel[n] * dst_alpha * (1.0f - alpha)) / a", * instead we only calculate the cheaper term. */ float src_term = (alpha * color_a) / a; float dst_term = 1.0f - src_term; r = color_r * src_term + r * dst_term; g = color_g * src_term + g * dst_term; b = color_b * src_term + b * dst_term; } if (colorize > 0.0f && base_alpha > 0.0f) { alpha = base_alpha * colorize; a = alpha + dst_alpha - alpha * dst_alpha; if (a > 0.0f) { GimpHSL pixel_hsl, out_hsl; GimpRGB pixel_rgb = {color_r, color_g, color_b}; GimpRGB out_rgb = {r, g, b}; float src_term = alpha / a; float dst_term = 1.0f - src_term; gimp_rgb_to_hsl (&pixel_rgb, &pixel_hsl); gimp_rgb_to_hsl (&out_rgb, &out_hsl); out_hsl.h = pixel_hsl.h; out_hsl.s = pixel_hsl.s; gimp_hsl_to_rgb (&out_hsl, &out_rgb); r = (float)out_rgb.r * src_term + r * dst_term; g = (float)out_rgb.g * src_term + g * dst_term; b = (float)out_rgb.b * src_term + b * dst_term; } } if (component_mask != GIMP_COMPONENT_MASK_ALL) { if (component_mask & GIMP_COMPONENT_MASK_RED) pixel[RED] = r; if (component_mask & GIMP_COMPONENT_MASK_GREEN) pixel[GREEN] = g; if (component_mask & GIMP_COMPONENT_MASK_BLUE) pixel[BLUE] = b; if (component_mask & GIMP_COMPONENT_MASK_ALPHA) pixel[ALPHA] = a; } else { pixel[RED] = r; pixel[GREEN] = g; pixel[BLUE] = b; pixel[ALPHA] = a; } pixel += 4; if (mask) mask += 1; } } } return 1; }
// Must be threadsafe void render_dab_mask (uint16_t * mask, float x, float y, float radius, float hardness, float aspect_ratio, float angle ) { hardness = CLAMP(hardness, 0.0, 1.0); if (aspect_ratio<1.0) aspect_ratio=1.0; assert(hardness != 0.0); // assured by caller // For a graphical explanation, see: // http://wiki.mypaint.info/Development/Documentation/Brushlib // // The hardness calculation is explained below: // // Dab opacity gradually fades out from the center (rr=0) to // fringe (rr=1) of the dab. How exactly depends on the hardness. // We use two linear segments, for which we pre-calculate slope // and offset here. // // opa // ^ // * . // | * // | . // +-----------*> rr = (distance_from_center/radius)^2 // 0 1 // float segment1_offset = 1.0f; float segment1_slope = -(1.0f/hardness - 1.0f); float segment2_offset = hardness/(1.0f-hardness); float segment2_slope = -hardness/(1.0f-hardness); // for hardness == 1.0, segment2 will never be used float angle_rad=angle/360*2*M_PI; float cs=cos(angle_rad); float sn=sin(angle_rad); const float r_fringe = radius + 1.0f; // +1.0 should not be required, only to be sure int x0 = floor (x - r_fringe); int y0 = floor (y - r_fringe); int x1 = floor (x + r_fringe); int y1 = floor (y + r_fringe); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > MYPAINT_TILE_SIZE-1) x1 = MYPAINT_TILE_SIZE-1; if (y1 > MYPAINT_TILE_SIZE-1) y1 = MYPAINT_TILE_SIZE-1; const float one_over_radius2 = 1.0f/(radius*radius); // Pre-calculate rr and put it in the mask. // This an optimization that makes use of auto-vectorization // OPTIMIZE: if using floats for the brush engine, store these directly in the mask float rr_mask[MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE+2*MYPAINT_TILE_SIZE]; if (radius < 3.0f) { const float aa_border = 1.0f; float r_aa_start = ((radius>aa_border) ? (radius-aa_border) : 0); r_aa_start *= r_aa_start / aspect_ratio; for (int yp = y0; yp <= y1; yp++) { for (int xp = x0; xp <= x1; xp++) { const float rr = calculate_rr_antialiased(xp, yp, x, y, aspect_ratio, sn, cs, one_over_radius2, r_aa_start); rr_mask[(yp*MYPAINT_TILE_SIZE)+xp] = rr; } } } else { for (int yp = y0; yp <= y1; yp++) { for (int xp = x0; xp <= x1; xp++) { const float rr = calculate_rr(xp, yp, x, y, aspect_ratio, sn, cs, one_over_radius2); rr_mask[(yp*MYPAINT_TILE_SIZE)+xp] = rr; } } } // we do run length encoding: if opacity is zero, the next // value in the mask is the number of pixels that can be skipped. uint16_t * mask_p = mask; int skip=0; skip += y0*MYPAINT_TILE_SIZE; for (int yp = y0; yp <= y1; yp++) { skip += x0; int xp; for (xp = x0; xp <= x1; xp++) { const float rr = rr_mask[(yp*MYPAINT_TILE_SIZE)+xp]; const float opa = calculate_opa(rr, hardness, segment1_offset, segment1_slope, segment2_offset, segment2_slope); const uint16_t opa_ = opa * (1<<15); if (!opa_) { skip++; } else { if (skip) { *mask_p++ = 0; *mask_p++ = skip*4; skip = 0; } *mask_p++ = opa_; } } skip += MYPAINT_TILE_SIZE-xp; } *mask_p++ = 0; *mask_p++ = 0; }