diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint/paint_image_2d.c')
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image_2d.c | 872 |
1 files changed, 677 insertions, 195 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.c b/source/blender/editors/sculpt_paint/paint_image_2d.c index 85889fe4cb9..d394d6d3f63 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.c +++ b/source/blender/editors/sculpt_paint/paint_image_2d.c @@ -40,11 +40,18 @@ #include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_math_color_blend.h" +#include "BLI_stack.h" +#include "BLI_bitmap.h" + #include "BKE_context.h" +#include "BKE_depsgraph.h" #include "BKE_brush.h" #include "BKE_image.h" #include "BKE_paint.h" #include "BKE_report.h" +#include "BKE_texture.h" #include "ED_paint.h" #include "ED_screen.h" @@ -69,24 +76,25 @@ /* Defines and Structs */ typedef struct BrushPainterCache { - int size; /* size override, if 0 uses 2*BKE_brush_size_get(brush) */ - bool use_float; /* need float imbuf? */ bool use_color_correction; /* use color correction for float */ - bool use_masking; /* use masking? */ + bool invert; bool is_texbrush; bool is_maskbrush; - int lastsize; - float lastalpha; - float lastjitter; + int lastdiameter; float last_tex_rotation; float last_mask_rotation; + float last_pressure; ImBuf *ibuf; ImBuf *texibuf; - unsigned short *mask; + unsigned short *curve_mask; + unsigned short *tex_mask; + unsigned short *tex_mask_old; + unsigned int tex_mask_old_w; + unsigned int tex_mask_old_h; } BrushPainterCache; typedef struct BrushPainter { @@ -136,43 +144,42 @@ typedef struct ImagePaintState { int do_facesel; bool need_redraw; + + BlurKernel *blurkernel; } ImagePaintState; -static BrushPainter *brush_painter_2d_new(Scene *scene, Brush *brush) +static BrushPainter *brush_painter_2d_new(Scene *scene, Brush *brush, bool invert) { 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->cache.lastdiameter = -1; /* force ibuf create in refresh */ + painter->cache.invert = invert; return painter; } -static void brush_painter_2d_require_imbuf(BrushPainter *painter, bool use_float, bool use_color_correction, bool use_masking) +static void brush_painter_2d_require_imbuf(BrushPainter *painter, bool use_float, bool use_color_correction) { Brush *brush = painter->brush; if ((painter->cache.use_float != use_float)) { if (painter->cache.ibuf) IMB_freeImBuf(painter->cache.ibuf); - if (painter->cache.mask) MEM_freeN(painter->cache.mask); + if (painter->cache.curve_mask) MEM_freeN(painter->cache.curve_mask); + if (painter->cache.tex_mask) MEM_freeN(painter->cache.tex_mask); + if (painter->cache.tex_mask_old) MEM_freeN(painter->cache.tex_mask_old); painter->cache.ibuf = NULL; - painter->cache.mask = NULL; - painter->cache.lastsize = -1; /* force ibuf create in refresh */ - } - - if (painter->cache.use_float != use_float) { - if (painter->cache.texibuf) IMB_freeImBuf(painter->cache.texibuf); - painter->cache.texibuf = NULL; - painter->cache.lastsize = -1; /* force ibuf create in refresh */ + painter->cache.curve_mask = NULL; + painter->cache.tex_mask = NULL; + painter->cache.lastdiameter = -1; /* force ibuf create in refresh */ } painter->cache.use_float = use_float; painter->cache.use_color_correction = use_float && use_color_correction; - painter->cache.use_masking = use_masking; painter->cache.is_texbrush = (brush->mtex.tex && brush->imagepaint_tool == PAINT_TOOL_DRAW) ? true : false; painter->cache.is_maskbrush = (brush->mask_mtex.tex) ? true : false; } @@ -181,7 +188,9 @@ static void brush_painter_2d_free(BrushPainter *painter) { if (painter->cache.ibuf) IMB_freeImBuf(painter->cache.ibuf); if (painter->cache.texibuf) IMB_freeImBuf(painter->cache.texibuf); - if (painter->cache.mask) MEM_freeN(painter->cache.mask); + if (painter->cache.curve_mask) MEM_freeN(painter->cache.curve_mask); + if (painter->cache.tex_mask) MEM_freeN(painter->cache.tex_mask); + if (painter->cache.tex_mask_old) MEM_freeN(painter->cache.tex_mask_old); MEM_freeN(painter); } @@ -192,41 +201,177 @@ static void brush_imbuf_tex_co(rctf *mapping, int x, int y, float texco[3]) texco[2] = 0.0f; } -/* create a mask with the falloff strength and optionally brush alpha */ -static unsigned short *brush_painter_mask_new(BrushPainter *painter, int size) +/* create a mask with the mask texture */ +static unsigned short *brush_painter_mask_ibuf_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; + rctf mask_mapping = painter->mask_mapping; + struct ImagePool *pool = painter->pool; + float texco[3]; unsigned short *mask, *m; - int x, y; + int x, y, thread = 0; - mask = MEM_callocN(sizeof(unsigned short) * size * size, "brush_painter_mask"); + mask = MEM_mallocN(sizeof(unsigned short) * size * size, "brush_painter_mask"); m = mask; for (y = 0; y < size; y++) { for (x = 0; x < size; x++, m++) { + float res; + brush_imbuf_tex_co(&mask_mapping, x, y, texco); + res = BKE_brush_sample_masktex(scene, brush, texco, thread, pool); + *m = (unsigned short)(65535.0f * res); + } + } + + return mask; +} + +/* update rectangular section of the brush image */ +static void brush_painter_mask_imbuf_update( + BrushPainter *painter, unsigned short *tex_mask_old, + int origx, int origy, int w, int h, int xt, int yt, int diameter) +{ + Scene *scene = painter->scene; + Brush *brush = painter->brush; + rctf tex_mapping = painter->mask_mapping; + struct ImagePool *pool = painter->pool; + unsigned short res; + + bool use_texture_old = (tex_mask_old != NULL); + + int x, y, thread = 0; + + unsigned short *tex_mask = painter->cache.tex_mask; + unsigned short *tex_mask_cur = painter->cache.tex_mask_old; + + /* fill pixels */ + for (y = origy; y < h; y++) { + for (x = origx; x < w; x++) { + /* sample texture */ + float texco[3]; + + /* handle byte pixel */ + unsigned short *b = tex_mask + (y * diameter + x); + unsigned short *t = tex_mask_cur + (y * diameter + x); + + if (!use_texture_old) { + brush_imbuf_tex_co(&tex_mapping, x, y, texco); + res = (unsigned short)(65535.0f * BKE_brush_sample_masktex(scene, brush, texco, thread, pool)); + } + + /* read from old texture buffer */ + if (use_texture_old) { + res = *(tex_mask_old + ((y - origy + yt) * painter->cache.tex_mask_old_w + (x - origx + xt))); + } + + /* write to new texture mask */ + *t = res; + /* write to mask image buffer */ + *b = res; + } + } +} + + +/** + * Update the brush mask image by trying to reuse the cached texture result. + * This can be considerably faster for brushes that change size due to pressure or + * textures that stick to the surface where only part of the pixels are new + */ +static void brush_painter_mask_imbuf_partial_update(BrushPainter *painter, const float pos[2], int diameter) +{ + BrushPainterCache *cache = &painter->cache; + unsigned short *tex_mask_old; + int destx, desty, srcx, srcy, w, h, x1, y1, x2, y2; + + /* create brush image buffer if it didn't exist yet */ + if (!cache->tex_mask) + cache->tex_mask = MEM_mallocN(sizeof(unsigned short) * diameter * diameter, "brush_painter_mask"); + + /* create new texture image buffer with coordinates relative to old */ + tex_mask_old = cache->tex_mask_old; + cache->tex_mask_old = MEM_mallocN(sizeof(unsigned short) * diameter * diameter, "brush_painter_mask"); + + if (tex_mask_old) { + ImBuf maskibuf; + ImBuf maskibuf_old; + maskibuf.x = maskibuf.y = diameter; + maskibuf_old.x = cache->tex_mask_old_w; + maskibuf_old.y = cache->tex_mask_old_h; + + srcx = srcy = 0; + w = cache->tex_mask_old_w; + h = cache->tex_mask_old_h; + destx = (int)painter->lastpaintpos[0] - (int)pos[0] + (diameter / 2 - w / 2); + desty = (int)painter->lastpaintpos[1] - (int)pos[1] + (diameter / 2 - h / 2); + + /* hack, use temporary rects so that clipping works */ + IMB_rectclip(&maskibuf, &maskibuf_old, &destx, &desty, &srcx, &srcy, &w, &h); + } + else { + srcx = srcy = 0; + destx = desty = 0; + w = h = 0; + } + + x1 = min_ii(destx, diameter); + y1 = min_ii(desty, diameter); + x2 = min_ii(destx + w, diameter); + y2 = min_ii(desty + h, diameter); + + /* blend existing texture in new position */ + if ((x1 < x2) && (y1 < y2)) + brush_painter_mask_imbuf_update(painter, tex_mask_old, x1, y1, x2, y2, srcx, srcy, diameter); + + if (tex_mask_old) + MEM_freeN(tex_mask_old); + + /* sample texture in new areas */ + if ((0 < x1) && (0 < diameter)) + brush_painter_mask_imbuf_update(painter, NULL, 0, 0, x1, diameter, 0, 0, diameter); + if ((x2 < diameter) && (0 < diameter)) + brush_painter_mask_imbuf_update(painter, NULL, x2, 0, diameter, diameter, 0, 0, diameter); + if ((x1 < x2) && (0 < y1)) + brush_painter_mask_imbuf_update(painter, NULL, x1, 0, x2, y1, 0, 0, diameter); + if ((x1 < x2) && (y2 < diameter)) + brush_painter_mask_imbuf_update(painter, NULL, x1, y2, x2, diameter, 0, 0, diameter); + + /* through with sampling, now update sizes */ + cache->tex_mask_old_w = diameter; + cache->tex_mask_old_h = diameter; +} + +/* create a mask with the falloff strength */ +static unsigned short *brush_painter_curve_mask_new(BrushPainter *painter, int diameter, float radius) +{ + Brush *brush = painter->brush; + + int xoff = -diameter * 0.5f + 0.5f; + int yoff = -diameter * 0.5f + 0.5f; + + unsigned short *mask, *m; + int x, y; + + mask = MEM_mallocN(sizeof(unsigned short) * diameter * diameter, "brush_painter_mask"); + m = mask; + + for (y = 0; y < diameter; y++) { + for (x = 0; x < diameter; 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); + *m = (unsigned short)(65535.0f * BKE_brush_curve_strength_clamp(brush, len, radius)); } } return mask; } + /* create imbuf with brush color */ -static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size) +static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size, float pressure, float distance) { Scene *scene = painter->scene; Brush *brush = painter->brush; @@ -235,19 +380,11 @@ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size) struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); 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]; @@ -257,11 +394,7 @@ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size) /* get brush color */ if (brush->imagepaint_tool == PAINT_TOOL_DRAW) { - copy_v3_v3(brush_rgb, brush->rgb); - - if (use_color_correction) { - IMB_colormanagement_display_to_scene_linear_v3(brush_rgb, display); - } + paint_brush_color_get(scene, brush, use_color_correction, painter->cache.invert, distance, pressure, brush_rgb, display); } else { brush_rgb[0] = 1.0f; @@ -289,19 +422,6 @@ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size) 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; @@ -332,14 +452,11 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); 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; bool use_texture_old = (oldtexibuf != NULL); int x, y, thread = 0; @@ -347,15 +464,10 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, ImBuf *ibuf = painter->cache.ibuf; ImBuf *texibuf = painter->cache.texibuf; - unsigned short *mask = painter->cache.mask; /* get brush color */ if (brush->imagepaint_tool == PAINT_TOOL_DRAW) { - copy_v3_v3(brush_rgb, brush->rgb); - - if (use_color_correction) { - IMB_colormanagement_display_to_scene_linear_v3(brush_rgb, display); - } + paint_brush_color_get(scene, brush, use_color_correction, painter->cache.invert, 0.0, 1.0, brush_rgb, display); } else { brush_rgb[0] = 1.0f; @@ -363,7 +475,7 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, brush_rgb[2] = 1.0f; } - /* fill pixes */ + /* fill pixels */ for (y = origy; y < h; y++) { for (x = origx; x < w; x++) { /* sample texture and multiply with brush color */ @@ -383,11 +495,6 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, 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); - } } if (use_float) { @@ -404,12 +511,6 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, /* write to new texture buffer */ copy_v4_v4(tf, rgba); - /* if not using masking, multiply in the mask now */ - if (!use_masking) { - unsigned short *m = mask + (y * ibuf->x + x); - rgba[3] *= *m * (1.0f / 65535.0f); - } - /* output premultiplied float image, mf was already premultiplied */ mul_v3_v3fl(bf, rgba, rgba[3]); bf[3] = rgba[3]; @@ -438,12 +539,6 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, t[2] = crgba[2]; t[3] = crgba[3]; - /* if not using masking, multiply in the mask now */ - if (!use_masking) { - unsigned short *m = mask + (y * ibuf->x + x); - crgba[3] = (crgba[3] * (*m)) / 65535; - } - /* write to brush image buffer */ b[0] = crgba[0]; b[1] = crgba[1]; @@ -457,14 +552,11 @@ static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf, /* update the brush image by trying to reuse the cached texture result. this * can be considerably faster for brushes that change size due to pressure or * textures that stick to the surface where only part of the pixels are new */ -static void brush_painter_imbuf_partial_update(BrushPainter *painter, const float pos[2]) +static void brush_painter_imbuf_partial_update(BrushPainter *painter, const float pos[2], int diameter) { - const Scene *scene = painter->scene; - Brush *brush = painter->brush; BrushPainterCache *cache = &painter->cache; ImBuf *oldtexibuf, *ibuf; int imbflag, destx, desty, srcx, srcy, w, h, x1, y1, x2, y2; - int diameter = 2 * BKE_brush_size_get(scene, brush); /* create brush image buffer if it didn't exist yet */ imbflag = (cache->use_float) ? IB_rectfloat : IB_rect; @@ -478,10 +570,10 @@ static void brush_painter_imbuf_partial_update(BrushPainter *painter, const floa if (oldtexibuf) { srcx = srcy = 0; - destx = (int)painter->lastpaintpos[0] - (int)pos[0]; - desty = (int)painter->lastpaintpos[1] - (int)pos[1]; w = oldtexibuf->x; h = oldtexibuf->y; + destx = (int)painter->lastpaintpos[0] - (int)pos[0] + (diameter / 2 - w / 2); + desty = (int)painter->lastpaintpos[1] - (int)pos[1] + (diameter / 2 - h / 2); IMB_rectclip(cache->texibuf, oldtexibuf, &destx, &desty, &srcx, &srcy, &w, &h); } @@ -514,7 +606,7 @@ static void brush_painter_imbuf_partial_update(BrushPainter *painter, const floa brush_painter_imbuf_update(painter, NULL, x1, y2, x2, ibuf->y, 0, 0); } -static void brush_painter_2d_tex_mapping(ImagePaintState *s, int size, const float startpos[2], const float pos[2], const float mouse[2], int mapmode, rctf *mapping) +static void brush_painter_2d_tex_mapping(ImagePaintState *s, int diameter, const float startpos[2], const float pos[2], const float mouse[2], int mapmode, rctf *mapping) { float invw = 1.0f / (float)s->canvas->x; float invh = 1.0f / (float)s->canvas->y; @@ -522,19 +614,19 @@ static void brush_painter_2d_tex_mapping(ImagePaintState *s, int size, const flo int ipos[2]; /* find start coordinate of brush in canvas */ - ipos[0] = (int)floorf((pos[0] - size / 2) + 1.0f); - ipos[1] = (int)floorf((pos[1] - size / 2) + 1.0f); + ipos[0] = (int)floorf((pos[0] - diameter / 2) + 1.0f); + ipos[1] = (int)floorf((pos[1] - diameter / 2) + 1.0f); if (mapmode == MTEX_MAP_MODE_STENCIL) { /* map from view coordinates of brush to region coordinates */ UI_view2d_view_to_region(s->v2d, ipos[0] * invw, ipos[1] * invh, &xmin, &ymin); - UI_view2d_view_to_region(s->v2d, (ipos[0] + size) * invw, (ipos[1] + size) * invh, &xmax, &ymax); + UI_view2d_view_to_region(s->v2d, (ipos[0] + diameter) * invw, (ipos[1] + diameter) * invh, &xmax, &ymax); /* output mapping from brush ibuf x/y to region coordinates */ mapping->xmin = xmin; mapping->ymin = ymin; - mapping->xmax = (xmax - xmin) / (float)size; - mapping->ymax = (ymax - ymin) / (float)size; + mapping->xmax = (xmax - xmin) / (float)diameter; + mapping->ymax = (ymax - ymin) / (float)diameter; } else if (mapmode == MTEX_MAP_MODE_3D) { /* 3D mapping, just mapping to canvas 0..1 */ @@ -545,104 +637,126 @@ static void brush_painter_2d_tex_mapping(ImagePaintState *s, int size, const flo } else if (ELEM(mapmode, MTEX_MAP_MODE_VIEW, MTEX_MAP_MODE_RANDOM)) { /* view mapping */ - mapping->xmin = mouse[0] - size * 0.5f + 0.5f; - mapping->ymin = mouse[1] - size * 0.5f + 0.5f; + mapping->xmin = mouse[0] - diameter * 0.5f + 0.5f; + mapping->ymin = mouse[1] - diameter * 0.5f + 0.5f; mapping->xmax = 1.0f; mapping->ymax = 1.0f; } else /* if (mapmode == MTEX_MAP_MODE_TILED) */ { - mapping->xmin = -size * 0.5f + 0.5f + (int)pos[0] - (int)startpos[0]; - mapping->ymin = -size * 0.5f + 0.5f + (int)pos[1] - (int)startpos[1]; + mapping->xmin = (int)(-diameter * 0.5) + (int)pos[0] - (int)startpos[0]; + mapping->ymin = (int)(-diameter * 0.5) + (int)pos[1] - (int)startpos[1]; mapping->xmax = 1.0f; mapping->ymax = 1.0f; } } -static void brush_painter_2d_refresh_cache(ImagePaintState *s, BrushPainter *painter, const float pos[2], const float mouse[2]) +static void brush_painter_2d_refresh_cache(ImagePaintState *s, BrushPainter *painter, const float pos[2], const float mouse[2], float pressure, float distance, float size) { 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; + const int diameter = 2 * size; bool do_random = false; bool do_partial_update = false; - bool do_view = false; + bool update_color = (brush->flag & BRUSH_USE_GRADIENT) && + ((ELEM(brush->gradient_stroke_mode, + BRUSH_GRADIENT_SPACING_REPEAT, + BRUSH_GRADIENT_SPACING_CLAMP)) || + (cache->last_pressure != pressure)); float tex_rotation = -brush->mtex.rot; float mask_rotation = -brush->mask_mtex.rot; + painter->pool = BKE_image_pool_new(); + /* 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 + else if (!((brush->flag & BRUSH_ANCHORED) || update_color)) do_partial_update = true; - brush_painter_2d_tex_mapping(s, size, painter->startpaintpos, pos, mouse, + brush_painter_2d_tex_mapping(s, diameter, painter->startpaintpos, pos, mouse, brush->mtex.brush_map_mode, &painter->tex_mapping); } if (painter->cache.is_maskbrush) { + bool renew_maxmask = false; + bool do_partial_update_mask = false; + /* invalidate case for all mapping modes */ 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; + else if (brush->mask_mtex.brush_map_mode == MTEX_MAP_MODE_RANDOM) { + renew_maxmask = true; + } + else if (!(brush->flag & BRUSH_ANCHORED)) { + do_partial_update_mask = true; + renew_maxmask = true; + } + /* explicilty disable partial update even if it has been enabled above */ + if (brush->mask_pressure) { + do_partial_update_mask = false; + renew_maxmask = true; + } - brush_painter_2d_tex_mapping(s, size, painter->startpaintpos, pos, mouse, - brush->mask_mtex.brush_map_mode, &painter->mask_mapping); + if ((diameter != cache->lastdiameter) || + (mask_rotation != cache->last_mask_rotation) || + renew_maxmask) + { + if (cache->tex_mask) { + MEM_freeN(cache->tex_mask); + cache->tex_mask = NULL; + } + + brush_painter_2d_tex_mapping(s, diameter, painter->startpaintpos, pos, mouse, + brush->mask_mtex.brush_map_mode, &painter->mask_mapping); + + if (do_partial_update_mask) + brush_painter_mask_imbuf_partial_update(painter, pos, diameter); + else + cache->tex_mask = brush_painter_mask_ibuf_new(painter, diameter); + cache->last_mask_rotation = mask_rotation; + } } - if (do_view || do_random) - do_partial_update = false; + /* curve mask can only change if the size changes */ + if (diameter != cache->lastdiameter) { + if (cache->curve_mask) { + MEM_freeN(cache->curve_mask); + cache->curve_mask = NULL; + } - painter->pool = BKE_image_pool_new(); + cache->curve_mask = brush_painter_curve_mask_new(painter, diameter, size); + } /* 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 ((diameter != cache->lastdiameter) || + (tex_rotation != cache->last_tex_rotation) || + do_random || + update_color) { 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); + /* do partial update of texture */ + brush_painter_imbuf_partial_update(painter, pos, diameter); } 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); + /* create brush from scratch */ + cache->ibuf = brush_painter_imbuf_new(painter, diameter, pressure, distance); } - cache->lastsize = diameter; - cache->lastalpha = alpha; - cache->lastjitter = brush->jitter; + cache->lastdiameter = diameter; cache->last_tex_rotation = tex_rotation; - cache->last_mask_rotation = mask_rotation; + cache->last_pressure = pressure; } else if (do_partial_update) { /* do only partial update of texture */ @@ -650,7 +764,7 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s, BrushPainter *pai int dy = (int)painter->lastpaintpos[1] - (int)pos[1]; if ((dx != 0) || (dy != 0)) { - brush_painter_imbuf_partial_update(painter, pos); + brush_painter_imbuf_partial_update(painter, pos, diameter); } } @@ -703,7 +817,7 @@ static void paint_2d_ibuf_rgb_set(ImBuf *ibuf, int x, int y, const bool is_torus } } -static int paint_2d_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, float *outrgb, short torus) +static float paint_2d_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, float *outrgb, short torus, float w) { float inrgb[4]; @@ -716,16 +830,23 @@ static int paint_2d_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, flo paint_2d_ibuf_rgb_get(ibuf, x, y, 0, inrgb); } + mul_v4_fl(inrgb, w); add_v4_v4(outrgb, inrgb); - return 1; + return w; } -static void paint_2d_lift_soften(ImBuf *ibuf, ImBuf *ibufb, int *pos, const bool is_torus) +static void paint_2d_lift_soften(ImagePaintState *s, ImBuf *ibuf, ImBuf *ibufb, int *pos, const short is_torus) { - int x, y, count, xi, yi, xo, yo; + 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; @@ -741,28 +862,52 @@ static void paint_2d_lift_soften(ImBuf *ibuf, ImBuf *ibufb, int *pos, const bool 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 = 1; - paint_2d_ibuf_rgb_get(ibuf, xi, yi, is_torus, outrgb); + count = 0.0; + paint_2d_ibuf_rgb_get(ibuf, xi, yi, is_torus, rgba); + zero_v4(outrgb); - count += paint_2d_ibuf_add_if(ibuf, xi - 1, yi - 1, outrgb, is_torus); - count += paint_2d_ibuf_add_if(ibuf, xi - 1, yi, outrgb, is_torus); - count += paint_2d_ibuf_add_if(ibuf, xi - 1, yi + 1, outrgb, is_torus); + 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, is_torus, + kernel->wdata[xk + yk * kernel->side]); + } + } - count += paint_2d_ibuf_add_if(ibuf, xi, yi - 1, outrgb, is_torus); - count += paint_2d_ibuf_add_if(ibuf, xi, yi + 1, outrgb, is_torus); + if (count > 0.0f) { + mul_v4_fl(outrgb, 1.0f / (float)count); - count += paint_2d_ibuf_add_if(ibuf, xi + 1, yi - 1, outrgb, is_torus); - count += paint_2d_ibuf_add_if(ibuf, xi + 1, yi, outrgb, is_torus); - count += paint_2d_ibuf_add_if(ibuf, xi + 1, yi + 1, outrgb, is_torus); + if (sharpen) { + /* subtract blurred image from normal image gives high pass filter */ + sub_v3_v3v3(outrgb, rgba, outrgb); - mul_v4_fl(outrgb, 1.0f / (float)count); + /* 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 threshhold */ + outrgb[0] = outrgb[1] = outrgb[2] = rgb_to_grayscale(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; @@ -830,10 +975,10 @@ static void paint_2d_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos) tot = paint_2d_torus_split_region(region, ibufb, ibuf); for (a = 0; a < tot; a++) - IMB_rectblend(ibufb, ibufb, ibuf, NULL, NULL, 0, region[a].destx, region[a].desty, + IMB_rectblend(ibufb, ibufb, ibuf, NULL, 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, IMB_BLEND_COPY_RGB); + region[a].width, region[a].height, IMB_BLEND_COPY_RGB, false); } static ImBuf *paint_2d_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos) @@ -844,10 +989,10 @@ static ImBuf *paint_2d_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos) ImBuf *clonebuf = IMB_allocImBuf(w, h, ibufb->planes, ibufb->flags); IMB_rectclip(clonebuf, ibuf, &destx, &desty, &srcx, &srcy, &w, &h); - IMB_rectblend(clonebuf, clonebuf, ibufb, NULL, NULL, 0, destx, desty, destx, desty, destx, desty, w, h, - IMB_BLEND_COPY_ALPHA); - IMB_rectblend(clonebuf, clonebuf, ibuf, NULL, NULL, 0, destx, desty, destx, desty, srcx, srcy, w, h, - IMB_BLEND_COPY_RGB); + IMB_rectblend(clonebuf, clonebuf, ibufb, NULL, NULL, NULL, 0, destx, desty, destx, desty, destx, desty, w, h, + IMB_BLEND_COPY_ALPHA, false); + IMB_rectblend(clonebuf, clonebuf, ibuf, NULL, NULL, NULL, 0, destx, desty, destx, desty, srcx, srcy, w, h, + IMB_BLEND_COPY_RGB, false); return clonebuf; } @@ -858,17 +1003,16 @@ static void paint_2d_convert_brushco(ImBuf *ibufb, const float pos[2], int ipos[ ipos[1] = (int)floorf((pos[1] - ibufb->y / 2) + 1.0f); } -static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *maskb, const float lastpos[2], const float pos[2]) +static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *curveb, unsigned short *texmaskb, const float lastpos[2], const float pos[2]) { ImagePaintState *s = ((ImagePaintState *)state); - ImBuf *clonebuf = NULL, *frombuf, *tmpbuf = NULL; + ImBuf *clonebuf = NULL, *frombuf; ImagePaintRegion region[4]; short torus = s->brush->flag & BRUSH_TORUS; short blend = s->blend; const 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); + float mask_max = BKE_brush_alpha_get(s->scene, s->brush); int bpos[2], blastpos[2], bliftpos[2]; int a, tot; @@ -876,7 +1020,7 @@ static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *maskb, const f /* lift from canvas */ if (s->tool == PAINT_TOOL_SOFTEN) { - paint_2d_lift_soften(s->canvas, ibufb, bpos, torus); + paint_2d_lift_soften(s, s->canvas, ibufb, bpos, torus); } else if (s->tool == PAINT_TOOL_SMEAR) { if (lastpos[0] == pos[0] && lastpos[1] == pos[1]) @@ -903,9 +1047,6 @@ static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *maskb, const f 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++) { @@ -916,11 +1057,14 @@ static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *maskb, const f if (s->do_masking) { /* masking, find original pixels tiles from undo buffer to composite over */ int tilex, tiley, tilew, tileh, tx, ty; + ImBuf *tmpbuf; imapaint_region_tiles(s->canvas, region[a].destx, region[a].desty, region[a].width, region[a].height, &tilex, &tiley, &tilew, &tileh); - + + tmpbuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, 0); + for (ty = tiley; ty <= tileh; ty++) { for (tx = tilex; tx <= tilew; tx++) { /* retrieve original pixels + mask from undo buffer */ @@ -929,31 +1073,32 @@ static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *maskb, const f 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); + tmpbuf->rect_float = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask, false); else - tmpbuf->rect = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask); + tmpbuf->rect = image_undo_find_tile(s->image, s->canvas, tx, ty, &mask, false); IMB_rectblend(s->canvas, tmpbuf, frombuf, mask, - maskb, mask_max, + curveb, texmaskb, mask_max, region[a].destx, region[a].desty, origx, origy, region[a].srcx, region[a].srcy, - region[a].width, region[a].height, blend); + region[a].width, region[a].height, blend, ((s->brush->flag & BRUSH_ACCUMULATE) != 0)); } } + + IMB_freeImBuf(tmpbuf); } else { /* no masking, composite brush directly onto canvas */ - IMB_rectblend(s->canvas, s->canvas, frombuf, NULL, NULL, 0, + IMB_rectblend(s->canvas, s->canvas, frombuf, NULL, curveb, texmaskb, mask_max, 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); + region[a].width, region[a].height, blend, false); } } if (clonebuf) IMB_freeImBuf(clonebuf); - if (tmpbuf) IMB_freeImBuf(tmpbuf); return 1; } @@ -1003,10 +1148,7 @@ static int paint_2d_canvas_set(ImagePaintState *s, Image *ima) } /* set masking */ - s->do_masking = (s->brush->flag & BRUSH_AIRBRUSH || - (s->brush->imagepaint_tool == PAINT_TOOL_SMEAR) || - (s->brush->mtex.tex && !ELEM(s->brush->mtex.brush_map_mode, MTEX_MAP_MODE_TILED, MTEX_MAP_MODE_STENCIL, MTEX_MAP_MODE_3D))) - ? false : true; + s->do_masking = paint_use_opacity_masking(s->brush); return 1; } @@ -1016,11 +1158,15 @@ static void paint_2d_canvas_free(ImagePaintState *s) BKE_image_release_ibuf(s->image, s->canvas, NULL); BKE_image_release_ibuf(s->brush->clone.image, s->clonecanvas, NULL); - if (s->do_masking) - image_undo_remove_masks(); + if (s->blurkernel) { + paint_delete_blur_kernel(s->blurkernel); + MEM_freeN(s->blurkernel); + } + + image_undo_remove_masks(); } -void paint_2d_stroke(void *ps, const float prev_mval[2], const float mval[2], int eraser) +void paint_2d_stroke(void *ps, const float prev_mval[2], const float mval[2], int eraser, float pressure, float distance, float size) { float newuv[2], olduv[2]; ImagePaintState *s = ps; @@ -1063,17 +1209,17 @@ void paint_2d_stroke(void *ps, const float prev_mval[2], const float mval[2], in /* OCIO_TODO: float buffers are now always linear, so always use color correction * this should probably be changed when texture painting color space is supported */ - brush_painter_2d_require_imbuf(painter, (ibuf->rect_float != NULL), !is_data, s->do_masking); + brush_painter_2d_require_imbuf(painter, (ibuf->rect_float != NULL), !is_data); - brush_painter_2d_refresh_cache(s, painter, newuv, mval); + brush_painter_2d_refresh_cache(s, painter, newuv, mval, pressure, distance, size); - if (paint_2d_op(s, painter->cache.ibuf, painter->cache.mask, olduv, newuv)) + if (paint_2d_op(s, painter->cache.ibuf, painter->cache.curve_mask, painter->cache.tex_mask, olduv, newuv)) s->need_redraw = true; BKE_image_release_ibuf(s->image, ibuf, NULL); } -void *paint_2d_new_stroke(bContext *C, wmOperator *op) +void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode) { Scene *scene = CTX_data_scene(C); ToolSettings *settings = scene->toolsettings; @@ -1102,10 +1248,14 @@ void *paint_2d_new_stroke(bContext *C, wmOperator *op) return NULL; } + if (brush->imagepaint_tool == PAINT_TOOL_SOFTEN) { + s->blurkernel = paint_new_blur_kernel(brush); + } + paint_brush_init_tex(s->brush); /* create painter */ - s->painter = brush_painter_2d_new(scene, s->brush); + s->painter = brush_painter_2d_new(scene, s->brush, mode == BRUSH_STROKE_INVERT); return s; } @@ -1134,6 +1284,7 @@ void paint_2d_redraw(const bContext *C, void *ps, bool final) /* compositor listener deals with updating */ WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, s->image); + DAG_id_tag_update(&s->image->id, 0); } else { if (!s->sima || !s->sima->lock) @@ -1153,3 +1304,334 @@ void paint_2d_stroke_done(void *ps) MEM_freeN(s); } + +static void paint_2d_fill_add_pixel_byte( + const int x_px, const int y_px, ImBuf *ibuf, BLI_Stack *stack, BLI_bitmap *touched, + const float color[4], float threshold_sq) +{ + int coordinate; + + if (x_px >= ibuf->x || x_px < 0 || y_px >= ibuf->y || y_px < 0) + return; + + coordinate = y_px * ibuf->x + x_px; + + if (!BLI_BITMAP_TEST(touched, coordinate)) { + float color_f[4]; + unsigned char *color_b = (unsigned char *)(ibuf->rect + coordinate); + rgba_uchar_to_float(color_f, color_b); + + if (compare_len_squared_v3v3(color_f, color, threshold_sq)) { + BLI_stack_push(stack, &coordinate); + } + BLI_BITMAP_SET(touched, coordinate, true); + } +} + +static void paint_2d_fill_add_pixel_float( + const int x_px, const int y_px, ImBuf *ibuf, BLI_Stack *stack, BLI_bitmap *touched, + const float color[4], float threshold_sq) +{ + int coordinate; + + if (x_px >= ibuf->x || x_px < 0 || y_px >= ibuf->y || y_px < 0) + return; + + coordinate = y_px * ibuf->x + x_px; + + if (!BLI_BITMAP_TEST(touched, coordinate)) { + if (compare_len_squared_v3v3(ibuf->rect_float + 4 * coordinate, color, threshold_sq)) { + BLI_stack_push(stack, &coordinate); + } + BLI_BITMAP_SET(touched, coordinate, true); + } +} + +/* this function expects linear space color values */ +void paint_2d_bucket_fill( + const bContext *C, const float color[3], Brush *br, + const float mouse_init[2], + void *ps) +{ + SpaceImage *sima = CTX_wm_space_image(C); + Image *ima = sima->image; + + ImagePaintState *s = ps; + + ImBuf *ibuf; + int x_px, y_px; + unsigned int color_b; + float color_f[4]; + float strength = br ? br->alpha : 1.0f; + + bool do_float; + + if (!ima) + return; + + ibuf = BKE_image_acquire_ibuf(ima, &sima->iuser, NULL); + + if (!ibuf) + return; + + do_float = (ibuf->rect_float != NULL); + /* first check if our image is float. If it is not we should correct the color to + * be in gamma space. strictly speaking this is not correct, but blender does not paint + * byte images in linear space */ + if (!do_float) { + linearrgb_to_srgb_uchar3((unsigned char *)&color_b, color); + *(((char *)&color_b) + 3) = strength * 255; + } + else { + copy_v3_v3(color_f, color); + color_f[3] = strength; + } + + if (!mouse_init || !br) { + /* first case, no image UV, fill the whole image */ + ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y); + + if (do_float) { + for (; x_px < ibuf->x; x_px++) { + for (y_px = 0; y_px < ibuf->y; y_px++) { + blend_color_mix_float(ibuf->rect_float + 4 * (y_px * ibuf->x + x_px), + ibuf->rect_float + 4 * (y_px * ibuf->x + x_px), color_f); + } + } + } + else { + for (; x_px < ibuf->x; x_px++) { + for (y_px = 0; y_px < ibuf->y; y_px++) { + blend_color_mix_byte((unsigned char *)(ibuf->rect + y_px * ibuf->x + x_px), + (unsigned char *)(ibuf->rect + y_px * ibuf->x + x_px), (unsigned char *)&color_b); + } + } + } + } + else { + /* second case, start sweeping the neighboring pixels, looking for pixels whose + * value is within the brush fill threshold from the fill color */ + BLI_Stack *stack; + BLI_bitmap *touched; + int coordinate; + int width = ibuf->x; + float image_init[2]; + int minx = ibuf->x, miny = ibuf->y, maxx = 0, maxy = 0; + float pixel_color[4]; + float threshold_sq = br->fill_threshold * br->fill_threshold; + + UI_view2d_region_to_view(s->v2d, mouse_init[0], mouse_init[1], &image_init[0], &image_init[1]); + + x_px = image_init[0] * ibuf->x; + y_px = image_init[1] * ibuf->y; + + if (x_px >= ibuf->x || x_px < 0 || y_px > ibuf->y || y_px < 0) { + BKE_image_release_ibuf(ima, ibuf, NULL); + return; + } + + /* change image invalidation method later */ + ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y); + + stack = BLI_stack_new(sizeof(int), __func__); + touched = BLI_BITMAP_NEW(ibuf->x * ibuf->y, "bucket_fill_bitmap"); + + coordinate = (y_px * ibuf->x + x_px); + + if (do_float) { + copy_v4_v4(pixel_color, ibuf->rect_float + 4 * coordinate); + } + else { + int pixel_color_b = *(ibuf->rect + coordinate); + rgba_uchar_to_float(pixel_color, (unsigned char *)&pixel_color_b); + } + + BLI_stack_push(stack, &coordinate); + BLI_BITMAP_SET(touched, coordinate, true); + + if (do_float) { + while (!BLI_stack_is_empty(stack)) { + BLI_stack_pop(stack, &coordinate); + + IMB_blend_color_float(ibuf->rect_float + 4 * (coordinate), + ibuf->rect_float + 4 * (coordinate), + color_f, br->blend); + + /* reconstruct the coordinates here */ + x_px = coordinate % width; + y_px = coordinate / width; + + paint_2d_fill_add_pixel_float(x_px - 1, y_px - 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px - 1, y_px, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px - 1, y_px + 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px, y_px + 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px, y_px - 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px + 1, y_px - 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px + 1, y_px, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_float(x_px + 1, y_px + 1, ibuf, stack, touched, pixel_color, threshold_sq); + + if (x_px > maxx) + maxx = x_px; + if (x_px < minx) + minx = x_px; + if (y_px > maxy) + maxy = y_px; + if (x_px > miny) + miny = y_px; + } + } + else { + while (!BLI_stack_is_empty(stack)) { + BLI_stack_pop(stack, &coordinate); + + IMB_blend_color_byte((unsigned char *)(ibuf->rect + coordinate), + (unsigned char *)(ibuf->rect + coordinate), + (unsigned char *)&color_b, br->blend); + + /* reconstruct the coordinates here */ + x_px = coordinate % width; + y_px = coordinate / width; + + paint_2d_fill_add_pixel_byte(x_px - 1, y_px - 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px - 1, y_px, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px - 1, y_px + 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px, y_px + 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px, y_px - 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px + 1, y_px - 1, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px + 1, y_px, ibuf, stack, touched, pixel_color, threshold_sq); + paint_2d_fill_add_pixel_byte(x_px + 1, y_px + 1, ibuf, stack, touched, pixel_color, threshold_sq); + + if (x_px > maxx) + maxx = x_px; + if (x_px < minx) + minx = x_px; + if (y_px > maxy) + maxy = y_px; + if (x_px > miny) + miny = y_px; + } + } + + MEM_freeN(touched); + BLI_stack_free(stack); + } + + imapaint_image_update(sima, ima, ibuf, false); + ED_imapaint_clear_partial_redraw(); + + BKE_image_release_ibuf(ima, ibuf, NULL); + + WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, ima); +} + +void paint_2d_gradient_fill( + const bContext *C, Brush *br, + const float mouse_init[2], const float mouse_final[2], + void *ps) +{ + SpaceImage *sima = CTX_wm_space_image(C); + Image *ima = sima->image; + ImagePaintState *s = ps; + + ImBuf *ibuf; + int x_px, y_px; + unsigned int color_b; + float color_f[4]; + float image_init[2], image_final[2]; + float tangent[2]; + float line_len_sq_inv, line_len; + + bool do_float; + + if (!ima) + return; + + ibuf = BKE_image_acquire_ibuf(ima, &sima->iuser, NULL); + + if (!ibuf) + return; + + UI_view2d_region_to_view(s->v2d, mouse_final[0], mouse_final[1], &image_final[0], &image_final[1]); + UI_view2d_region_to_view(s->v2d, mouse_init[0], mouse_init[1], &image_init[0], &image_init[1]); + + image_final[0] *= ibuf->x; + image_final[1] *= ibuf->y; + + image_init[0] *= ibuf->x; + image_init[1] *= ibuf->y; + + /* some math to get needed gradient variables */ + sub_v2_v2v2(tangent, image_final, image_init); + line_len = len_squared_v2(tangent); + line_len_sq_inv = 1.0f / line_len; + line_len = sqrt(line_len); + + do_float = (ibuf->rect_float != NULL); + + /* this will be substituted by something else when selection is available */ + ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y); + + if (do_float) { + for (x_px = 0; x_px < ibuf->x; x_px++) { + for (y_px = 0; y_px < ibuf->y; y_px++) { + float f; + float p[2] = {x_px - image_init[0], y_px - image_init[1]}; + + switch (br->gradient_fill_mode) { + case BRUSH_GRADIENT_LINEAR: + { + f = dot_v2v2(p, tangent) * line_len_sq_inv; + break; + } + case BRUSH_GRADIENT_RADIAL: + { + f = len_v2(p) / line_len; + break; + } + } + do_colorband(br->gradient, f, color_f); + /* convert to premultiplied */ + mul_v3_fl(color_f, color_f[3]); + color_f[3] *= br->alpha; + IMB_blend_color_float(ibuf->rect_float + 4 * (y_px * ibuf->x + x_px), + ibuf->rect_float + 4 * (y_px * ibuf->x + x_px), + color_f, br->blend); + } + } + } + else { + for (x_px = 0; x_px < ibuf->x; x_px++) { + for (y_px = 0; y_px < ibuf->y; y_px++) { + float f; + float p[2] = {x_px - image_init[0], y_px - image_init[1]}; + + switch (br->gradient_fill_mode) { + case BRUSH_GRADIENT_LINEAR: + { + f = dot_v2v2(p, tangent) * line_len_sq_inv; + break; + } + case BRUSH_GRADIENT_RADIAL: + { + f = len_v2(p) / line_len; + break; + } + } + + do_colorband(br->gradient, f, color_f); + rgba_float_to_uchar((unsigned char *)&color_b, color_f); + ((unsigned char *)&color_b)[3] *= br->alpha; + IMB_blend_color_byte((unsigned char *)(ibuf->rect + y_px * ibuf->x + x_px), + (unsigned char *)(ibuf->rect + y_px * ibuf->x + x_px), + (unsigned char *)&color_b, br->blend); + } + } + } + + imapaint_image_update(sima, ima, ibuf, false); + ED_imapaint_clear_partial_redraw(); + + BKE_image_release_ibuf(ima, ibuf, NULL); + + WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, ima); +} |