2013-01-01 21:23:12 +00:00
|
|
|
/*
|
|
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* The Original Code is: all of this file.
|
|
|
|
*
|
|
|
|
* Contributor(s): none yet.
|
|
|
|
*
|
|
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
|
|
*/
|
|
|
|
|
2013-01-02 01:49:07 +00:00
|
|
|
/** \file blender/editors/sculpt_paint/paint_image_2d.c
|
2013-01-01 21:23:12 +00:00
|
|
|
* \ingroup bke
|
|
|
|
*/
|
|
|
|
//#include <math.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
|
|
#include "DNA_brush_types.h"
|
|
|
|
#include "DNA_scene_types.h"
|
2013-03-07 12:11:38 +00:00
|
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "DNA_object_types.h"
|
2013-01-01 21:23:12 +00:00
|
|
|
|
2013-03-09 05:35:49 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
#include "BLI_math_color_blend.h"
|
|
|
|
#include "BLI_stack.h"
|
|
|
|
#include "BLI_bitmap.h"
|
2016-05-06 11:48:07 +02:00
|
|
|
#include "BLI_task.h"
|
2014-07-21 12:02:05 +02:00
|
|
|
|
2017-12-07 15:36:26 +11:00
|
|
|
#include "BKE_colorband.h"
|
2013-03-07 12:11:38 +00:00
|
|
|
#include "BKE_context.h"
|
2014-07-21 12:02:05 +02:00
|
|
|
#include "BKE_depsgraph.h"
|
2013-01-01 21:23:12 +00:00
|
|
|
#include "BKE_brush.h"
|
2013-03-07 12:11:38 +00:00
|
|
|
#include "BKE_image.h"
|
|
|
|
#include "BKE_paint.h"
|
|
|
|
#include "BKE_report.h"
|
|
|
|
|
2014-06-23 21:59:34 +10:00
|
|
|
#include "ED_paint.h"
|
2013-03-07 12:11:38 +00:00
|
|
|
#include "ED_screen.h"
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
#include "IMB_imbuf.h"
|
|
|
|
#include "IMB_imbuf_types.h"
|
2013-03-07 12:11:38 +00:00
|
|
|
#include "IMB_colormanagement.h"
|
|
|
|
|
|
|
|
#include "WM_api.h"
|
|
|
|
#include "WM_types.h"
|
|
|
|
|
|
|
|
#include "UI_view2d.h"
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
#include "GPU_draw.h"
|
|
|
|
|
|
|
|
#include "paint_intern.h"
|
|
|
|
|
|
|
|
/* Brush Painting for 2D image editor */
|
|
|
|
|
|
|
|
/* Defines and Structs */
|
|
|
|
|
2013-03-10 18:05:13 +00:00
|
|
|
typedef struct BrushPainterCache {
|
2013-05-12 09:14:13 +00:00
|
|
|
bool use_float; /* need float imbuf? */
|
|
|
|
bool use_color_correction; /* use color correction for float */
|
2014-07-21 12:02:05 +02:00
|
|
|
bool invert;
|
2013-05-12 09:14:13 +00:00
|
|
|
|
|
|
|
bool is_texbrush;
|
|
|
|
bool is_maskbrush;
|
2013-03-10 18:05:13 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
int lastdiameter;
|
2013-05-12 09:14:13 +00:00
|
|
|
float last_tex_rotation;
|
|
|
|
float last_mask_rotation;
|
2014-07-21 12:02:05 +02:00
|
|
|
float last_pressure;
|
2013-03-10 18:05:13 +00:00
|
|
|
|
|
|
|
ImBuf *ibuf;
|
|
|
|
ImBuf *texibuf;
|
2014-07-21 12:02:05 +02:00
|
|
|
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;
|
2013-03-10 18:05:13 +00:00
|
|
|
} BrushPainterCache;
|
|
|
|
|
|
|
|
typedef struct BrushPainter {
|
|
|
|
Scene *scene;
|
|
|
|
Brush *brush;
|
|
|
|
|
|
|
|
float lastpaintpos[2]; /* position of last paint op */
|
|
|
|
float startpaintpos[2]; /* position of first paint */
|
|
|
|
|
|
|
|
short firsttouch; /* first paint op */
|
|
|
|
|
2013-04-30 09:59:40 +00:00
|
|
|
struct ImagePool *pool; /* image pool */
|
2013-05-12 09:14:13 +00:00
|
|
|
rctf tex_mapping; /* texture coordinate mapping */
|
|
|
|
rctf mask_mapping; /* mask texture coordinate mapping */
|
2013-04-30 09:59:40 +00:00
|
|
|
|
2013-03-10 18:05:13 +00:00
|
|
|
BrushPainterCache cache;
|
|
|
|
} BrushPainter;
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
typedef struct ImagePaintRegion {
|
|
|
|
int destx, desty;
|
|
|
|
int srcx, srcy;
|
|
|
|
int width, height;
|
|
|
|
} ImagePaintRegion;
|
|
|
|
|
|
|
|
typedef struct ImagePaintState {
|
|
|
|
BrushPainter *painter;
|
|
|
|
SpaceImage *sima;
|
|
|
|
View2D *v2d;
|
|
|
|
Scene *scene;
|
|
|
|
bScreen *screen;
|
2013-04-30 09:59:40 +00:00
|
|
|
struct ImagePool *image_pool;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
Brush *brush;
|
|
|
|
short tool, blend;
|
|
|
|
Image *image;
|
|
|
|
ImBuf *canvas;
|
|
|
|
ImBuf *clonecanvas;
|
2014-04-27 00:22:49 +10:00
|
|
|
const char *warnpackedfile;
|
|
|
|
const char *warnmultifile;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2013-04-30 06:07:42 +00:00
|
|
|
bool do_masking;
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
/* viewport texture paint only, but _not_ project paint */
|
|
|
|
Object *ob;
|
|
|
|
int faceindex;
|
|
|
|
float uv[2];
|
|
|
|
int do_facesel;
|
2015-07-27 12:55:40 +02:00
|
|
|
int symmetry;
|
2013-05-15 14:37:05 +00:00
|
|
|
|
|
|
|
bool need_redraw;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
BlurKernel *blurkernel;
|
2013-03-07 12:11:38 +00:00
|
|
|
} ImagePaintState;
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
static BrushPainter *brush_painter_2d_new(Scene *scene, Brush *brush, bool invert)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
|
|
|
BrushPainter *painter = MEM_callocN(sizeof(BrushPainter), "BrushPainter");
|
|
|
|
|
|
|
|
painter->brush = brush;
|
|
|
|
painter->scene = scene;
|
|
|
|
painter->firsttouch = 1;
|
2014-07-21 12:02:05 +02:00
|
|
|
painter->cache.lastdiameter = -1; /* force ibuf create in refresh */
|
|
|
|
painter->cache.invert = invert;
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
return painter;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
static void brush_painter_2d_require_imbuf(BrushPainter *painter, bool use_float, bool use_color_correction)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
2013-05-12 09:14:13 +00:00
|
|
|
Brush *brush = painter->brush;
|
|
|
|
|
|
|
|
if ((painter->cache.use_float != use_float)) {
|
2013-01-01 21:23:12 +00:00
|
|
|
if (painter->cache.ibuf) IMB_freeImBuf(painter->cache.ibuf);
|
2014-07-21 12:02:05 +02:00
|
|
|
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);
|
2013-05-12 09:14:13 +00:00
|
|
|
painter->cache.ibuf = NULL;
|
2014-07-21 12:02:05 +02:00
|
|
|
painter->cache.curve_mask = NULL;
|
|
|
|
painter->cache.tex_mask = NULL;
|
|
|
|
painter->cache.lastdiameter = -1; /* force ibuf create in refresh */
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
painter->cache.use_float = use_float;
|
|
|
|
painter->cache.use_color_correction = use_float && use_color_correction;
|
|
|
|
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;
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
|
2013-03-10 18:05:13 +00:00
|
|
|
static void brush_painter_2d_free(BrushPainter *painter)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
|
|
|
if (painter->cache.ibuf) IMB_freeImBuf(painter->cache.ibuf);
|
|
|
|
if (painter->cache.texibuf) IMB_freeImBuf(painter->cache.texibuf);
|
2014-07-21 12:02:05 +02:00
|
|
|
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);
|
2013-01-01 21:23:12 +00:00
|
|
|
MEM_freeN(painter);
|
|
|
|
}
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
static void brush_imbuf_tex_co(rctf *mapping, int x, int y, float texco[3])
|
|
|
|
{
|
|
|
|
texco[0] = mapping->xmin + x * mapping->xmax;
|
|
|
|
texco[1] = mapping->ymin + y * mapping->ymax;
|
|
|
|
texco[2] = 0.0f;
|
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
/* create a mask with the mask texture */
|
|
|
|
static unsigned short *brush_painter_mask_ibuf_new(BrushPainter *painter, int size)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
|
|
|
Scene *scene = painter->scene;
|
|
|
|
Brush *brush = painter->brush;
|
2014-07-21 12:02:05 +02:00
|
|
|
rctf mask_mapping = painter->mask_mapping;
|
|
|
|
struct ImagePool *pool = painter->pool;
|
2013-01-01 21:23:12 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
float texco[3];
|
2013-05-12 09:14:13 +00:00
|
|
|
unsigned short *mask, *m;
|
2014-07-21 12:02:05 +02:00
|
|
|
int x, y, thread = 0;
|
2013-05-12 09:14:13 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
mask = MEM_mallocN(sizeof(unsigned short) * size * size, "brush_painter_mask");
|
2013-05-12 09:14:13 +00:00
|
|
|
m = mask;
|
|
|
|
|
|
|
|
for (y = 0; y < size; y++) {
|
|
|
|
for (x = 0; x < size; x++, m++) {
|
2014-07-21 12:02:05 +02:00
|
|
|
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;
|
2016-05-03 17:49:18 +10:00
|
|
|
destx = (int)floorf(painter->lastpaintpos[0]) - (int)floorf(pos[0]) + (diameter / 2 - w / 2);
|
|
|
|
desty = (int)floorf(painter->lastpaintpos[1]) - (int)floorf(pos[1]) + (diameter / 2 - h / 2);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2015-03-13 13:09:52 +01:00
|
|
|
int xoff = -radius;
|
|
|
|
int yoff = -radius;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
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++) {
|
2013-05-12 09:14:13 +00:00
|
|
|
float xy[2] = {x + xoff, y + yoff};
|
|
|
|
float len = len_v2(xy);
|
|
|
|
|
2015-06-10 13:32:11 +02:00
|
|
|
*m = (unsigned short)(65535.0f * BKE_brush_curve_strength_clamped(brush, len, radius));
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
/* create imbuf with brush color */
|
2014-07-21 12:02:05 +02:00
|
|
|
static ImBuf *brush_painter_imbuf_new(BrushPainter *painter, int size, float pressure, float distance)
|
2013-05-12 09:14:13 +00:00
|
|
|
{
|
|
|
|
Scene *scene = painter->scene;
|
|
|
|
Brush *brush = painter->brush;
|
|
|
|
|
2014-04-14 14:08:10 +06:00
|
|
|
const char *display_device = scene->display_settings.display_device;
|
|
|
|
struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device);
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
rctf tex_mapping = painter->tex_mapping;
|
|
|
|
struct ImagePool *pool = painter->pool;
|
|
|
|
|
|
|
|
bool use_color_correction = painter->cache.use_color_correction;
|
|
|
|
bool use_float = painter->cache.use_float;
|
|
|
|
bool is_texbrush = painter->cache.is_texbrush;
|
|
|
|
|
|
|
|
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) {
|
2014-07-21 12:02:05 +02:00
|
|
|
paint_brush_color_get(scene, brush, use_color_correction, painter->cache.invert, distance, pressure, brush_rgb, display);
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
|
|
|
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);
|
2013-12-03 22:13:15 +06:00
|
|
|
/* TODO(sergey): Support texture paint color space. */
|
|
|
|
if (!use_float) {
|
2014-05-09 00:12:18 +03:00
|
|
|
IMB_colormanagement_scene_linear_to_display_v3(rgba, display);
|
2013-12-03 22:13:15 +06:00
|
|
|
}
|
2015-02-19 12:37:35 +01:00
|
|
|
mul_v3_v3(rgba, brush_rgb);
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
copy_v3_v3(rgba, brush_rgb);
|
|
|
|
rgba[3] = 1.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2018-05-07 17:31:28 +02:00
|
|
|
dst[3] = unit_float_to_uchar_clamp(rgba[3]);
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-05-12 09:14:13 +00:00
|
|
|
|
|
|
|
return ibuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update rectangular section of the brush image */
|
|
|
|
static void brush_painter_imbuf_update(BrushPainter *painter, ImBuf *oldtexibuf,
|
|
|
|
int origx, int origy, int w, int h, int xt, int yt)
|
|
|
|
{
|
|
|
|
Scene *scene = painter->scene;
|
|
|
|
Brush *brush = painter->brush;
|
|
|
|
|
2014-04-14 14:08:10 +06:00
|
|
|
const char *display_device = scene->display_settings.display_device;
|
|
|
|
struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device);
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
rctf tex_mapping = painter->tex_mapping;
|
|
|
|
struct ImagePool *pool = painter->pool;
|
|
|
|
|
|
|
|
bool use_color_correction = painter->cache.use_color_correction;
|
|
|
|
bool use_float = painter->cache.use_float;
|
|
|
|
bool is_texbrush = painter->cache.is_texbrush;
|
|
|
|
bool use_texture_old = (oldtexibuf != NULL);
|
|
|
|
|
|
|
|
int x, y, thread = 0;
|
|
|
|
float brush_rgb[3];
|
|
|
|
|
|
|
|
ImBuf *ibuf = painter->cache.ibuf;
|
|
|
|
ImBuf *texibuf = painter->cache.texibuf;
|
|
|
|
|
|
|
|
/* get brush color */
|
|
|
|
if (brush->imagepaint_tool == PAINT_TOOL_DRAW) {
|
2014-07-21 12:02:05 +02:00
|
|
|
paint_brush_color_get(scene, brush, use_color_correction, painter->cache.invert, 0.0, 1.0, brush_rgb, display);
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
2013-01-01 21:23:12 +00:00
|
|
|
else {
|
2013-05-12 09:14:13 +00:00
|
|
|
brush_rgb[0] = 1.0f;
|
|
|
|
brush_rgb[1] = 1.0f;
|
|
|
|
brush_rgb[2] = 1.0f;
|
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
/* fill pixels */
|
2013-05-12 09:14:13 +00:00
|
|
|
for (y = origy; y < h; y++) {
|
|
|
|
for (x = origx; x < w; x++) {
|
|
|
|
/* sample texture and multiply with brush color */
|
|
|
|
float texco[3], rgba[4];
|
|
|
|
|
|
|
|
if (!use_texture_old) {
|
|
|
|
if (is_texbrush) {
|
|
|
|
brush_imbuf_tex_co(&tex_mapping, x, y, texco);
|
|
|
|
BKE_brush_sample_tex_3D(scene, brush, texco, rgba, thread, pool);
|
2013-12-03 22:13:15 +06:00
|
|
|
/* TODO(sergey): Support texture paint color space. */
|
|
|
|
if (!use_float) {
|
2014-05-09 00:12:18 +03:00
|
|
|
IMB_colormanagement_scene_linear_to_display_v3(rgba, display);
|
2013-12-03 22:13:15 +06:00
|
|
|
}
|
2015-02-19 12:37:35 +01:00
|
|
|
mul_v3_v3(rgba, brush_rgb);
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
else {
|
2013-05-12 09:14:13 +00:00
|
|
|
copy_v3_v3(rgba, brush_rgb);
|
|
|
|
rgba[3] = 1.0f;
|
|
|
|
}
|
|
|
|
}
|
2013-01-01 21:23:12 +00:00
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
if (use_float) {
|
|
|
|
/* handle float pixel */
|
|
|
|
float *bf = ibuf->rect_float + (y * ibuf->x + x) * 4;
|
|
|
|
float *tf = texibuf->rect_float + (y * texibuf->x + x) * 4;
|
|
|
|
|
|
|
|
/* read from old texture buffer */
|
|
|
|
if (use_texture_old) {
|
2014-04-27 00:22:49 +10:00
|
|
|
const float *otf = oldtexibuf->rect_float + ((y - origy + yt) * oldtexibuf->x + (x - origx + xt)) * 4;
|
2013-05-12 09:14:13 +00:00
|
|
|
copy_v4_v4(rgba, otf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write to new texture buffer */
|
|
|
|
copy_v4_v4(tf, rgba);
|
|
|
|
|
|
|
|
/* output premultiplied float image, mf was already premultiplied */
|
|
|
|
mul_v3_v3fl(bf, rgba, rgba[3]);
|
|
|
|
bf[3] = rgba[3];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
unsigned char crgba[4];
|
|
|
|
|
|
|
|
/* handle byte pixel */
|
|
|
|
unsigned char *b = (unsigned char *)ibuf->rect + (y * ibuf->x + x) * 4;
|
|
|
|
unsigned char *t = (unsigned char *)texibuf->rect + (y * texibuf->x + x) * 4;
|
|
|
|
|
|
|
|
/* read from old texture buffer */
|
|
|
|
if (use_texture_old) {
|
|
|
|
unsigned char *ot = (unsigned char *)oldtexibuf->rect + ((y - origy + yt) * oldtexibuf->x + (x - origx + xt)) * 4;
|
|
|
|
crgba[0] = ot[0];
|
|
|
|
crgba[1] = ot[1];
|
|
|
|
crgba[2] = ot[2];
|
|
|
|
crgba[3] = ot[3];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rgba_float_to_uchar(crgba, rgba);
|
|
|
|
|
|
|
|
/* write to new texture buffer */
|
|
|
|
t[0] = crgba[0];
|
|
|
|
t[1] = crgba[1];
|
|
|
|
t[2] = crgba[2];
|
|
|
|
t[3] = crgba[3];
|
|
|
|
|
|
|
|
/* write to brush image buffer */
|
|
|
|
b[0] = crgba[0];
|
|
|
|
b[1] = crgba[1];
|
|
|
|
b[2] = crgba[2];
|
|
|
|
b[3] = crgba[3];
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
/* 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 */
|
2014-07-21 12:02:05 +02:00
|
|
|
static void brush_painter_imbuf_partial_update(BrushPainter *painter, const float pos[2], int diameter)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
|
|
|
BrushPainterCache *cache = &painter->cache;
|
|
|
|
ImBuf *oldtexibuf, *ibuf;
|
|
|
|
int imbflag, destx, desty, srcx, srcy, w, h, x1, y1, x2, y2;
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
/* create brush image buffer if it didn't exist yet */
|
|
|
|
imbflag = (cache->use_float) ? IB_rectfloat : IB_rect;
|
2013-01-01 21:23:12 +00:00
|
|
|
if (!cache->ibuf)
|
|
|
|
cache->ibuf = IMB_allocImBuf(diameter, diameter, 32, imbflag);
|
|
|
|
ibuf = cache->ibuf;
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
/* create new texture image buffer with coordinates relative to old */
|
2013-01-01 21:23:12 +00:00
|
|
|
oldtexibuf = cache->texibuf;
|
|
|
|
cache->texibuf = IMB_allocImBuf(diameter, diameter, 32, imbflag);
|
2013-05-12 09:14:13 +00:00
|
|
|
|
2013-01-01 21:23:12 +00:00
|
|
|
if (oldtexibuf) {
|
|
|
|
srcx = srcy = 0;
|
|
|
|
w = oldtexibuf->x;
|
|
|
|
h = oldtexibuf->y;
|
2016-05-03 17:49:18 +10:00
|
|
|
destx = (int)floorf(painter->lastpaintpos[0]) - (int)floorf(pos[0]) + (diameter / 2 - w / 2);
|
|
|
|
desty = (int)floorf(painter->lastpaintpos[1]) - (int)floorf(pos[1]) + (diameter / 2 - h / 2);
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
IMB_rectclip(cache->texibuf, oldtexibuf, &destx, &desty, &srcx, &srcy, &w, &h);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
srcx = srcy = 0;
|
|
|
|
destx = desty = 0;
|
|
|
|
w = h = 0;
|
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-05-09 23:29:57 +03:00
|
|
|
x1 = min_ii(destx, ibuf->x);
|
|
|
|
y1 = min_ii(desty, ibuf->y);
|
2013-05-12 09:14:13 +00:00
|
|
|
x2 = min_ii(destx + w, ibuf->x);
|
|
|
|
y2 = min_ii(desty + h, ibuf->y);
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
/* blend existing texture in new position */
|
|
|
|
if ((x1 < x2) && (y1 < y2))
|
2013-05-12 09:14:13 +00:00
|
|
|
brush_painter_imbuf_update(painter, oldtexibuf, x1, y1, x2, y2, srcx, srcy);
|
2013-01-01 21:23:12 +00:00
|
|
|
|
|
|
|
if (oldtexibuf)
|
|
|
|
IMB_freeImBuf(oldtexibuf);
|
|
|
|
|
|
|
|
/* sample texture in new areas */
|
|
|
|
if ((0 < x1) && (0 < ibuf->y))
|
2013-05-12 09:14:13 +00:00
|
|
|
brush_painter_imbuf_update(painter, NULL, 0, 0, x1, ibuf->y, 0, 0);
|
2013-01-01 21:23:12 +00:00
|
|
|
if ((x2 < ibuf->x) && (0 < ibuf->y))
|
2013-05-12 09:14:13 +00:00
|
|
|
brush_painter_imbuf_update(painter, NULL, x2, 0, ibuf->x, ibuf->y, 0, 0);
|
2013-01-01 21:23:12 +00:00
|
|
|
if ((x1 < x2) && (0 < y1))
|
2013-05-12 09:14:13 +00:00
|
|
|
brush_painter_imbuf_update(painter, NULL, x1, 0, x2, y1, 0, 0);
|
2013-01-01 21:23:12 +00:00
|
|
|
if ((x1 < x2) && (y2 < ibuf->y))
|
2013-05-12 09:14:13 +00:00
|
|
|
brush_painter_imbuf_update(painter, NULL, x1, y2, x2, ibuf->y, 0, 0);
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
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)
|
2013-04-30 09:59:40 +00:00
|
|
|
{
|
2013-05-01 02:53:45 +00:00
|
|
|
float invw = 1.0f / (float)s->canvas->x;
|
|
|
|
float invh = 1.0f / (float)s->canvas->y;
|
2013-04-30 09:59:40 +00:00
|
|
|
int xmin, ymin, xmax, ymax;
|
|
|
|
int ipos[2];
|
|
|
|
|
|
|
|
/* find start coordinate of brush in canvas */
|
2014-07-21 12:02:05 +02:00
|
|
|
ipos[0] = (int)floorf((pos[0] - diameter / 2) + 1.0f);
|
|
|
|
ipos[1] = (int)floorf((pos[1] - diameter / 2) + 1.0f);
|
2013-04-30 09:59:40 +00:00
|
|
|
|
2013-05-15 21:03:57 +00:00
|
|
|
if (mapmode == MTEX_MAP_MODE_STENCIL) {
|
2013-04-30 09:59:40 +00:00
|
|
|
/* map from view coordinates of brush to region coordinates */
|
2014-04-21 16:47:16 +10:00
|
|
|
UI_view2d_view_to_region(s->v2d, ipos[0] * invw, ipos[1] * invh, &xmin, &ymin);
|
2014-07-21 12:02:05 +02:00
|
|
|
UI_view2d_view_to_region(s->v2d, (ipos[0] + diameter) * invw, (ipos[1] + diameter) * invh, &xmax, &ymax);
|
2013-04-30 09:59:40 +00:00
|
|
|
|
|
|
|
/* output mapping from brush ibuf x/y to region coordinates */
|
|
|
|
mapping->xmin = xmin;
|
|
|
|
mapping->ymin = ymin;
|
2014-07-21 12:02:05 +02:00
|
|
|
mapping->xmax = (xmax - xmin) / (float)diameter;
|
|
|
|
mapping->ymax = (ymax - ymin) / (float)diameter;
|
2013-04-30 09:59:40 +00:00
|
|
|
}
|
2013-05-12 09:14:13 +00:00
|
|
|
else if (mapmode == MTEX_MAP_MODE_3D) {
|
2013-04-30 09:59:40 +00:00
|
|
|
/* 3D mapping, just mapping to canvas 0..1 */
|
2013-05-15 21:03:57 +00:00
|
|
|
mapping->xmin = 2.0f * (ipos[0] * invw - 0.5f);
|
|
|
|
mapping->ymin = 2.0f * (ipos[1] * invh - 0.5f);
|
|
|
|
mapping->xmax = 2.0f * invw;
|
|
|
|
mapping->ymax = 2.0f * invh;
|
2013-04-30 09:59:40 +00:00
|
|
|
}
|
2013-05-15 21:03:57 +00:00
|
|
|
else if (ELEM(mapmode, MTEX_MAP_MODE_VIEW, MTEX_MAP_MODE_RANDOM)) {
|
|
|
|
/* view mapping */
|
2014-07-21 12:02:05 +02:00
|
|
|
mapping->xmin = mouse[0] - diameter * 0.5f + 0.5f;
|
|
|
|
mapping->ymin = mouse[1] - diameter * 0.5f + 0.5f;
|
2013-04-30 09:59:40 +00:00
|
|
|
mapping->xmax = 1.0f;
|
|
|
|
mapping->ymax = 1.0f;
|
|
|
|
}
|
2013-05-15 21:03:57 +00:00
|
|
|
else /* if (mapmode == MTEX_MAP_MODE_TILED) */ {
|
2016-05-03 17:49:18 +10:00
|
|
|
mapping->xmin = (int)(-diameter * 0.5) + (int)floorf(pos[0]) - (int)floorf(startpos[0]);
|
|
|
|
mapping->ymin = (int)(-diameter * 0.5) + (int)floorf(pos[1]) - (int)floorf(startpos[1]);
|
2013-05-15 21:03:57 +00:00
|
|
|
mapping->xmax = 1.0f;
|
|
|
|
mapping->ymax = 1.0f;
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
2013-04-30 09:59:40 +00:00
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
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)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
|
|
|
const Scene *scene = painter->scene;
|
2013-03-15 09:48:51 +00:00
|
|
|
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
|
2013-01-01 21:23:12 +00:00
|
|
|
Brush *brush = painter->brush;
|
|
|
|
BrushPainterCache *cache = &painter->cache;
|
2014-07-21 12:02:05 +02:00
|
|
|
const int diameter = 2 * size;
|
2013-05-12 09:14:13 +00:00
|
|
|
|
|
|
|
bool do_random = false;
|
|
|
|
bool do_partial_update = false;
|
2014-07-21 12:02:05 +02:00
|
|
|
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));
|
2013-05-12 09:14:13 +00:00
|
|
|
float tex_rotation = -brush->mtex.rot;
|
|
|
|
float mask_rotation = -brush->mask_mtex.rot;
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
painter->pool = BKE_image_pool_new();
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
/* determine how can update based on textures used */
|
|
|
|
if (painter->cache.is_texbrush) {
|
|
|
|
if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_VIEW) {
|
|
|
|
tex_rotation += ups->brush_rotation;
|
|
|
|
}
|
|
|
|
else if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_RANDOM)
|
|
|
|
do_random = true;
|
2014-07-21 12:02:05 +02:00
|
|
|
else if (!((brush->flag & BRUSH_ANCHORED) || update_color))
|
2013-05-12 09:14:13 +00:00
|
|
|
do_partial_update = true;
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
brush_painter_2d_tex_mapping(s, diameter, painter->startpaintpos, pos, mouse,
|
2013-05-12 09:14:13 +00:00
|
|
|
brush->mtex.brush_map_mode, &painter->tex_mapping);
|
|
|
|
}
|
2013-03-15 09:48:51 +00:00
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
if (painter->cache.is_maskbrush) {
|
2014-07-21 12:02:05 +02:00
|
|
|
bool renew_maxmask = false;
|
|
|
|
bool do_partial_update_mask = false;
|
|
|
|
/* invalidate case for all mapping modes */
|
2013-05-12 09:14:13 +00:00
|
|
|
if (brush->mask_mtex.brush_map_mode == MTEX_MAP_MODE_VIEW) {
|
2014-12-26 23:51:27 +01:00
|
|
|
mask_rotation += ups->brush_rotation_sec;
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
2014-07-21 12:02:05 +02:00
|
|
|
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;
|
|
|
|
}
|
2013-05-12 09:14:13 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
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;
|
|
|
|
}
|
2013-03-15 09:48:51 +00:00
|
|
|
}
|
2013-01-01 21:23:12 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
/* 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;
|
|
|
|
}
|
2013-04-30 09:59:40 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
cache->curve_mask = brush_painter_curve_mask_new(painter, diameter, size);
|
|
|
|
}
|
2013-04-30 09:59:40 +00:00
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
/* detect if we need to recreate image brush buffer */
|
2014-07-21 12:02:05 +02:00
|
|
|
if ((diameter != cache->lastdiameter) ||
|
|
|
|
(tex_rotation != cache->last_tex_rotation) ||
|
|
|
|
do_random ||
|
|
|
|
update_color)
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
|
|
|
if (cache->ibuf) {
|
|
|
|
IMB_freeImBuf(cache->ibuf);
|
|
|
|
cache->ibuf = NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
if (do_partial_update) {
|
2014-07-21 12:02:05 +02:00
|
|
|
/* do partial update of texture */
|
|
|
|
brush_painter_imbuf_partial_update(painter, pos, diameter);
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
2013-04-30 16:07:52 +00:00
|
|
|
else {
|
2014-07-21 12:02:05 +02:00
|
|
|
/* create brush from scratch */
|
|
|
|
cache->ibuf = brush_painter_imbuf_new(painter, diameter, pressure, distance);
|
2013-04-30 16:07:52 +00:00
|
|
|
}
|
2013-01-01 21:23:12 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
cache->lastdiameter = diameter;
|
2013-05-12 09:14:13 +00:00
|
|
|
cache->last_tex_rotation = tex_rotation;
|
2014-07-21 12:02:05 +02:00
|
|
|
cache->last_pressure = pressure;
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
2013-05-12 09:14:13 +00:00
|
|
|
else if (do_partial_update) {
|
|
|
|
/* do only partial update of texture */
|
2016-05-03 17:49:18 +10:00
|
|
|
int dx = (int)floorf(painter->lastpaintpos[0]) - (int)floorf(pos[0]);
|
|
|
|
int dy = (int)floorf(painter->lastpaintpos[1]) - (int)floorf(pos[1]);
|
2013-01-01 21:23:12 +00:00
|
|
|
|
2013-05-12 09:14:13 +00:00
|
|
|
if ((dx != 0) || (dy != 0)) {
|
2014-07-21 12:02:05 +02:00
|
|
|
brush_painter_imbuf_partial_update(painter, pos, diameter);
|
2013-05-12 09:14:13 +00:00
|
|
|
}
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
2013-04-30 09:59:40 +00:00
|
|
|
|
|
|
|
BKE_image_pool_free(painter->pool);
|
|
|
|
painter->pool = NULL;
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
/* keep these functions in sync */
|
2015-07-27 12:55:40 +02:00
|
|
|
static void paint_2d_ibuf_rgb_get(ImBuf *ibuf, int x, int y, float r_rgb[4])
|
2013-01-01 21:23:12 +00:00
|
|
|
{
|
2013-03-07 12:11:38 +00:00
|
|
|
if (ibuf->rect_float) {
|
2014-04-27 00:22:49 +10:00
|
|
|
const float *rrgbf = ibuf->rect_float + (ibuf->x * y + x) * 4;
|
2013-05-22 20:06:50 +00:00
|
|
|
copy_v4_v4(r_rgb, rrgbf);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
else {
|
2013-05-22 20:06:50 +00:00
|
|
|
unsigned char *rrgb = (unsigned char *)ibuf->rect + (ibuf->x * y + x) * 4;
|
|
|
|
straight_uchar_to_premul_float(r_rgb, rrgb);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
2014-02-03 18:55:59 +11:00
|
|
|
static void paint_2d_ibuf_rgb_set(ImBuf *ibuf, int x, int y, const bool is_torus, const float rgb[4])
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
|
|
|
if (is_torus) {
|
|
|
|
x %= ibuf->x;
|
|
|
|
if (x < 0) x += ibuf->x;
|
|
|
|
y %= ibuf->y;
|
|
|
|
if (y < 0) y += ibuf->y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ibuf->rect_float) {
|
|
|
|
float *rrgbf = ibuf->rect_float + (ibuf->x * y + x) * 4;
|
2014-01-12 22:27:55 +11:00
|
|
|
float map_alpha = (rgb[3] == 0.0f) ? rrgbf[3] : rrgbf[3] / rgb[3];
|
2013-05-22 20:06:50 +00:00
|
|
|
|
|
|
|
mul_v3_v3fl(rrgbf, rgb, map_alpha);
|
2017-07-21 00:16:59 +02:00
|
|
|
rrgbf[3] = rgb[3];
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
else {
|
2013-05-22 20:06:50 +00:00
|
|
|
unsigned char straight[4];
|
|
|
|
unsigned char *rrgb = (unsigned char *)ibuf->rect + (ibuf->x * y + x) * 4;
|
|
|
|
|
|
|
|
premul_float_to_straight_uchar(straight, rgb);
|
|
|
|
rrgb[0] = straight[0];
|
|
|
|
rrgb[1] = straight[1];
|
|
|
|
rrgb[2] = straight[2];
|
2017-07-21 00:16:59 +02:00
|
|
|
rrgb[3] = straight[3];
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
static void paint_2d_ibuf_tile_convert(ImBuf *ibuf, int *x, int *y, short tile)
|
|
|
|
{
|
|
|
|
if (tile & PAINT_TILE_X) {
|
|
|
|
*x %= ibuf->x;
|
|
|
|
if (*x < 0) *x += ibuf->x;
|
|
|
|
}
|
|
|
|
if (tile & PAINT_TILE_Y) {
|
|
|
|
*y %= ibuf->y;
|
|
|
|
if (*y < 0) *y += ibuf->y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static float paint_2d_ibuf_add_if(ImBuf *ibuf, int x, int y, float *outrgb, short tile, float w)
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
2013-05-22 20:06:50 +00:00
|
|
|
float inrgb[4];
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
if (tile) paint_2d_ibuf_tile_convert(ibuf, &x, &y, tile);
|
|
|
|
/* need to also do clipping here always since tiled coordinates
|
|
|
|
* are not always within bounds */
|
|
|
|
if (x < ibuf->x && x >= 0 && y < ibuf->y && y >= 0) {
|
|
|
|
paint_2d_ibuf_rgb_get(ibuf, x, y, inrgb);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
2015-07-27 12:55:40 +02:00
|
|
|
else return 0;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
mul_v4_fl(inrgb, w);
|
2013-05-22 20:06:50 +00:00
|
|
|
add_v4_v4(outrgb, inrgb);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
return w;
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
static void paint_2d_lift_soften(ImagePaintState *s, ImBuf *ibuf, ImBuf *ibufb, int *pos, const short tile)
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
2014-07-21 12:02:05 +02:00
|
|
|
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;
|
2013-03-07 12:11:38 +00:00
|
|
|
int out_off[2], in_off[2], dim[2];
|
2014-07-21 12:02:05 +02:00
|
|
|
int diff_pos[2];
|
2013-05-22 20:06:50 +00:00
|
|
|
float outrgb[4];
|
2014-07-21 12:02:05 +02:00
|
|
|
float rgba[4];
|
|
|
|
BlurKernel *kernel = s->blurkernel;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
if (!tile) {
|
2013-03-07 12:11:38 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
/* find offset inside mask buffers to sample them */
|
|
|
|
sub_v2_v2v2_int(diff_pos, out_off, in_off);
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
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;
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
count = 0.0;
|
2015-07-27 12:55:40 +02:00
|
|
|
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);
|
|
|
|
}
|
2014-07-21 12:02:05 +02:00
|
|
|
zero_v4(outrgb);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
for (yk = 0; yk < kernel->side; yk++) {
|
|
|
|
for (xk = 0; xk < kernel->side; xk++) {
|
2014-08-27 14:44:36 +02:00
|
|
|
count += paint_2d_ibuf_add_if(ibuf, xi + xk - kernel->pixel_len,
|
2015-07-27 12:55:40 +02:00
|
|
|
yi + yk - kernel->pixel_len, outrgb, tile,
|
2014-07-21 12:02:05 +02:00
|
|
|
kernel->wdata[xk + yk * kernel->side]);
|
|
|
|
}
|
|
|
|
}
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
if (count > 0.0f) {
|
|
|
|
mul_v4_fl(outrgb, 1.0f / (float)count);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
if (sharpen) {
|
|
|
|
/* subtract blurred image from normal image gives high pass filter */
|
|
|
|
sub_v3_v3v3(outrgb, rgba, outrgb);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
/* now rgba_ub contains the edge result, but this should be converted to luminance to avoid
|
2014-08-02 16:53:52 +10:00
|
|
|
* colored speckles appearing in final image, and also to check for threshold */
|
2015-03-17 15:20:33 +01:00
|
|
|
outrgb[0] = outrgb[1] = outrgb[2] = IMB_colormanagement_get_luminance(outrgb);
|
2014-07-21 12:02:05 +02:00
|
|
|
if (fabsf(outrgb[0]) > threshold) {
|
|
|
|
float mask = BKE_brush_alpha_get(s->scene, s->brush);
|
|
|
|
float alpha = rgba[3];
|
|
|
|
rgba[3] = outrgb[3] = mask;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
/* 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);
|
2013-03-07 12:11:38 +00:00
|
|
|
/* write into brush buffer */
|
|
|
|
xo = out_off[0] + x;
|
|
|
|
yo = out_off[1] + y;
|
|
|
|
paint_2d_ibuf_rgb_set(ibufb, xo, yo, 0, outrgb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void paint_2d_set_region(ImagePaintRegion *region, int destx, int desty, int srcx, int srcy, int width, int height)
|
|
|
|
{
|
|
|
|
region->destx = destx;
|
|
|
|
region->desty = desty;
|
|
|
|
region->srcx = srcx;
|
|
|
|
region->srcy = srcy;
|
|
|
|
region->width = width;
|
|
|
|
region->height = height;
|
|
|
|
}
|
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
static int paint_2d_torus_split_region(ImagePaintRegion region[4], ImBuf *dbuf, ImBuf *sbuf, short tile)
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
|
|
|
int destx = region->destx;
|
|
|
|
int desty = region->desty;
|
|
|
|
int srcx = region->srcx;
|
|
|
|
int srcy = region->srcy;
|
|
|
|
int width = region->width;
|
|
|
|
int height = region->height;
|
|
|
|
int origw, origh, w, h, tot = 0;
|
|
|
|
|
|
|
|
/* convert destination and source coordinates to be within image */
|
2015-07-27 12:55:40 +02:00
|
|
|
if (tile & PAINT_TILE_X) {
|
|
|
|
destx = destx % dbuf->x;
|
|
|
|
if (destx < 0) destx += dbuf->x;
|
|
|
|
srcx = srcx % sbuf->x;
|
|
|
|
if (srcx < 0) srcx += sbuf->x;
|
|
|
|
}
|
|
|
|
if (tile & PAINT_TILE_Y) {
|
|
|
|
desty = desty % dbuf->y;
|
|
|
|
if (desty < 0) desty += dbuf->y;
|
|
|
|
srcy = srcy % sbuf->y;
|
|
|
|
if (srcy < 0) srcy += sbuf->y;
|
|
|
|
}
|
2013-03-07 12:11:38 +00:00
|
|
|
/* clip width of blending area to destination imbuf, to avoid writing the
|
|
|
|
* same pixel twice */
|
|
|
|
origw = w = (width > dbuf->x) ? dbuf->x : width;
|
|
|
|
origh = h = (height > dbuf->y) ? dbuf->y : height;
|
|
|
|
|
|
|
|
/* clip within image */
|
|
|
|
IMB_rectclip(dbuf, sbuf, &destx, &desty, &srcx, &srcy, &w, &h);
|
|
|
|
paint_2d_set_region(®ion[tot++], destx, desty, srcx, srcy, w, h);
|
|
|
|
|
|
|
|
/* do 3 other rects if needed */
|
2015-07-27 12:55:40 +02:00
|
|
|
if ((tile & PAINT_TILE_X) && w < origw)
|
2013-03-07 12:11:38 +00:00
|
|
|
paint_2d_set_region(®ion[tot++], (destx + w) % dbuf->x, desty, (srcx + w) % sbuf->x, srcy, origw - w, h);
|
2015-07-27 12:55:40 +02:00
|
|
|
if ((tile & PAINT_TILE_Y) && h < origh)
|
2013-03-07 12:11:38 +00:00
|
|
|
paint_2d_set_region(®ion[tot++], destx, (desty + h) % dbuf->y, srcx, (srcy + h) % sbuf->y, w, origh - h);
|
2015-07-27 12:55:40 +02:00
|
|
|
if ((tile & PAINT_TILE_X) && (tile & PAINT_TILE_Y) && (w < origw) && (h < origh))
|
2013-03-07 12:11:38 +00:00
|
|
|
paint_2d_set_region(®ion[tot++], (destx + w) % dbuf->x, (desty + h) % dbuf->y, (srcx + w) % sbuf->x, (srcy + h) % sbuf->y, origw - w, origh - h);
|
|
|
|
|
|
|
|
return tot;
|
|
|
|
}
|
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
static void paint_2d_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos, short tile)
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
|
|
|
ImagePaintRegion region[4];
|
|
|
|
int a, tot;
|
|
|
|
|
|
|
|
paint_2d_set_region(region, 0, 0, pos[0], pos[1], ibufb->x, ibufb->y);
|
2015-07-27 12:55:40 +02:00
|
|
|
tot = paint_2d_torus_split_region(region, ibufb, ibuf, tile);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
for (a = 0; a < tot; a++)
|
2014-07-21 12:02:05 +02:00
|
|
|
IMB_rectblend(ibufb, ibufb, ibuf, NULL, NULL, NULL, 0, region[a].destx, region[a].desty,
|
2013-04-30 06:07:42 +00:00
|
|
|
region[a].destx, region[a].desty,
|
2013-03-07 12:11:38 +00:00
|
|
|
region[a].srcx, region[a].srcy,
|
2017-07-21 00:16:59 +02:00
|
|
|
region[a].width, region[a].height, IMB_BLEND_COPY, false);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static ImBuf *paint_2d_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos)
|
|
|
|
{
|
|
|
|
/* note: allocImbuf returns zero'd memory, so regions outside image will
|
|
|
|
* have zero alpha, and hence not be blended onto the image */
|
|
|
|
int w = ibufb->x, h = ibufb->y, destx = 0, desty = 0, srcx = pos[0], srcy = pos[1];
|
|
|
|
ImBuf *clonebuf = IMB_allocImBuf(w, h, ibufb->planes, ibufb->flags);
|
|
|
|
|
|
|
|
IMB_rectclip(clonebuf, ibuf, &destx, &desty, &srcx, &srcy, &w, &h);
|
2014-07-21 12:02:05 +02:00
|
|
|
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);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
return clonebuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void paint_2d_convert_brushco(ImBuf *ibufb, const float pos[2], int ipos[2])
|
|
|
|
{
|
2015-04-09 12:18:51 +02:00
|
|
|
ipos[0] = (int)floorf((pos[0] - ibufb->x / 2));
|
|
|
|
ipos[1] = (int)floorf((pos[1] - ibufb->y / 2));
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
|
2016-05-06 11:48:07 +02:00
|
|
|
static void paint_2d_do_making_brush(ImagePaintState *s,
|
|
|
|
ImagePaintRegion *region,
|
|
|
|
unsigned short *curveb,
|
|
|
|
unsigned short *texmaskb,
|
|
|
|
ImBuf *frombuf,
|
|
|
|
float mask_max,
|
|
|
|
short blend,
|
|
|
|
int tilex, int tiley,
|
|
|
|
int tilew, int tileh)
|
|
|
|
{
|
|
|
|
ImBuf tmpbuf;
|
|
|
|
IMB_initImBuf(&tmpbuf, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, 0);
|
|
|
|
|
2018-03-19 14:17:59 +01:00
|
|
|
ListBase *undo_tiles = ED_image_undo_get_tiles();
|
|
|
|
|
2016-05-06 11:48:07 +02:00
|
|
|
for (int ty = tiley; ty <= tileh; ty++) {
|
|
|
|
for (int tx = tilex; tx <= tilew; tx++) {
|
|
|
|
/* retrieve original pixels + mask from undo buffer */
|
|
|
|
unsigned short *mask;
|
|
|
|
int origx = region->destx - tx * IMAPAINT_TILE_SIZE;
|
|
|
|
int origy = region->desty - ty * IMAPAINT_TILE_SIZE;
|
|
|
|
|
|
|
|
if (s->canvas->rect_float)
|
2018-03-19 14:17:59 +01:00
|
|
|
tmpbuf.rect_float = image_undo_find_tile(undo_tiles, s->image, s->canvas, tx, ty, &mask, false);
|
2016-05-06 11:48:07 +02:00
|
|
|
else
|
2018-03-19 14:17:59 +01:00
|
|
|
tmpbuf.rect = image_undo_find_tile(undo_tiles, s->image, s->canvas, tx, ty, &mask, false);
|
2016-05-06 11:48:07 +02:00
|
|
|
|
|
|
|
IMB_rectblend(s->canvas, &tmpbuf, frombuf, mask,
|
|
|
|
curveb, texmaskb, mask_max,
|
|
|
|
region->destx, region->desty,
|
|
|
|
origx, origy,
|
|
|
|
region->srcx, region->srcy,
|
|
|
|
region->width, region->height,
|
|
|
|
blend, ((s->brush->flag & BRUSH_ACCUMULATE) != 0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct Paint2DForeachData {
|
|
|
|
ImagePaintState *s;
|
|
|
|
ImagePaintRegion *region;
|
|
|
|
unsigned short *curveb;
|
|
|
|
unsigned short *texmaskb;
|
|
|
|
ImBuf *frombuf;
|
|
|
|
float mask_max;
|
|
|
|
short blend;
|
|
|
|
int tilex;
|
|
|
|
int tilew;
|
|
|
|
} Paint2DForeachData;
|
|
|
|
|
2018-01-10 12:49:51 +01:00
|
|
|
static void paint_2d_op_foreach_do(
|
|
|
|
void *__restrict data_v,
|
|
|
|
const int iter,
|
|
|
|
const ParallelRangeTLS *__restrict UNUSED(tls))
|
2016-05-06 11:48:07 +02:00
|
|
|
{
|
|
|
|
Paint2DForeachData *data = (Paint2DForeachData *)data_v;
|
|
|
|
paint_2d_do_making_brush(data->s, data->region, data->curveb,
|
|
|
|
data->texmaskb, data->frombuf, data->mask_max,
|
|
|
|
data->blend,
|
|
|
|
data->tilex, iter,
|
|
|
|
data->tilew, iter);
|
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *curveb, unsigned short *texmaskb, const float lastpos[2], const float pos[2])
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
|
|
|
ImagePaintState *s = ((ImagePaintState *)state);
|
2014-07-21 12:02:05 +02:00
|
|
|
ImBuf *clonebuf = NULL, *frombuf;
|
2013-03-07 12:11:38 +00:00
|
|
|
ImagePaintRegion region[4];
|
2015-07-27 12:55:40 +02:00
|
|
|
short tile = s->symmetry & (PAINT_TILE_X | PAINT_TILE_Y);
|
2013-03-07 12:11:38 +00:00
|
|
|
short blend = s->blend;
|
2014-04-27 00:22:49 +10:00
|
|
|
const float *offset = s->brush->clone.offset;
|
2013-03-07 12:11:38 +00:00
|
|
|
float liftpos[2];
|
2014-07-21 12:02:05 +02:00
|
|
|
float mask_max = BKE_brush_alpha_get(s->scene, s->brush);
|
2013-03-07 12:11:38 +00:00
|
|
|
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) {
|
2015-07-27 12:55:40 +02:00
|
|
|
paint_2d_lift_soften(s, s->canvas, ibufb, bpos, tile);
|
2017-07-21 00:16:59 +02:00
|
|
|
blend = IMB_BLEND_INTERPOLATE;
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
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);
|
2015-07-27 12:55:40 +02:00
|
|
|
paint_2d_lift_smear(s->canvas, ibufb, blastpos, tile);
|
2017-07-21 00:16:59 +02:00
|
|
|
blend = IMB_BLEND_INTERPOLATE;
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2015-07-27 12:55:40 +02:00
|
|
|
if (tile) {
|
2013-03-07 12:11:38 +00:00
|
|
|
paint_2d_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y);
|
2015-07-27 12:55:40 +02:00
|
|
|
tot = paint_2d_torus_split_region(region, s->canvas, frombuf, tile);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
paint_2d_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y);
|
|
|
|
tot = 1;
|
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
/* blend into canvas */
|
|
|
|
for (a = 0; a < tot; a++) {
|
2013-12-05 22:28:14 +02:00
|
|
|
ED_imapaint_dirty_region(s->image, s->canvas,
|
2016-07-28 14:17:35 +02:00
|
|
|
region[a].destx, region[a].desty,
|
|
|
|
region[a].width, region[a].height, true);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-04-30 06:07:42 +00:00
|
|
|
if (s->do_masking) {
|
|
|
|
/* masking, find original pixels tiles from undo buffer to composite over */
|
2016-05-06 11:48:07 +02:00
|
|
|
int tilex, tiley, tilew, tileh;
|
2013-04-30 06:07:42 +00:00
|
|
|
|
|
|
|
imapaint_region_tiles(s->canvas, region[a].destx, region[a].desty,
|
|
|
|
region[a].width, region[a].height,
|
2013-05-01 02:53:45 +00:00
|
|
|
&tilex, &tiley, &tilew, &tileh);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
2016-05-06 11:48:07 +02:00
|
|
|
if (tiley == tileh) {
|
|
|
|
paint_2d_do_making_brush(s, ®ion[a], curveb, texmaskb, frombuf,
|
|
|
|
mask_max, blend, tilex, tiley, tilew, tileh);
|
2013-04-30 06:07:42 +00:00
|
|
|
}
|
2016-05-06 11:48:07 +02:00
|
|
|
else {
|
|
|
|
Paint2DForeachData data;
|
|
|
|
data.s = s;
|
|
|
|
data.region = ®ion[a];
|
|
|
|
data.curveb = curveb;
|
|
|
|
data.texmaskb = texmaskb;
|
|
|
|
data.frombuf = frombuf;
|
|
|
|
data.mask_max = mask_max;
|
|
|
|
data.blend = blend;
|
|
|
|
data.tilex = tilex;
|
|
|
|
data.tilew = tilew;
|
2018-01-08 11:35:48 +01:00
|
|
|
|
|
|
|
ParallelRangeSettings settings;
|
|
|
|
BLI_parallel_range_settings_defaults(&settings);
|
2016-05-06 11:48:07 +02:00
|
|
|
BLI_task_parallel_range(tiley, tileh + 1, &data,
|
|
|
|
paint_2d_op_foreach_do,
|
2018-01-08 11:35:48 +01:00
|
|
|
&settings);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
2016-05-06 11:48:07 +02:00
|
|
|
}
|
2013-04-30 06:07:42 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* no masking, composite brush directly onto canvas */
|
2016-05-06 11:48:07 +02:00
|
|
|
IMB_rectblend_threaded(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, false);
|
2013-04-30 06:07:42 +00:00
|
|
|
}
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (clonebuf) IMB_freeImBuf(clonebuf);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int paint_2d_canvas_set(ImagePaintState *s, Image *ima)
|
|
|
|
{
|
|
|
|
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, s->sima ? &s->sima->iuser : NULL, NULL);
|
|
|
|
|
|
|
|
/* verify that we can paint and set canvas */
|
|
|
|
if (ima == NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
2015-04-06 10:40:12 -03:00
|
|
|
else if (BKE_image_has_packedfile(ima) && ima->rr) {
|
2013-03-07 12:11:38 +00:00
|
|
|
s->warnpackedfile = ima->id.name + 2;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (ibuf && ibuf->channels != 4) {
|
|
|
|
s->warnmultifile = ima->id.name + 2;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (!ibuf || !(ibuf->rect || ibuf->rect_float))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
s->image = ima;
|
|
|
|
s->canvas = ibuf;
|
|
|
|
|
|
|
|
/* set clone canvas */
|
|
|
|
if (s->tool == PAINT_TOOL_CLONE) {
|
|
|
|
ima = s->brush->clone.image;
|
|
|
|
ibuf = BKE_image_acquire_ibuf(ima, s->sima ? &s->sima->iuser : NULL, NULL);
|
|
|
|
|
|
|
|
if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) {
|
|
|
|
BKE_image_release_ibuf(ima, ibuf, NULL);
|
|
|
|
BKE_image_release_ibuf(s->image, s->canvas, NULL);
|
|
|
|
return 0;
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
s->clonecanvas = ibuf;
|
|
|
|
|
|
|
|
/* temporarily add float rect for cloning */
|
|
|
|
if (s->canvas->rect_float && !s->clonecanvas->rect_float) {
|
|
|
|
IMB_float_from_rect(s->clonecanvas);
|
|
|
|
}
|
|
|
|
else if (!s->canvas->rect_float && !s->clonecanvas->rect)
|
|
|
|
IMB_rect_from_float(s->clonecanvas);
|
|
|
|
}
|
|
|
|
|
2013-04-30 06:07:42 +00:00
|
|
|
/* set masking */
|
2014-07-21 12:02:05 +02:00
|
|
|
s->do_masking = paint_use_opacity_masking(s->brush);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2013-04-30 06:07:42 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
if (s->blurkernel) {
|
|
|
|
paint_delete_blur_kernel(s->blurkernel);
|
|
|
|
MEM_freeN(s->blurkernel);
|
|
|
|
}
|
|
|
|
|
|
|
|
image_undo_remove_masks();
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
|
2014-08-12 14:20:06 +02:00
|
|
|
void paint_2d_stroke(void *ps, const float prev_mval[2], const float mval[2], const bool eraser, float pressure, float distance, float size)
|
2013-03-07 12:11:38 +00:00
|
|
|
{
|
2013-03-10 02:30:53 +00:00
|
|
|
float newuv[2], olduv[2];
|
|
|
|
ImagePaintState *s = ps;
|
|
|
|
BrushPainter *painter = s->painter;
|
|
|
|
ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, s->sima ? &s->sima->iuser : NULL, NULL);
|
2013-03-17 10:15:06 +00:00
|
|
|
const bool is_data = (ibuf && ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
if (!ibuf)
|
2013-05-15 14:37:05 +00:00
|
|
|
return;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2013-03-10 02:30:53 +00:00
|
|
|
s->blend = s->brush->blend;
|
|
|
|
if (eraser)
|
|
|
|
s->blend = IMB_BLEND_ERASE_ALPHA;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2013-03-10 02:30:53 +00:00
|
|
|
UI_view2d_region_to_view(s->v2d, mval[0], mval[1], &newuv[0], &newuv[1]);
|
|
|
|
UI_view2d_region_to_view(s->v2d, prev_mval[0], prev_mval[1], &olduv[0], &olduv[1]);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2013-03-10 02:30:53 +00:00
|
|
|
newuv[0] *= ibuf->x;
|
|
|
|
newuv[1] *= ibuf->y;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2013-03-10 02:30:53 +00:00
|
|
|
olduv[0] *= ibuf->x;
|
|
|
|
olduv[1] *= ibuf->y;
|
|
|
|
|
|
|
|
if (painter->firsttouch) {
|
2013-04-30 22:14:19 +00:00
|
|
|
float startuv[2];
|
|
|
|
|
|
|
|
UI_view2d_region_to_view(s->v2d, 0, 0, &startuv[0], &startuv[1]);
|
|
|
|
|
2013-03-10 02:30:53 +00:00
|
|
|
/* paint exactly once on first touch */
|
2013-05-01 02:53:45 +00:00
|
|
|
painter->startpaintpos[0] = startuv[0] * ibuf->x;
|
|
|
|
painter->startpaintpos[1] = startuv[1] * ibuf->y;
|
2013-03-10 02:30:53 +00:00
|
|
|
|
|
|
|
painter->firsttouch = 0;
|
|
|
|
copy_v2_v2(painter->lastpaintpos, newuv);
|
2013-03-11 16:23:33 +00:00
|
|
|
}
|
|
|
|
else {
|
2013-03-10 02:30:53 +00:00
|
|
|
copy_v2_v2(painter->lastpaintpos, olduv);
|
|
|
|
}
|
2013-05-12 09:14:13 +00:00
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
/* 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
|
|
|
|
*/
|
2014-07-21 12:02:05 +02:00
|
|
|
brush_painter_2d_require_imbuf(painter, (ibuf->rect_float != NULL), !is_data);
|
2013-03-10 02:30:53 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
brush_painter_2d_refresh_cache(s, painter, newuv, mval, pressure, distance, size);
|
2013-03-10 02:30:53 +00:00
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
if (paint_2d_op(s, painter->cache.ibuf, painter->cache.curve_mask, painter->cache.tex_mask, olduv, newuv))
|
2013-05-15 14:37:05 +00:00
|
|
|
s->need_redraw = true;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
2013-05-15 14:37:05 +00:00
|
|
|
BKE_image_release_ibuf(s->image, ibuf, NULL);
|
2013-01-01 21:23:12 +00:00
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode)
|
2013-03-08 04:00:06 +00:00
|
|
|
{
|
2013-03-07 12:11:38 +00:00
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
ToolSettings *settings = scene->toolsettings;
|
Paint refactoring commit, non-disruptive (in theory :p)
* Fix precision overflow issue with overlay previews,
* Expose alpha mask mapping to UI (still not functional but coming soon).
* More overlay refactoring:
Overlay now does minimal checking for texture refresh.
Instead, we now have invalidation flags to set an aspect of the brush
overlay as invalid. This is necessary because this way we will be able to
separate and preview different brush attributes on the overlays, using
different textures:
These attributes/aspects are:
Primary texture (main texture for sculpt, vertex, imapaint)
Secondary texture (mask/alpha texture for imapaint)
Cursor texture (cursor texture. It involves brush strength and curves)
Modified the relevant RNA property update functions and C update callback
functions to call the relevant cursor invalidation functions instead
of checking every frame for multiple properties.
Properties that affect this are:
Image changes, if image is used by current brush,
Texture slot changes, similarly
Curve changes,
Object mode change invalidates the cursor
Paint tool change invalidates the cursor.
These changes give slightly more invalidation cases than simply
comparing the relevant properties each frame, but these do not occur in
performance critical moments and it's a much more elegant system than
adding more variables to check per frame each time we add something on
the system.
2013-04-12 17:21:31 +00:00
|
|
|
Brush *brush = BKE_paint_brush(&settings->imapaint.paint);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
ImagePaintState *s = MEM_callocN(sizeof(ImagePaintState), "ImagePaintState");
|
|
|
|
|
|
|
|
s->sima = CTX_wm_space_image(C);
|
|
|
|
s->v2d = &CTX_wm_region(C)->v2d;
|
|
|
|
s->scene = scene;
|
|
|
|
s->screen = CTX_wm_screen(C);
|
|
|
|
|
|
|
|
s->brush = brush;
|
|
|
|
s->tool = brush->imagepaint_tool;
|
|
|
|
s->blend = brush->blend;
|
|
|
|
|
|
|
|
s->image = s->sima->image;
|
2015-07-27 12:55:40 +02:00
|
|
|
s->symmetry = settings->imapaint.paint.symmetry_flags;
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
if (!paint_2d_canvas_set(s, s->image)) {
|
|
|
|
if (s->warnmultifile)
|
|
|
|
BKE_report(op->reports, RPT_WARNING, "Image requires 4 color channels to paint");
|
|
|
|
if (s->warnpackedfile)
|
|
|
|
BKE_report(op->reports, RPT_WARNING, "Packed MultiLayer files cannot be painted");
|
|
|
|
|
|
|
|
MEM_freeN(s);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-07-21 12:02:05 +02:00
|
|
|
if (brush->imagepaint_tool == PAINT_TOOL_SOFTEN) {
|
2014-08-27 15:07:57 +02:00
|
|
|
s->blurkernel = paint_new_blur_kernel(brush, false);
|
2014-07-21 12:02:05 +02:00
|
|
|
}
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
paint_brush_init_tex(s->brush);
|
|
|
|
|
|
|
|
/* create painter */
|
2014-07-21 12:02:05 +02:00
|
|
|
s->painter = brush_painter_2d_new(scene, s->brush, mode == BRUSH_STROKE_INVERT);
|
2013-03-07 12:11:38 +00:00
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2013-05-15 14:37:05 +00:00
|
|
|
void paint_2d_redraw(const bContext *C, void *ps, bool final)
|
2013-03-08 04:00:06 +00:00
|
|
|
{
|
2013-03-07 12:11:38 +00:00
|
|
|
ImagePaintState *s = ps;
|
|
|
|
|
2013-05-15 14:37:05 +00:00
|
|
|
if (s->need_redraw) {
|
|
|
|
ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, s->sima ? &s->sima->iuser : NULL, NULL);
|
|
|
|
|
|
|
|
imapaint_image_update(s->sima, s->image, ibuf, false);
|
2013-12-05 22:28:14 +02:00
|
|
|
ED_imapaint_clear_partial_redraw();
|
2013-05-15 14:37:05 +00:00
|
|
|
|
|
|
|
BKE_image_release_ibuf(s->image, ibuf, NULL);
|
|
|
|
|
|
|
|
s->need_redraw = false;
|
|
|
|
}
|
|
|
|
else if (!final) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-03-07 12:11:38 +00:00
|
|
|
if (final) {
|
|
|
|
if (s->image && !(s->sima && s->sima->lock))
|
|
|
|
GPU_free_image(s->image);
|
|
|
|
|
|
|
|
/* compositor listener deals with updating */
|
|
|
|
WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, s->image);
|
2014-07-21 12:02:05 +02:00
|
|
|
DAG_id_tag_update(&s->image->id, 0);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!s->sima || !s->sima->lock)
|
|
|
|
ED_region_tag_redraw(CTX_wm_region(C));
|
|
|
|
else
|
2013-05-15 14:37:05 +00:00
|
|
|
WM_event_add_notifier(C, NC_IMAGE | NA_PAINTING, s->image);
|
2013-03-07 12:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-08 04:00:06 +00:00
|
|
|
void paint_2d_stroke_done(void *ps)
|
|
|
|
{
|
2013-03-07 12:11:38 +00:00
|
|
|
ImagePaintState *s = ps;
|
|
|
|
|
|
|
|
paint_2d_canvas_free(s);
|
|
|
|
brush_painter_2d_free(s->painter);
|
|
|
|
paint_brush_exit_tex(s->brush);
|
|
|
|
|
|
|
|
MEM_freeN(s);
|
|
|
|
}
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2015-04-30 14:24:05 +02:00
|
|
|
size_t coordinate;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
if (x_px >= ibuf->x || x_px < 0 || y_px >= ibuf->y || y_px < 0)
|
|
|
|
return;
|
|
|
|
|
2015-04-30 14:24:05 +02:00
|
|
|
coordinate = ((size_t)y_px) * ibuf->x + x_px;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2015-04-30 13:52:25 +02:00
|
|
|
size_t coordinate;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
if (x_px >= ibuf->x || x_px < 0 || y_px >= ibuf->y || y_px < 0)
|
|
|
|
return;
|
|
|
|
|
2015-04-30 13:52:25 +02:00
|
|
|
coordinate = ((size_t)y_px) * ibuf->x + x_px;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
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 */
|
2015-04-30 13:52:25 +02:00
|
|
|
ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y, false);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
if (do_float) {
|
2014-07-24 15:18:49 +02:00
|
|
|
for (x_px = 0; x_px < ibuf->x; x_px++) {
|
2014-07-21 12:02:05 +02:00
|
|
|
for (y_px = 0; y_px < ibuf->y; y_px++) {
|
2015-04-30 13:52:25 +02:00
|
|
|
blend_color_mix_float(ibuf->rect_float + 4 * (((size_t)y_px) * ibuf->x + x_px),
|
|
|
|
ibuf->rect_float + 4 * (((size_t)y_px) * ibuf->x + x_px), color_f);
|
2014-07-21 12:02:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2014-07-24 15:18:49 +02:00
|
|
|
for (x_px = 0; x_px < ibuf->x; x_px++) {
|
2014-07-21 12:02:05 +02:00
|
|
|
for (y_px = 0; y_px < ibuf->y; y_px++) {
|
2015-04-30 13:52:25 +02:00
|
|
|
blend_color_mix_byte((unsigned char *)(ibuf->rect + ((size_t)y_px) * ibuf->x + x_px),
|
|
|
|
(unsigned char *)(ibuf->rect + ((size_t)y_px) * ibuf->x + x_px), (unsigned char *)&color_b);
|
2014-07-21 12:02:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
2015-04-30 13:52:25 +02:00
|
|
|
size_t coordinate;
|
2014-07-21 12:02:05 +02:00
|
|
|
int width = ibuf->x;
|
|
|
|
float image_init[2];
|
|
|
|
int minx = ibuf->x, miny = ibuf->y, maxx = 0, maxy = 0;
|
|
|
|
float pixel_color[4];
|
2016-11-18 15:32:50 +01:00
|
|
|
/* We are comparing to sum of three squared values (assumed in range [0,1]), so need to multiply... */
|
|
|
|
float threshold_sq = br->fill_threshold * br->fill_threshold * 3;
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
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 */
|
2015-04-30 13:52:25 +02:00
|
|
|
ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y, false);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
2015-04-30 13:52:25 +02:00
|
|
|
stack = BLI_stack_new(sizeof(size_t), __func__);
|
|
|
|
touched = BLI_BITMAP_NEW(((size_t)ibuf->x) * ibuf->y, "bucket_fill_bitmap");
|
2014-07-21 12:02:05 +02:00
|
|
|
|
2015-04-30 13:52:25 +02:00
|
|
|
coordinate = (((size_t)y_px) * ibuf->x + x_px);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
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;
|
2014-09-17 14:11:37 +10:00
|
|
|
line_len = sqrtf(line_len);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
do_float = (ibuf->rect_float != NULL);
|
|
|
|
|
|
|
|
/* this will be substituted by something else when selection is available */
|
2015-04-30 13:52:25 +02:00
|
|
|
ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y, false);
|
2014-07-21 12:02:05 +02:00
|
|
|
|
|
|
|
if (do_float) {
|
|
|
|
for (x_px = 0; x_px < ibuf->x; x_px++) {
|
|
|
|
for (y_px = 0; y_px < ibuf->y; y_px++) {
|
2015-09-04 14:24:22 +10:00
|
|
|
float f;
|
2014-07-21 12:02:05 +02:00
|
|
|
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:
|
2015-09-04 14:24:22 +10:00
|
|
|
default:
|
2014-07-21 12:02:05 +02:00
|
|
|
{
|
|
|
|
f = len_v2(p) / line_len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-12-07 15:52:59 +11:00
|
|
|
BKE_colorband_evaluate(br->gradient, f, color_f);
|
2014-07-21 12:02:05 +02:00
|
|
|
/* convert to premultiplied */
|
|
|
|
mul_v3_fl(color_f, color_f[3]);
|
|
|
|
color_f[3] *= br->alpha;
|
2015-04-30 13:52:25 +02:00
|
|
|
IMB_blend_color_float(ibuf->rect_float + 4 * (((size_t)y_px) * ibuf->x + x_px),
|
|
|
|
ibuf->rect_float + 4 * (((size_t)y_px) * ibuf->x + x_px),
|
2014-07-21 12:02:05 +02:00
|
|
|
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++) {
|
2015-09-04 14:24:22 +10:00
|
|
|
float f;
|
2014-07-21 12:02:05 +02:00
|
|
|
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:
|
2015-09-04 14:24:22 +10:00
|
|
|
default:
|
2014-07-21 12:02:05 +02:00
|
|
|
{
|
|
|
|
f = len_v2(p) / line_len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-07 15:52:59 +11:00
|
|
|
BKE_colorband_evaluate(br->gradient, f, color_f);
|
2014-10-17 14:25:50 +02:00
|
|
|
linearrgb_to_srgb_v3_v3(color_f, color_f);
|
2014-07-21 12:02:05 +02:00
|
|
|
rgba_float_to_uchar((unsigned char *)&color_b, color_f);
|
|
|
|
((unsigned char *)&color_b)[3] *= br->alpha;
|
2015-04-30 13:52:25 +02:00
|
|
|
IMB_blend_color_byte((unsigned char *)(ibuf->rect + ((size_t)y_px) * ibuf->x + x_px),
|
|
|
|
(unsigned char *)(ibuf->rect + ((size_t)y_px) * ibuf->x + x_px),
|
2014-07-21 12:02:05 +02:00
|
|
|
(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);
|
|
|
|
}
|