/* create a mask with the falloff strength and optionally brush alpha */ static unsigned short *brush_painter_mask_new(BrushPainter *painter, int size) { Scene *scene = painter->scene; Brush *brush = painter->brush; bool use_masking = painter->cache.use_masking; float alpha = (use_masking) ? 1.0f : BKE_brush_alpha_get(scene, brush); int radius = BKE_brush_size_get(scene, brush); int xoff = -size * 0.5f + 0.5f; int yoff = -size * 0.5f + 0.5f; unsigned short *mask, *m; int x, y; mask = MEM_callocN(sizeof(unsigned short) * size * size, "brush_painter_mask"); m = mask; for (y = 0; y < size; y++) { for (x = 0; x < size; x++, m++) { float xy[2] = {x + xoff, y + yoff}; float len = len_v2(xy); float strength = alpha; strength *= BKE_brush_curve_strength_clamp(brush, len, radius); *m = (unsigned short)(65535.0f * strength); } } return mask; }
BrushPainter *BKE_brush_painter_new(Scene *scene, Brush *brush) { BrushPainter *painter = MEM_callocN(sizeof(BrushPainter), "BrushPainter"); painter->brush = brush; painter->scene = scene; painter->firsttouch = 1; painter->cache.lastsize = -1; /* force ibuf create in refresh */ painter->startsize = BKE_brush_size_get(scene, brush); painter->startalpha = BKE_brush_alpha_get(scene, brush); painter->startjitter = brush->jitter; painter->startspacing = brush->spacing; return painter; }
static void brush_painter_refresh_cache(BrushPainter *painter, const float pos[2], int use_color_correction) { const Scene *scene = painter->scene; Brush *brush = painter->brush; BrushPainterCache *cache = &painter->cache; MTex *mtex = &brush->mtex; int size; short flt; const int diameter = 2 * BKE_brush_size_get(scene, brush); const float alpha = BKE_brush_alpha_get(scene, brush); if (diameter != cache->lastsize || alpha != cache->lastalpha || brush->jitter != cache->lastjitter) { if (cache->ibuf) { IMB_freeImBuf(cache->ibuf); cache->ibuf = NULL; } if (cache->maskibuf) { IMB_freeImBuf(cache->maskibuf); cache->maskibuf = NULL; } flt = cache->flt; size = (cache->size) ? cache->size : diameter; if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_TILED) { BKE_brush_imbuf_new(scene, brush, flt, 3, size, &cache->maskibuf, use_color_correction); brush_painter_fixed_tex_partial_update(painter, pos); } else BKE_brush_imbuf_new(scene, brush, flt, 2, size, &cache->ibuf, use_color_correction); cache->lastsize = diameter; cache->lastalpha = alpha; cache->lastjitter = brush->jitter; } else if ((brush->mtex.brush_map_mode == MTEX_MAP_MODE_TILED) && mtex && mtex->tex) { int dx = (int)painter->lastpaintpos[0] - (int)pos[0]; int dy = (int)painter->lastpaintpos[1] - (int)pos[1]; if ((dx != 0) || (dy != 0)) brush_painter_fixed_tex_partial_update(painter, pos); } }
static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *maskb, const float lastpos[2], const float pos[2]) { ImagePaintState *s = ((ImagePaintState *)state); ImBuf *clonebuf = NULL, *frombuf, *tmpbuf = NULL; ImagePaintRegion region[4]; short torus = s->brush->flag & BRUSH_TORUS; short blend = s->blend; float *offset = s->brush->clone.offset; float liftpos[2]; float brush_alpha = BKE_brush_alpha_get(s->scene, s->brush); unsigned short mask_max = (unsigned short)(brush_alpha * 65535.0f); int bpos[2], blastpos[2], bliftpos[2]; int a, tot; paint_2d_convert_brushco(ibufb, pos, bpos); /* lift from canvas */ if (s->tool == PAINT_TOOL_SOFTEN) { paint_2d_lift_soften(s->canvas, ibufb, bpos, torus); } else if (s->tool == PAINT_TOOL_SMEAR) { if (lastpos[0] == pos[0] && lastpos[1] == pos[1]) return 0; paint_2d_convert_brushco(ibufb, lastpos, blastpos); paint_2d_lift_smear(s->canvas, ibufb, blastpos); } else if (s->tool == PAINT_TOOL_CLONE && s->clonecanvas) { liftpos[0] = pos[0] - offset[0] * s->canvas->x; liftpos[1] = pos[1] - offset[1] * s->canvas->y; paint_2d_convert_brushco(ibufb, liftpos, bliftpos); clonebuf = paint_2d_lift_clone(s->clonecanvas, ibufb, bliftpos); } frombuf = (clonebuf) ? clonebuf : ibufb; if (torus) { paint_2d_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y); tot = paint_2d_torus_split_region(region, s->canvas, frombuf); } else { paint_2d_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y); tot = 1; } if (s->do_masking) tmpbuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, 0); /* blend into canvas */ for (a = 0; a < tot; a++) { ED_imapaint_dirty_region(s->image, s->canvas, region[a].destx, region[a].desty, region[a].width, region[a].height); if (s->do_masking) { /* masking, find original pixels tiles from undo buffer to composite over */ int tilex, tiley, tilew, tileh, tx, ty; imapaint_region_tiles(s->canvas, region[a].destx, region[a].desty, region[a].width, region[a].height, &tilex, &tiley, &tilew, &tileh); for (ty = tiley; ty <= tileh; ty++) { for (tx = tilex; tx <= tilew; tx++) { /* retrieve original pixels + mask from undo buffer */ unsigned short *mask; int origx = region[a].destx - tx * IMAPAINT_TILE_SIZE; int origy = region[a].desty - ty * IMAPAINT_TILE_SIZE; if (s->canvas->rect_float) tmpbuf->rect_float = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask); else tmpbuf->rect = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask); IMB_rectblend(s->canvas, tmpbuf, frombuf, mask, maskb, mask_max, region[a].destx, region[a].desty, origx, origy, region[a].srcx, region[a].srcy, region[a].width, region[a].height, blend); } } } else { /* no masking, composite brush directly onto canvas */ IMB_rectblend(s->canvas, s->canvas, frombuf, NULL, NULL, 0, region[a].destx, region[a].desty, region[a].destx, region[a].desty, region[a].srcx, region[a].srcy, region[a].width, region[a].height, blend); } } if (clonebuf) IMB_freeImBuf(clonebuf); if (tmpbuf) IMB_freeImBuf(tmpbuf); return 1; }
static void brush_painter_2d_refresh_cache(ImagePaintState *s, BrushPainter *painter, const float pos[2], const float mouse[2]) { const Scene *scene = painter->scene; UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; Brush *brush = painter->brush; BrushPainterCache *cache = &painter->cache; const int diameter = 2 * BKE_brush_size_get(scene, brush); const int size = (cache->size) ? cache->size : diameter; const float alpha = BKE_brush_alpha_get(scene, brush); const bool use_masking = painter->cache.use_masking; bool do_random = false; bool do_partial_update = false; bool do_view = false; float tex_rotation = -brush->mtex.rot; float mask_rotation = -brush->mask_mtex.rot; /* determine how can update based on textures used */ if (painter->cache.is_texbrush) { if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_VIEW) { do_view = true; tex_rotation += ups->brush_rotation; } else if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_RANDOM) do_random = true; else do_partial_update = true; brush_painter_2d_tex_mapping(s, size, painter->startpaintpos, pos, mouse, brush->mtex.brush_map_mode, &painter->tex_mapping); } if (painter->cache.is_maskbrush) { if (brush->mask_mtex.brush_map_mode == MTEX_MAP_MODE_VIEW) { do_view = true; mask_rotation += ups->brush_rotation; } else if (brush->mask_mtex.brush_map_mode == MTEX_MAP_MODE_RANDOM) do_random = true; else do_partial_update = true; brush_painter_2d_tex_mapping(s, size, painter->startpaintpos, pos, mouse, brush->mask_mtex.brush_map_mode, &painter->mask_mapping); } if (do_view || do_random) do_partial_update = false; painter->pool = BKE_image_pool_new(); /* detect if we need to recreate image brush buffer */ if (diameter != cache->lastsize || alpha != cache->lastalpha || brush->jitter != cache->lastjitter || tex_rotation != cache->last_tex_rotation || mask_rotation != cache->last_mask_rotation || do_random) { if (cache->ibuf) { IMB_freeImBuf(cache->ibuf); cache->ibuf = NULL; } if (cache->mask) { MEM_freeN(cache->mask); cache->mask = NULL; } if (do_partial_update) { /* do partial update of texture + recreate mask */ cache->mask = brush_painter_mask_new(painter, size); brush_painter_imbuf_partial_update(painter, pos); } else { /* create brush and mask from scratch */ if (use_masking) cache->mask = brush_painter_mask_new(painter, size); cache->ibuf = brush_painter_imbuf_new(painter, size); } cache->lastsize = diameter; cache->lastalpha = alpha; cache->lastjitter = brush->jitter; cache->last_tex_rotation = tex_rotation; cache->last_mask_rotation = mask_rotation; } else if (do_partial_update) { /* do only partial update of texture */ int dx = (int)painter->lastpaintpos[0] - (int)pos[0]; int dy = (int)painter->lastpaintpos[1] - (int)pos[1]; if ((dx != 0) || (dy != 0)) { brush_painter_imbuf_partial_update(painter, pos); } } BKE_image_pool_free(painter->pool); painter->pool = NULL; }
/* create imbuf with brush color */ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size) { Scene *scene = painter->scene; Brush *brush = painter->brush; rctf tex_mapping = painter->tex_mapping; rctf mask_mapping = painter->mask_mapping; struct ImagePool *pool = painter->pool; bool use_masking = painter->cache.use_masking; bool use_color_correction = painter->cache.use_color_correction; bool use_float = painter->cache.use_float; bool is_texbrush = painter->cache.is_texbrush; bool is_maskbrush = painter->cache.is_maskbrush; float alpha = (use_masking) ? 1.0f : BKE_brush_alpha_get(scene, brush); int radius = BKE_brush_size_get(scene, brush); int xoff = -size * 0.5f + 0.5f; int yoff = -size * 0.5f + 0.5f; int x, y, thread = 0; float brush_rgb[3]; /* allocate image buffer */ ImBuf *ibuf = IMB_allocImBuf(size, size, 32, (use_float) ? IB_rectfloat : IB_rect); /* get brush color */ if (brush->imagepaint_tool == PAINT_TOOL_DRAW) { copy_v3_v3(brush_rgb, brush->rgb); if (use_color_correction) srgb_to_linearrgb_v3_v3(brush_rgb, brush_rgb); } else { brush_rgb[0] = 1.0f; brush_rgb[1] = 1.0f; brush_rgb[2] = 1.0f; } /* fill image buffer */ for (y = 0; y < size; y++) { for (x = 0; x < size; x++) { /* sample texture and multiply with brush color */ float texco[3], rgba[4]; if (is_texbrush) { brush_imbuf_tex_co(&tex_mapping, x, y, texco); BKE_brush_sample_tex_3D(scene, brush, texco, rgba, thread, pool); /* TODO(sergey): Support texture paint color space. */ if (!use_float) { linearrgb_to_srgb_v3_v3(rgba, rgba); } mul_v3_v3(rgba, brush_rgb); } else { copy_v3_v3(rgba, brush_rgb); rgba[3] = 1.0f; } if (is_maskbrush) { brush_imbuf_tex_co(&mask_mapping, x, y, texco); rgba[3] *= BKE_brush_sample_masktex(scene, brush, texco, thread, pool); } /* when not using masking, multiply in falloff and strength */ if (!use_masking) { float xy[2] = {x + xoff, y + yoff}; float len = len_v2(xy); rgba[3] *= alpha * BKE_brush_curve_strength_clamp(brush, len, radius); } if (use_float) { /* write to float pixel */ float *dstf = ibuf->rect_float + (y * size + x) * 4; mul_v3_v3fl(dstf, rgba, rgba[3]); /* premultiply */ dstf[3] = rgba[3]; } else { /* write to byte pixel */ unsigned char *dst = (unsigned char *)ibuf->rect + (y * size + x) * 4; rgb_float_to_uchar(dst, rgba); dst[3] = FTOCHAR(rgba[3]); } } } return ibuf; }
static void paint_2d_lift_soften(ImagePaintState *s, ImBuf *ibuf, ImBuf *ibufb, int *pos, const short tile) { bool sharpen = (s->painter->cache.invert ^ ((s->brush->flag & BRUSH_DIR_IN) != 0)); float threshold = s->brush->sharp_threshold; int x, y, xi, yi, xo, yo, xk, yk; float count; int out_off[2], in_off[2], dim[2]; int diff_pos[2]; float outrgb[4]; float rgba[4]; BlurKernel *kernel = s->blurkernel; dim[0] = ibufb->x; dim[1] = ibufb->y; in_off[0] = pos[0]; in_off[1] = pos[1]; out_off[0] = out_off[1] = 0; if (!tile) { IMB_rectclip(ibuf, ibufb, &in_off[0], &in_off[1], &out_off[0], &out_off[1], &dim[0], &dim[1]); if ((dim[0] == 0) || (dim[1] == 0)) return; } /* find offset inside mask buffers to sample them */ sub_v2_v2v2_int(diff_pos, out_off, in_off); for (y = 0; y < dim[1]; y++) { for (x = 0; x < dim[0]; x++) { /* get input pixel */ xi = in_off[0] + x; yi = in_off[1] + y; count = 0.0; if (tile) { paint_2d_ibuf_tile_convert(ibuf, &xi, &yi, tile); if (xi < ibuf->x && xi >= 0 && yi < ibuf->y && yi >= 0) paint_2d_ibuf_rgb_get(ibuf, xi, yi, rgba); else zero_v4(rgba); } else { /* coordinates have been clipped properly here, it should be safe to do this */ paint_2d_ibuf_rgb_get(ibuf, xi, yi, rgba); } zero_v4(outrgb); for (yk = 0; yk < kernel->side; yk++) { for (xk = 0; xk < kernel->side; xk++) { count += paint_2d_ibuf_add_if(ibuf, xi + xk - kernel->pixel_len, yi + yk - kernel->pixel_len, outrgb, tile, kernel->wdata[xk + yk * kernel->side]); } } if (count > 0.0f) { mul_v4_fl(outrgb, 1.0f / (float)count); if (sharpen) { /* subtract blurred image from normal image gives high pass filter */ sub_v3_v3v3(outrgb, rgba, outrgb); /* now rgba_ub contains the edge result, but this should be converted to luminance to avoid * colored speckles appearing in final image, and also to check for threshold */ outrgb[0] = outrgb[1] = outrgb[2] = IMB_colormanagement_get_luminance(outrgb); if (fabsf(outrgb[0]) > threshold) { float mask = BKE_brush_alpha_get(s->scene, s->brush); float alpha = rgba[3]; rgba[3] = outrgb[3] = mask; /* add to enhance edges */ blend_color_add_float(outrgb, rgba, outrgb); outrgb[3] = alpha; } else copy_v4_v4(outrgb, rgba); } } else copy_v4_v4(outrgb, rgba); /* write into brush buffer */ xo = out_off[0] + x; yo = out_off[1] + y; paint_2d_ibuf_rgb_set(ibufb, xo, yo, 0, outrgb); } } }
/* TODO, use define for 'texfall' arg */ void BKE_brush_imbuf_new(const Scene *scene, Brush *brush, short flt, short texfall, int bufsize, ImBuf **outbuf, int use_color_correction) { ImBuf *ibuf; float xy[2], rgba[4], *dstf; int x, y, rowbytes, xoff, yoff, imbflag; const int radius = BKE_brush_size_get(scene, brush); unsigned char *dst, crgb[3]; const float alpha = BKE_brush_alpha_get(scene, brush); float brush_rgb[3]; imbflag = (flt) ? IB_rectfloat : IB_rect; xoff = -bufsize / 2.0f + 0.5f; yoff = -bufsize / 2.0f + 0.5f; rowbytes = bufsize * 4; if (*outbuf) ibuf = *outbuf; else ibuf = IMB_allocImBuf(bufsize, bufsize, 32, imbflag); if (flt) { copy_v3_v3(brush_rgb, brush->rgb); if (use_color_correction) { srgb_to_linearrgb_v3_v3(brush_rgb, brush_rgb); } for (y = 0; y < ibuf->y; y++) { dstf = ibuf->rect_float + y * rowbytes; for (x = 0; x < ibuf->x; x++, dstf += 4) { xy[0] = x + xoff; xy[1] = y + yoff; if (texfall == 0) { copy_v3_v3(dstf, brush_rgb); dstf[3] = alpha * BKE_brush_curve_strength_clamp(brush, len_v2(xy), radius); } else if (texfall == 1) { BKE_brush_sample_tex(scene, brush, xy, dstf, 0); } else { BKE_brush_sample_tex(scene, brush, xy, rgba, 0); mul_v3_v3v3(dstf, rgba, brush_rgb); dstf[3] = rgba[3] * alpha * BKE_brush_curve_strength_clamp(brush, len_v2(xy), radius); } } } } else { float alpha_f; /* final float alpha to convert to char */ rgb_float_to_uchar(crgb, brush->rgb); for (y = 0; y < ibuf->y; y++) { dst = (unsigned char *)ibuf->rect + y * rowbytes; for (x = 0; x < ibuf->x; x++, dst += 4) { xy[0] = x + xoff; xy[1] = y + yoff; if (texfall == 0) { alpha_f = alpha * BKE_brush_curve_strength(brush, len_v2(xy), radius); dst[0] = crgb[0]; dst[1] = crgb[1]; dst[2] = crgb[2]; dst[3] = FTOCHAR(alpha_f); } else if (texfall == 1) { BKE_brush_sample_tex(scene, brush, xy, rgba, 0); rgba_float_to_uchar(dst, rgba); } else if (texfall == 2) { BKE_brush_sample_tex(scene, brush, xy, rgba, 0); mul_v3_v3(rgba, brush->rgb); alpha_f = rgba[3] * alpha * BKE_brush_curve_strength_clamp(brush, len_v2(xy), radius); rgb_float_to_uchar(dst, rgba); dst[3] = FTOCHAR(alpha_f); } else { BKE_brush_sample_tex(scene, brush, xy, rgba, 0); alpha_f = rgba[3] * alpha * BKE_brush_curve_strength_clamp(brush, len_v2(xy), radius); dst[0] = crgb[0]; dst[1] = crgb[1]; dst[2] = crgb[2]; dst[3] = FTOCHAR(alpha_f); } } } } *outbuf = ibuf; }