diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint/paint_image_proj.c')
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image_proj.c | 968 |
1 files changed, 692 insertions, 276 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 0154960cf40..1f0b2c80fc4 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -48,30 +48,39 @@ #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLF_translation.h" + #include "PIL_time.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" #include "DNA_brush_types.h" +#include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "BKE_camera.h" +#include "BKE_colortools.h" #include "BKE_context.h" +#include "BKE_depsgraph.h" #include "BKE_DerivedMesh.h" #include "BKE_idprop.h" #include "BKE_brush.h" #include "BKE_image.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_paint.h" #include "BKE_report.h" #include "BKE_scene.h" +#include "BKE_texture.h" #include "UI_view2d.h" +#include "UI_interface.h" #include "ED_paint.h" #include "ED_screen.h" @@ -141,6 +150,7 @@ BLI_INLINE unsigned char f_to_char(const float val) #define PROJ_SRC_VIEW 1 #define PROJ_SRC_IMAGE_CAM 2 #define PROJ_SRC_IMAGE_VIEW 3 +#define PROJ_SRC_VIEW_FILL 4 #define PROJ_VIEW_DATA_ID "view_data" #define PROJ_VIEW_DATA_SIZE (4 * 4 + 4 * 4 + 3) /* viewmat + winmat + clipsta + clipend + is_ortho */ @@ -162,6 +172,9 @@ BLI_INLINE unsigned char f_to_char(const float val) /* vert flags */ #define PROJ_VERT_CULL 1 +/* to avoid locking in tile initialization */ +#define TILE_PENDING SET_INT_IN_POINTER(-1) + /* This is mainly a convenience struct used so we can keep an array of images we use * Thir imbufs, etc, in 1 array, When using threads this array is copied for each thread * because 'partRedrawRect' and 'touch' values would not be thread safe */ @@ -169,7 +182,10 @@ typedef struct ProjPaintImage { Image *ima; ImBuf *ibuf; ImagePaintPartialRedraw *partRedrawRect; - void **undoRect; /* only used to build undo tiles after painting */ + volatile void **undoRect; /* only used to build undo tiles during painting */ + unsigned short **maskRect; /* the mask accumulation must happen on canvas, not on space screen bucket. + * Here we store the mask rectangle */ + bool **valid; /* store flag to enforce validation of undo rectangle */ int touch; } ProjPaintImage; @@ -181,9 +197,14 @@ typedef struct ProjPaintState { Scene *scene; int source; /* PROJ_SRC_**** */ + /* the paint color. It can change depending of inverted mode or not */ + float paint_color[3]; + float paint_color_linear[3]; + Brush *brush; short tool, blend, mode; int orig_brush_size; + float brush_size; Object *ob; /* end similarities with ImagePaintState */ @@ -194,10 +215,13 @@ typedef struct ProjPaintState { MVert *dm_mvert; MFace *dm_mface; - MTFace *dm_mtface; - MTFace *dm_mtface_clone; /* other UV map, use for cloning between layers */ + MTFace **dm_mtface; + MTFace **dm_mtface_clone; /* other UV map, use for cloning between layers */ MTFace *dm_mtface_stencil; + Image *stencil_ima; + float stencil_value; + /* projection painting only */ MemArena *arena_mt[BLENDER_MAX_THREADS]; /* for multithreading, the first item is sometimes used for non threaded cases too */ LinkNode **bucketRect; /* screen sized 2D array, each pixel has a linked list of ProjPixel's */ @@ -231,6 +255,7 @@ typedef struct ProjPaintState { bool do_layer_clone; bool do_layer_stencil; bool do_layer_stencil_inv; + bool do_stencil_brush; bool do_occlude; /* Use raytraced occlusion? - ortherwise will paint right through to the back*/ bool do_backfacecull; /* ignore faces with normals pointing away, skips a lot of raycasts if your normals are correctly flipped */ @@ -245,7 +270,6 @@ typedef struct ProjPaintState { bool do_masking; /* use masking during painting. Some operations such as airbrush may disable */ bool is_texbrush; /* only to avoid running */ bool is_maskbrush; /* mask brush is applied before masking */ - bool is_maskbrush_tiled; /* mask brush is applied after masking */ #ifndef PROJ_DEBUG_NOSEAMBLEED float seam_bleed_px; #endif @@ -269,6 +293,10 @@ typedef struct ProjPaintState { /* redraw */ bool need_redraw; + + BlurKernel *blurkernel; + + SpinLock *tile_lock; } ProjPaintState; typedef union pixelPointer { @@ -290,14 +318,16 @@ typedef struct ProjPixel { * Store the max mask value to avoid painting over an area with a lower opacity * with an advantage that we can avoid touching the pixel at all, if the * new mask value is lower then mask_accum */ - unsigned short mask_accum; + unsigned short *mask_accum; /* for various reasons we may want to mask out painting onto this pixel */ unsigned short mask; short x_px, y_px; + /* horrible hack, store tile valid flag pointer here to re-validate tiles used for anchored and drag-dot strokes */ + bool *valid; - PixelStore origColor; + PixelPointer origColor; PixelStore newColor; PixelPointer pixel; @@ -310,33 +340,43 @@ typedef struct ProjPixelClone { PixelStore clonepx; } ProjPixelClone; -/* blur, store surrounding colors */ -#define PROJ_PIXEL_SOFTEN_TOT 4 -/* blur picking offset (in screenspace) */ -#define PROJ_PIXEL_SOFTEN_OFS_PX 1.0f +/* undo tile pushing */ +typedef struct { + SpinLock *lock; + bool masked; + unsigned short tile_width; + ImBuf **tmpibuf; + ProjPaintImage *pjima; +} TileInfo; -static const float proj_pixel_soften_v2[PROJ_PIXEL_SOFTEN_TOT][2] = { - {-PROJ_PIXEL_SOFTEN_OFS_PX, 0.0f}, - { 0.0f, -PROJ_PIXEL_SOFTEN_OFS_PX}, - { 0.0f, PROJ_PIXEL_SOFTEN_OFS_PX}, - { PROJ_PIXEL_SOFTEN_OFS_PX, 0.0f}, -}; /* Finish projection painting structs */ -static Image *project_paint_face_image(const ProjPaintState *ps, MTFace *dm_mtface, int face_index) +static TexPaintSlot *project_paint_face_paint_slot(const ProjPaintState *ps, int face_index) { - Image *ima; + MFace *mf = ps->dm_mface + face_index; + Material *ma = ps->dm->mat[mf->mat_nr]; + return &ma->texpaintslot[ma->paint_active_slot]; +} - if (ps->do_new_shading_nodes) { /* cached BKE_scene_use_new_shading_nodes result */ - MFace *mf = ps->dm_mface + face_index; - ED_object_get_active_image(ps->ob, mf->mat_nr + 1, &ima, NULL, NULL); +static Image *project_paint_face_paint_image(const ProjPaintState *ps, int face_index) +{ + if (ps->do_stencil_brush) { + return ps->stencil_ima; } else { - ima = dm_mtface[face_index].tpage; + MFace *mf = ps->dm_mface + face_index; + Material *ma = ps->dm->mat[mf->mat_nr]; + return ma->texpaintslot[ma->paint_active_slot].ima; } +} + - return ima; +static TexPaintSlot *project_paint_face_clone_slot(const ProjPaintState *ps, int face_index) +{ + MFace *mf = ps->dm_mface + face_index; + Material *ma = ps->dm->mat[mf->mat_nr]; + return &ma->texpaintslot[ma->paint_clone_slot]; } /* fast projection bucket array lookup, use the safe version for bound checking */ @@ -477,8 +517,8 @@ static int project_paint_PickFace(const ProjPaintState *ps, const float pt[2], f static void uvco_to_wrapped_pxco(const float uv[2], int ibuf_x, int ibuf_y, float *x, float *y) { /* use */ - *x = (float)fmodf(uv[0], 1.0f); - *y = (float)fmodf(uv[1], 1.0f); + *x = fmodf(uv[0], 1.0f); + *y = fmodf(uv[1], 1.0f); if (*x < 0.0f) *x += 1.0f; if (*y < 0.0f) *y += 1.0f; @@ -505,7 +545,7 @@ static bool project_paint_PickColor(const ProjPaintState *ps, const float pt[2], if (face_index == -1) return 0; - tf = ps->dm_mtface + face_index; + tf = *(ps->dm_mtface + face_index); if (side == 0) { interp_v2_v2v2v2(uv, tf->uv[0], tf->uv[1], tf->uv[2], w); @@ -514,8 +554,9 @@ static bool project_paint_PickColor(const ProjPaintState *ps, const float pt[2], interp_v2_v2v2v2(uv, tf->uv[0], tf->uv[2], tf->uv[3], w); } - ima = project_paint_face_image(ps, ps->dm_mtface, face_index); + ima = project_paint_face_paint_image(ps, face_index); ibuf = BKE_image_get_first_ibuf(ima); /* we must have got the imbuf before getting here */ + if (!ibuf) return 0; if (interp) { float x, y; @@ -762,11 +803,11 @@ static int line_isect_x(const float p1[2], const float p2[2], const float x_leve static bool cmp_uv(const float vec2a[2], const float vec2b[2]) { /* if the UV's are not between 0.0 and 1.0 */ - float xa = (float)fmodf(vec2a[0], 1.0f); - float ya = (float)fmodf(vec2a[1], 1.0f); + float xa = fmodf(vec2a[0], 1.0f); + float ya = fmodf(vec2a[1], 1.0f); - float xb = (float)fmodf(vec2b[0], 1.0f); - float yb = (float)fmodf(vec2b[1], 1.0f); + float xb = fmodf(vec2b[0], 1.0f); + float yb = fmodf(vec2b[1], 1.0f); if (xa < 0.0f) xa += 1.0f; if (ya < 0.0f) ya += 1.0f; @@ -843,7 +884,7 @@ static bool pixel_bounds_array(float (*uv)[2], rcti *bounds_px, const int ibuf_x static void project_face_winding_init(const ProjPaintState *ps, const int face_index) { /* detect the winding of faces in uv space */ - MTFace *tf = ps->dm_mtface + face_index; + MTFace *tf = ps->dm_mtface[face_index]; float winding = cross_tri_v2(tf->uv[0], tf->uv[1], tf->uv[2]); if (ps->dm_mface[face_index].v4) @@ -868,7 +909,7 @@ static bool check_seam(const ProjPaintState *ps, MFace *mf; MTFace *tf; const MFace *orig_mf = ps->dm_mface + orig_face; - const MTFace *orig_tf = ps->dm_mtface + orig_face; + const MTFace *orig_tf = ps->dm_mtface[orig_face]; /* vert indices from face vert order indices */ i1 = (*(&orig_mf->v1 + orig_i1_fidx)); @@ -890,13 +931,13 @@ static bool check_seam(const ProjPaintState *ps, /* Only need to check if 'i2_fidx' is valid because we know i1_fidx is the same vert on both faces */ if (i2_fidx != -1) { - Image *tpage = project_paint_face_image(ps, ps->dm_mtface, face_index); - Image *orig_tpage = project_paint_face_image(ps, ps->dm_mtface, orig_face); + Image *tpage = project_paint_face_paint_image(ps, face_index); + Image *orig_tpage = project_paint_face_paint_image(ps, orig_face); BLI_assert(i1_fidx != -1); /* This IS an adjacent face!, now lets check if the UVs are ok */ - tf = ps->dm_mtface + face_index; + tf = ps->dm_mtface[face_index]; /* set up the other face */ *other_face = face_index; @@ -909,7 +950,7 @@ static bool check_seam(const ProjPaintState *ps, /* first test if they have the same image */ if ((orig_tpage == tpage) && cmp_uv(orig_tf->uv[orig_i1_fidx], tf->uv[i1_fidx]) && - cmp_uv(orig_tf->uv[orig_i2_fidx], tf->uv[i2_fidx]) ) + cmp_uv(orig_tf->uv[orig_i2_fidx], tf->uv[i2_fidx])) { /* if faces don't have the same winding in uv space, * they are on the same side so edge is boundary */ @@ -1168,7 +1209,7 @@ static float project_paint_uvpixel_mask( if (ps->do_layer_stencil) { /* another UV maps image is masking this one's */ ImBuf *ibuf_other; - Image *other_tpage = project_paint_face_image(ps, ps->dm_mtface_stencil, face_index); + Image *other_tpage = ps->stencil_ima; const MTFace *tf_other = ps->dm_mtface_stencil + face_index; if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) { @@ -1296,24 +1337,70 @@ static int project_paint_pixel_sizeof(const short tool) } } +static int project_paint_undo_subtiles(const TileInfo *tinf, int tx, int ty) +{ + ProjPaintImage *pjIma = tinf->pjima; + int tile_index = tx + ty * tinf->tile_width; + bool generate_tile = false; + + /* double check lock to avoid locking */ + if (UNLIKELY(!pjIma->undoRect[tile_index])) { + if (tinf->lock) + BLI_spin_lock(tinf->lock); + if (LIKELY(!pjIma->undoRect[tile_index])) { + pjIma->undoRect[tile_index] = TILE_PENDING; + generate_tile = true; + } + if (tinf->lock) + BLI_spin_unlock(tinf->lock); + } + + + if (generate_tile) { + volatile void *undorect; + if (tinf->masked) { + undorect = image_undo_push_tile(pjIma->ima, pjIma->ibuf, tinf->tmpibuf, tx, ty, &pjIma->maskRect[tile_index], &pjIma->valid[tile_index], true); + } + else { + undorect = image_undo_push_tile(pjIma->ima, pjIma->ibuf, tinf->tmpibuf, tx, ty, NULL, &pjIma->valid[tile_index], true); + } + + pjIma->ibuf->userflags |= IB_BITMAPDIRTY; + /* tile ready, publish */ + if (tinf->lock) + BLI_spin_lock(tinf->lock); + pjIma->undoRect[tile_index] = undorect; + if (tinf->lock) + BLI_spin_unlock(tinf->lock); + + } + + return tile_index; +} /* run this function when we know a bucket's, face's pixel can be initialized, * return the ProjPixel which is added to 'ps->bucketRect[bucket_index]' */ static ProjPixel *project_paint_uvpixel_init( const ProjPaintState *ps, MemArena *arena, - const ImBuf *ibuf, + const TileInfo *tinf, int x_px, int y_px, const float mask, const int face_index, - const int image_index, const float pixelScreenCo[4], const float world_spaceCo[3], const int side, const float w[3]) { ProjPixel *projPixel; - + int x_tile, y_tile; + int x_round, y_round; + int tile_offset; + /* volatile is important here to ensure pending check is not optimized away by compiler*/ + volatile int tile_index; + + ProjPaintImage *projima = tinf->pjima; + ImBuf *ibuf = projima->ibuf; /* wrap pixel location */ x_px = mod_i(x_px, ibuf->x); @@ -1321,19 +1408,36 @@ static ProjPixel *project_paint_uvpixel_init( BLI_assert(ps->pixel_sizeof == project_paint_pixel_sizeof(ps->tool)); projPixel = (ProjPixel *)BLI_memarena_alloc(arena, ps->pixel_sizeof); + + /* calculate the undo tile offset of the pixel, used to store the original + * pixel color and accumulated mask if any */ + x_tile = x_px >> IMAPAINT_TILE_BITS; + y_tile = y_px >> IMAPAINT_TILE_BITS; + + x_round = x_tile * IMAPAINT_TILE_SIZE; + y_round = y_tile * IMAPAINT_TILE_SIZE; //memset(projPixel, 0, size); + tile_offset = (x_px - x_round) + (y_px - y_round) * IMAPAINT_TILE_SIZE; + tile_index = project_paint_undo_subtiles(tinf, x_tile, y_tile); + + /* other thread may be initializing the tile so wait here */ + while (projima->undoRect[tile_index] == TILE_PENDING) + ; + + BLI_assert(tile_index < (IMAPAINT_TILE_NUMBER(ibuf->x) * IMAPAINT_TILE_NUMBER(ibuf->y))); + BLI_assert(tile_offset < (IMAPAINT_TILE_SIZE * IMAPAINT_TILE_SIZE)); + + projPixel->valid = projima->valid[tile_index]; + if (ibuf->rect_float) { projPixel->pixel.f_pt = ibuf->rect_float + ((x_px + y_px * ibuf->x) * 4); - projPixel->origColor.f[0] = projPixel->pixel.f_pt[0]; - projPixel->origColor.f[1] = projPixel->pixel.f_pt[1]; - projPixel->origColor.f[2] = projPixel->pixel.f_pt[2]; - projPixel->origColor.f[3] = projPixel->pixel.f_pt[3]; + projPixel->origColor.f_pt = (float *)projima->undoRect[tile_index] + 4 * tile_offset; zero_v4(projPixel->newColor.f); } else { - projPixel->pixel.ch_pt = ((unsigned char *)ibuf->rect + ((x_px + y_px * ibuf->x) * 4)); - projPixel->origColor.uint = *projPixel->pixel.uint_pt; + projPixel->pixel.ch_pt = (unsigned char *)(ibuf->rect + (x_px + y_px * ibuf->x)); + projPixel->origColor.uint_pt = (unsigned int *)projima->undoRect[tile_index] + tile_offset; projPixel->newColor.uint = 0; } @@ -1348,7 +1452,10 @@ static ProjPixel *project_paint_uvpixel_init( projPixel->y_px = y_px; projPixel->mask = (unsigned short)(mask * 65535); - projPixel->mask_accum = 0; + if (ps->do_masking) + projPixel->mask_accum = projima->maskRect[tile_index] + tile_offset; + else + projPixel->mask_accum = NULL; /* which bounding box cell are we in?, needed for undo */ projPixel->bb_cell_index = ((int)(((float)x_px / (float)ibuf->x) * PROJ_BOUNDBOX_DIV)) + @@ -1358,8 +1465,8 @@ static ProjPixel *project_paint_uvpixel_init( if (ps->tool == PAINT_TOOL_CLONE) { if (ps->dm_mtface_clone) { ImBuf *ibuf_other; - Image *other_tpage = project_paint_face_image(ps, ps->dm_mtface_clone, face_index); - const MTFace *tf_other = ps->dm_mtface_clone + face_index; + Image *other_tpage = project_paint_face_clone_slot(ps, face_index)->ima; + const MTFace *tf_other = ps->dm_mtface_clone[face_index]; if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) { /* BKE_image_acquire_ibuf - TODO - this may be slow */ @@ -1423,7 +1530,8 @@ static ProjPixel *project_paint_uvpixel_init( if (ibuf->rect_float) projPixel->pixel.f_pt[0] = 0; else projPixel->pixel.ch_pt[0] = 0; #endif - projPixel->image_index = image_index; + /* pointer arithmetics */ + projPixel->image_index = projima - ps->projImages; return projPixel; } @@ -2131,15 +2239,24 @@ static bool IsectPoly2Df_twoside(const float pt[2], float uv[][2], const int tot /* One of the most important function for projection painting, since it selects the pixels to be added into each bucket. * initialize pixels from this face where it intersects with the bucket_index, optionally initialize pixels for removing seams */ -static void project_paint_face_init(const ProjPaintState *ps, const int thread_index, const int bucket_index, const int face_index, const int image_index, rctf *bucket_bounds, const ImBuf *ibuf, const short clamp_u, const short clamp_v) +static void project_paint_face_init(const ProjPaintState *ps, const int thread_index, const int bucket_index, const int face_index, const int image_index, rctf *bucket_bounds, ImBuf *ibuf, ImBuf **tmpibuf, const short clamp_u, const short clamp_v) { /* Projection vars, to get the 3D locations into screen space */ MemArena *arena = ps->arena_mt[thread_index]; LinkNode **bucketPixelNodes = ps->bucketRect + bucket_index; LinkNode *bucketFaceNodes = ps->bucketFaces[bucket_index]; + bool threaded = (ps->thread_tot > 1); + + TileInfo tinf = { + ps->tile_lock, + ps->do_masking, + IMAPAINT_TILE_NUMBER(ibuf->x), + tmpibuf, + ps->projImages + image_index + }; const MFace *mf = ps->dm_mface + face_index; - const MTFace *tf = ps->dm_mtface + face_index; + const MTFace *tf = ps->dm_mtface[face_index]; /* UV/pixel seeking data */ int x; /* Image X-Pixel */ @@ -2258,6 +2375,10 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i CLAMP(bounds_px.ymax, 0, ibuf->y); } + /* + project_paint_undo_tiles_init(&bounds_px, ps->projImages + image_index, tmpibuf, + tile_width, threaded, ps->do_masking); + */ /* clip face and */ has_isect = 0; @@ -2300,8 +2421,8 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i if (mask > 0.0f) { BLI_linklist_prepend_arena( bucketPixelNodes, - project_paint_uvpixel_init(ps, arena, ibuf, x, y, mask, face_index, - image_index, pixelScreenCo, wco, side, w), + project_paint_uvpixel_init(ps, arena, &tinf, x, y, mask, face_index, + pixelScreenCo, wco, side, w), arena ); } @@ -2333,7 +2454,7 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i if (ps->seam_bleed_px > 0.0f) { int face_seam_flag; - if (ps->thread_tot > 1) + if (threaded) BLI_lock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */ face_seam_flag = ps->faceSeamFlags[face_index]; @@ -2351,7 +2472,7 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i if ((face_seam_flag & (PROJ_FACE_SEAM1 | PROJ_FACE_SEAM2 | PROJ_FACE_SEAM3 | PROJ_FACE_SEAM4)) == 0) { - if (ps->thread_tot > 1) + if (threaded) BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */ } @@ -2376,7 +2497,7 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i uv_image_outset(tf_uv_pxoffset, outset_uv, ps->seam_bleed_px, ibuf->x, ibuf->y, mf->v4 != 0); /* ps->faceSeamUVs cant be modified when threading, now this is done we can unlock */ - if (ps->thread_tot > 1) + if (threaded) BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */ vCoSS[0] = ps->screenCoords[mf->v1]; @@ -2520,7 +2641,8 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i if (mask > 0.0f) { BLI_linklist_prepend_arena( bucketPixelNodes, - project_paint_uvpixel_init(ps, arena, ibuf, x, y, mask, face_index, image_index, pixelScreenCo, wco, side, w), + project_paint_uvpixel_init(ps, arena, &tinf, x, y, mask, face_index, + pixelScreenCo, wco, side, w), arena ); } @@ -2589,6 +2711,7 @@ static void project_bucket_init(const ProjPaintState *ps, const int thread_index ImBuf *ibuf = NULL; Image *tpage_last = NULL, *tpage; Image *ima = NULL; + ImBuf *tmpibuf = NULL; if (ps->image_tot == 1) { /* Simple loop, no context switching */ @@ -2596,7 +2719,7 @@ static void project_bucket_init(const ProjPaintState *ps, const int thread_index ima = ps->projImages[0].ima; for (node = ps->bucketFaces[bucket_index]; node; node = node->next) { - project_paint_face_init(ps, thread_index, bucket_index, GET_INT_FROM_POINTER(node->link), 0, bucket_bounds, ibuf, ima->tpageflag & IMA_CLAMP_U, ima->tpageflag & IMA_CLAMP_V); + project_paint_face_init(ps, thread_index, bucket_index, GET_INT_FROM_POINTER(node->link), 0, bucket_bounds, ibuf, &tmpibuf, ima->tpageflag & IMA_CLAMP_U, ima->tpageflag & IMA_CLAMP_V); } } else { @@ -2606,7 +2729,7 @@ static void project_bucket_init(const ProjPaintState *ps, const int thread_index face_index = GET_INT_FROM_POINTER(node->link); /* Image context switching */ - tpage = project_paint_face_image(ps, ps->dm_mtface, face_index); + tpage = project_paint_face_paint_image(ps, face_index); if (tpage_last != tpage) { tpage_last = tpage; @@ -2620,10 +2743,13 @@ static void project_bucket_init(const ProjPaintState *ps, const int thread_index } /* context switching done */ - project_paint_face_init(ps, thread_index, bucket_index, face_index, image_index, bucket_bounds, ibuf, ima->tpageflag & IMA_CLAMP_U, ima->tpageflag & IMA_CLAMP_V); + project_paint_face_init(ps, thread_index, bucket_index, face_index, image_index, bucket_bounds, ibuf, &tmpibuf, ima->tpageflag & IMA_CLAMP_U, ima->tpageflag & IMA_CLAMP_V); } } + if (tmpibuf) + IMB_freeImBuf(tmpibuf); + ps->bucketFlags[bucket_index] |= PROJ_BUCKET_INIT; } @@ -2771,11 +2897,17 @@ static void project_paint_begin(ProjPaintState *ps) ProjPaintImage *projIma; Image *tpage_last = NULL, *tpage; + TexPaintSlot *slot_last = NULL, *slot = NULL; + TexPaintSlot *slot_last_clone = NULL, *slot_clone; /* Face vars */ MPoly *mpoly_orig; MFace *mf; - MTFace *tf; + MTFace **tf; + MTFace *tf_base; + + MTFace **tf_clone; + MTFace *tf_clone_base; int a, i; /* generic looping vars */ int image_index = -1, face_index; @@ -2830,13 +2962,15 @@ static void project_paint_begin(ProjPaintState *ps) return; } - ps->dm_mvert = ps->dm->getVertArray(ps->dm); - ps->dm_mface = ps->dm->getTessFaceArray(ps->dm); - ps->dm_mtface = ps->dm->getTessFaceDataArray(ps->dm, CD_MTFACE); + DM_update_materials(ps->dm, ps->ob); ps->dm_totvert = ps->dm->getNumVerts(ps->dm); ps->dm_totface = ps->dm->getNumTessFaces(ps->dm); + ps->dm_mvert = ps->dm->getVertArray(ps->dm); + ps->dm_mface = ps->dm->getTessFaceArray(ps->dm); + ps->dm_mtface = MEM_mallocN(ps->dm_totface * sizeof(MTFace *), "proj_paint_mtfaces"); + if (ps->do_face_sel) { index_mf_to_mpoly = ps->dm->getTessFaceDataArray(ps->dm, CD_ORIGINDEX); index_mp_to_orig = ps->dm->getPolyDataArray(ps->dm, CD_ORIGINDEX); @@ -2852,32 +2986,23 @@ static void project_paint_begin(ProjPaintState *ps) } /* use clone mtface? */ - - - /* Note, use the original mesh for getting the clone and mask layer index - * this avoids re-generating the derived mesh just to get the new index */ if (ps->do_layer_clone) { - //int layer_num = CustomData_get_clone_layer(&ps->dm->faceData, CD_MTFACE); - int layer_num = CustomData_get_clone_layer(&((Mesh *)ps->ob->data)->pdata, CD_MTEXPOLY); - if (layer_num != -1) - ps->dm_mtface_clone = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num); - - if (ps->dm_mtface_clone == NULL || ps->dm_mtface_clone == ps->dm_mtface) { - ps->do_layer_clone = false; - ps->dm_mtface_clone = NULL; - } + ps->dm_mtface_clone = MEM_mallocN(ps->dm_totface * sizeof(MTFace *), "proj_paint_mtfaces"); } - if (ps->do_layer_stencil) { + if (ps->do_layer_stencil || ps->do_stencil_brush) { //int layer_num = CustomData_get_stencil_layer(&ps->dm->faceData, CD_MTFACE); int layer_num = CustomData_get_stencil_layer(&((Mesh *)ps->ob->data)->pdata, CD_MTEXPOLY); if (layer_num != -1) ps->dm_mtface_stencil = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num); - if (ps->dm_mtface_stencil == NULL || ps->dm_mtface_stencil == ps->dm_mtface) { - ps->do_layer_stencil = false; - ps->dm_mtface_stencil = NULL; + if (ps->dm_mtface_stencil == NULL) { + /* get active instead */ + ps->dm_mtface_stencil = CustomData_get_layer(&ps->dm->faceData, CD_MTFACE); } + + if (ps->do_stencil_brush) + tf_base = ps->dm_mtface_stencil; } /* when using subsurf or multires, mface arrays are thrown away, we need to keep a copy */ @@ -3092,6 +3217,13 @@ static void project_paint_begin(ProjPaintState *ps) if (reset_threads) ps->thread_tot = 1; + if (ps->thread_tot > 1) { + ps->tile_lock = MEM_mallocN(sizeof(SpinLock), "projpaint_tile_lock"); + BLI_spin_init(ps->tile_lock); + } + + image_undo_init_locks(); + for (a = 0; a < ps->thread_tot; a++) { ps->arena_mt[a] = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), "project paint arena"); } @@ -3153,7 +3285,46 @@ static void project_paint_begin(ProjPaintState *ps) is_face_sel = true; } - if (is_face_sel && (tpage = project_paint_face_image(ps, ps->dm_mtface, face_index))) { + if (!ps->do_stencil_brush) { + slot = project_paint_face_paint_slot(ps, face_index); + /* all faces should have a valid slot, reassert here */ + if (slot == NULL) + continue; + + if (slot != slot_last) { + if (!slot->uvname[0] || !(tf_base = CustomData_get_layer_named(&ps->dm->faceData, CD_MTFACE, slot->uvname))) + tf_base = CustomData_get_layer(&ps->dm->faceData, CD_MTFACE); + slot_last = slot; + } + + /* don't allow using the same inage for painting and stencilling */ + if (slot->ima == ps->stencil_ima) + continue; + } + + *tf = tf_base + face_index; + + if (ps->do_layer_clone) { + slot_clone = project_paint_face_clone_slot(ps, face_index); + /* all faces should have a valid slot, reassert here */ + if (ELEM(slot_clone, NULL, slot)) + continue; + + tf_clone = ps->dm_mtface_clone + face_index; + + if (slot_clone != slot_last_clone) { + if (!slot_clone->uvname[0] || !(tf_clone_base = CustomData_get_layer_named(&ps->dm->faceData, CD_MTFACE, slot_clone->uvname))) + tf_clone_base = CustomData_get_layer(&ps->dm->faceData, CD_MTFACE); + slot_last_clone = slot_clone; + } + + *tf_clone = tf_clone_base + face_index; + } + + /* tfbase here should be non-null! */ + BLI_assert (tf_base != NULL); + + if (is_face_sel && ((slot && (tpage = slot->ima)) || (tpage = project_paint_face_paint_image(ps, face_index)))) { const float *v1coSS, *v2coSS, *v3coSS, *v4coSS = NULL; v1coSS = ps->screenCoords[mf->v1]; @@ -3251,10 +3422,19 @@ static void project_paint_begin(ProjPaintState *ps) projIma = ps->projImages = (ProjPaintImage *)BLI_memarena_alloc(arena, sizeof(ProjPaintImage) * ps->image_tot); for (node = image_LinkList, i = 0; node; node = node->next, i++, projIma++) { + int size; projIma->ima = node->link; projIma->touch = 0; projIma->ibuf = BKE_image_acquire_ibuf(projIma->ima, NULL, NULL); - projIma->partRedrawRect = BLI_memarena_calloc(arena, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); + size = sizeof(void **) * IMAPAINT_TILE_NUMBER(projIma->ibuf->x) * IMAPAINT_TILE_NUMBER(projIma->ibuf->y); + projIma->partRedrawRect = BLI_memarena_alloc(arena, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); + memset(projIma->partRedrawRect, 0, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); + projIma->undoRect = (volatile void **) BLI_memarena_alloc(arena, size); + memset(projIma->undoRect, 0, size); + projIma->maskRect = (unsigned short **) BLI_memarena_alloc(arena, size); + memset(projIma->maskRect, 0, size); + projIma->valid = (bool **) BLI_memarena_alloc(arena, size); + memset(projIma->valid, 0, size); } /* we have built the array, discard the linked list */ @@ -3281,95 +3461,12 @@ static void project_paint_end(ProjPaintState *ps) int a; ProjPaintImage *projIma; - /* build undo data from original pixel colors */ - if (U.uiflag & USER_GLOBALUNDO) { - ProjPixel *projPixel; - ImBuf *tmpibuf = NULL, *tmpibuf_float = NULL; - LinkNode *pixel_node; - void *tilerect; - MemArena *arena = ps->arena_mt[0]; /* threaded arena re-used for non threaded case */ - - int bucket_tot = (ps->buckets_x * ps->buckets_y); /* we could get an X/Y but easier to loop through all possible buckets */ - int bucket_index; - int tile_index; - int x_round, y_round; - int x_tile, y_tile; - int is_float = -1; - - /* context */ - ProjPaintImage *last_projIma; - int last_image_index = -1; - int last_tile_width = 0; - - for (a = 0, last_projIma = ps->projImages; a < ps->image_tot; a++, last_projIma++) { - int size = sizeof(void **) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->y); - last_projIma->undoRect = (void **) BLI_memarena_calloc(arena, size); - last_projIma->ibuf->userflags |= IB_BITMAPDIRTY; - } - - for (bucket_index = 0; bucket_index < bucket_tot; bucket_index++) { - /* loop through all pixels */ - for (pixel_node = ps->bucketRect[bucket_index]; pixel_node; pixel_node = pixel_node->next) { - - /* ok we have a pixel, was it modified? */ - projPixel = (ProjPixel *)pixel_node->link; - - if (last_image_index != projPixel->image_index) { - /* set the context */ - last_image_index = projPixel->image_index; - last_projIma = ps->projImages + last_image_index; - last_tile_width = IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x); - is_float = last_projIma->ibuf->rect_float ? 1 : 0; - } - - - if ((is_float == 0 && projPixel->origColor.uint != *projPixel->pixel.uint_pt) || - (is_float == 1 && - (projPixel->origColor.f[0] != projPixel->pixel.f_pt[0] || - projPixel->origColor.f[1] != projPixel->pixel.f_pt[1] || - projPixel->origColor.f[2] != projPixel->pixel.f_pt[2] || - projPixel->origColor.f[3] != projPixel->pixel.f_pt[3])) - ) - { - - x_tile = projPixel->x_px >> IMAPAINT_TILE_BITS; - y_tile = projPixel->y_px >> IMAPAINT_TILE_BITS; - - x_round = x_tile * IMAPAINT_TILE_SIZE; - y_round = y_tile * IMAPAINT_TILE_SIZE; - - tile_index = x_tile + y_tile * last_tile_width; - - if (last_projIma->undoRect[tile_index] == NULL) { - /* add the undo tile from the modified image, then write the original colors back into it */ - tilerect = last_projIma->undoRect[tile_index] = image_undo_push_tile(last_projIma->ima, last_projIma->ibuf, is_float ? (&tmpibuf_float) : (&tmpibuf), x_tile, y_tile); - } - else { - tilerect = last_projIma->undoRect[tile_index]; - } - - /* This is a BIT ODD, but overwrite the undo tiles image info with this pixels original color - * because allocating the tiles along the way slows down painting */ - - if (is_float) { - float *rgba_fp = (float *)tilerect + (((projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE)) * 4; - copy_v4_v4(rgba_fp, projPixel->origColor.f); - } - else { - ((unsigned int *)tilerect)[(projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE] = projPixel->origColor.uint; - } - } - } - } - - if (tmpibuf) IMB_freeImBuf(tmpibuf); - if (tmpibuf_float) IMB_freeImBuf(tmpibuf_float); - } - /* done calculating undo data */ + image_undo_remove_masks(); /* dereference used image buffers */ for (a = 0, projIma = ps->projImages; a < ps->image_tot; a++, projIma++) { BKE_image_release_ibuf(projIma->ima, projIma->ibuf, NULL); + DAG_id_tag_update(&projIma->ima->id, 0); } BKE_image_release_ibuf(ps->reproject_image, ps->reproject_ibuf, NULL); @@ -3378,6 +3475,14 @@ static void project_paint_end(ProjPaintState *ps) MEM_freeN(ps->bucketRect); MEM_freeN(ps->bucketFaces); MEM_freeN(ps->bucketFlags); + MEM_freeN(ps->dm_mtface); + if (ps->do_layer_clone) + MEM_freeN(ps->dm_mtface_clone); + if (ps->thread_tot > 1) { + BLI_spin_end(ps->tile_lock); + MEM_freeN((void *)ps->tile_lock); + } + image_undo_end_locks(); #ifndef PROJ_DEBUG_NOSEAMBLEED if (ps->seam_bleed_px > 0.0f) { @@ -3388,6 +3493,11 @@ static void project_paint_end(ProjPaintState *ps) } #endif + if (ps->blurkernel) { + paint_delete_blur_kernel(ps->blurkernel); + MEM_freeN(ps->blurkernel); + } + if (ps->vertFlags) MEM_freeN(ps->vertFlags); for (a = 0; a < ps->thread_tot; a++) { @@ -3480,7 +3590,7 @@ static bool project_bucket_iter_init(ProjPaintState *ps, const float mval_f[2]) { if (ps->source == PROJ_SRC_VIEW) { float min_brush[2], max_brush[2]; - const float radius = (float)BKE_brush_size_get(ps->scene, ps->brush); + const float radius = ps->brush_size; /* so we don't have a bucket bounds that is way too small to paint into */ // if (radius < 1.0f) radius = 1.0f; // this doesn't work yet :/ @@ -3518,7 +3628,7 @@ static bool project_bucket_iter_init(ProjPaintState *ps, const float mval_f[2]) static bool project_bucket_iter_next(ProjPaintState *ps, int *bucket_index, rctf *bucket_bounds, const float mval[2]) { - const int diameter = 2 * BKE_brush_size_get(ps->scene, ps->brush); + const int diameter = 2 * ps->brush_size; if (ps->thread_tot > 1) BLI_lock_thread(LOCK_CUSTOM1); @@ -3580,7 +3690,7 @@ static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, floa clone_rgba[3] = (unsigned char)(clone_pt[3] * mask); if (ps->do_masking) { - IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch, clone_rgba, ps->blend); + IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch_pt, clone_rgba, ps->blend); } else { IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->pixel.ch_pt, clone_rgba, ps->blend); @@ -3598,7 +3708,7 @@ static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, fl mul_v4_v4fl(clone_rgba, clone_pt, mask); if (ps->do_masking) { - IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f, clone_rgba, ps->blend); + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f_pt, clone_rgba, ps->blend); } else { IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, clone_rgba, ps->blend); @@ -3636,42 +3746,58 @@ static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, fl BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena); } -/* do_projectpaint_soften for float & byte - */ -static float inv_pow2(float f) -{ - f = 1.0f - f; - f = f * f; - return 1.0f - f; -} - static void do_projectpaint_soften_f(ProjPaintState *ps, ProjPixel *projPixel, float mask, MemArena *softenArena, LinkNode **softenPixels) { - unsigned int accum_tot = 0; - unsigned int i; - + float accum_tot = 0.0; + int xk, yk; + BlurKernel *kernel = ps->blurkernel; float *rgba = projPixel->newColor.f; - /* sigh, mask values tend to need to be a _lot_ stronger with blur */ - mask = inv_pow2(mask); - /* rather then painting, accumulate surrounding colors */ zero_v4(rgba); - for (i = 0; i < PROJ_PIXEL_SOFTEN_TOT; i++) { - float co_ofs[2]; - float rgba_tmp[4]; - sub_v2_v2v2(co_ofs, projPixel->projCoSS, proj_pixel_soften_v2[i]); - if (project_paint_PickColor(ps, co_ofs, rgba_tmp, NULL, true)) { - add_v4_v4(rgba, rgba_tmp); - accum_tot++; + for (yk = 0; yk < kernel->side; yk++) { + for (xk = 0; xk < kernel->side; xk++) { + float rgba_tmp[4]; + float co_ofs[2] = {xk - kernel->pixel_len, yk - kernel->pixel_len}; + + add_v2_v2(co_ofs, projPixel->projCoSS); + + if (project_paint_PickColor(ps, co_ofs, rgba_tmp, NULL, true)) { + float weight = kernel->wdata[xk + yk * kernel->side]; + mul_v4_fl(rgba_tmp, weight); + add_v4_v4(rgba, rgba_tmp); + accum_tot += weight; + } } } if (LIKELY(accum_tot != 0)) { mul_v4_fl(rgba, 1.0f / (float)accum_tot); - blend_color_interpolate_float(rgba, rgba, projPixel->pixel.f_pt, mask); + + if (ps->mode == BRUSH_STROKE_INVERT) { + /* subtract blurred image from normal image gives high pass filter */ + sub_v3_v3v3(rgba, projPixel->pixel.f_pt, rgba); + + /* 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 */ + rgba[0] = rgba[1] = rgba[2] = rgb_to_grayscale(rgba); + if (fabsf(rgba[0]) > ps->brush->sharp_threshold) { + float alpha = projPixel->pixel.f_pt[3]; + projPixel->pixel.f_pt[3] = rgba[3] = mask; + + /* add to enhance edges */ + blend_color_add_float(rgba, projPixel->pixel.f_pt, rgba); + projPixel->pixel.f_pt[3] = alpha; + } + else + return; + } + else { + blend_color_interpolate_float(rgba, rgba, projPixel->pixel.f_pt, mask); + } + BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena); } } @@ -3679,24 +3805,27 @@ static void do_projectpaint_soften_f(ProjPaintState *ps, ProjPixel *projPixel, f static void do_projectpaint_soften(ProjPaintState *ps, ProjPixel *projPixel, float mask, MemArena *softenArena, LinkNode **softenPixels) { - unsigned int accum_tot = 0; - unsigned int i; - + float accum_tot = 0; + int xk, yk; + BlurKernel *kernel = ps->blurkernel; float rgba[4]; /* convert to byte after */ - /* sigh, mask values tend to need to be a _lot_ stronger with blur */ - mask = inv_pow2(mask); - /* rather then painting, accumulate surrounding colors */ zero_v4(rgba); - for (i = 0; i < PROJ_PIXEL_SOFTEN_TOT; i++) { - float co_ofs[2]; - float rgba_tmp[4]; - sub_v2_v2v2(co_ofs, projPixel->projCoSS, proj_pixel_soften_v2[i]); - if (project_paint_PickColor(ps, co_ofs, rgba_tmp, NULL, true)) { - add_v4_v4(rgba, rgba_tmp); - accum_tot++; + for (yk = 0; yk < kernel->side; yk++) { + for (xk = 0; xk < kernel->side; xk++) { + float rgba_tmp[4]; + float co_ofs[2] = {xk - kernel->pixel_len, yk - kernel->pixel_len}; + + add_v2_v2(co_ofs, projPixel->projCoSS); + + if (project_paint_PickColor(ps, co_ofs, rgba_tmp, NULL, true)) { + float weight = kernel->wdata[xk + yk * kernel->side]; + mul_v4_fl(rgba_tmp, weight); + add_v4_v4(rgba, rgba_tmp); + accum_tot += weight; + } } } @@ -3704,9 +3833,34 @@ static void do_projectpaint_soften(ProjPaintState *ps, ProjPixel *projPixel, flo unsigned char *rgba_ub = projPixel->newColor.ch; mul_v4_fl(rgba, 1.0f / (float)accum_tot); - premul_float_to_straight_uchar(rgba_ub, rgba); - blend_color_interpolate_byte(rgba_ub, rgba_ub, projPixel->pixel.ch_pt, mask); + if (ps->mode == BRUSH_STROKE_INVERT) { + float rgba_pixel[4]; + + straight_uchar_to_premul_float(rgba_pixel, projPixel->pixel.ch_pt); + + /* subtract blurred image from normal image gives high pass filter */ + sub_v3_v3v3(rgba, rgba_pixel, rgba); + /* 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 */ + rgba[0] = rgba[1] = rgba[2] = rgb_to_grayscale(rgba); + if (fabsf(rgba[0]) > ps->brush->sharp_threshold) { + float alpha = rgba_pixel[3]; + rgba[3] = rgba_pixel[3] = mask; + + /* add to enhance edges */ + blend_color_add_float(rgba, rgba_pixel, rgba); + + rgba[3] = alpha; + premul_float_to_straight_uchar(rgba_ub, rgba); + } + else + return; + } + else { + premul_float_to_straight_uchar(rgba_ub, rgba); + blend_color_interpolate_byte(rgba_ub, rgba_ub, projPixel->pixel.ch_pt, mask); + } BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena); } } @@ -3716,7 +3870,7 @@ static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, const float rgb[3]; unsigned char rgba_ub[4]; - copy_v3_v3(rgb, ps->brush->rgb); + copy_v3_v3(rgb, ps->paint_color); if (ps->is_texbrush) { mul_v3_v3(rgb, texrgb); @@ -3728,7 +3882,7 @@ static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, const rgba_ub[3] = f_to_char(mask); if (ps->do_masking) { - IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch, rgba_ub, ps->blend); + IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch_pt, rgba_ub, ps->blend); } else { IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->pixel.ch_pt, rgba_ub, ps->blend); @@ -3739,7 +3893,7 @@ static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, con { float rgba[4]; - srgb_to_linearrgb_v3_v3(rgba, ps->brush->rgb); + copy_v3_v3(rgba, ps->paint_color_linear); if (ps->is_texbrush) mul_v3_v3(rgba, texrgb); @@ -3748,13 +3902,42 @@ static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, con rgba[3] = mask; if (ps->do_masking) { - IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f, rgba, ps->blend); + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f_pt, rgba, ps->blend); } else { IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, rgba, ps->blend); } } +static void do_projectpaint_mask(ProjPaintState *ps, ProjPixel *projPixel, float mask) +{ + unsigned char rgba_ub[4]; + rgba_ub[0] = rgba_ub[1] = rgba_ub[2] = ps->stencil_value * 255.0f; + rgba_ub[3] = f_to_char(mask); + + if (ps->do_masking) { + IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch_pt, rgba_ub, ps->blend); + } + else { + IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->pixel.ch_pt, rgba_ub, ps->blend); + } +} + +static void do_projectpaint_mask_f(ProjPaintState *ps, ProjPixel *projPixel, float mask) +{ + float rgba[4]; + rgba[0] = rgba[1] = rgba[2] = ps->stencil_value; + rgba[3] = mask; + + if (ps->do_masking) { + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f_pt, rgba, ps->blend); + } + else { + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, rgba, ps->blend); + } +} + + /* run this for single and multithreaded painting */ static void *do_projectpaint_thread(void *ph_v) { @@ -3788,7 +3971,7 @@ static void *do_projectpaint_thread(void *ph_v) float co[2]; unsigned short mask_short; const float brush_alpha = BKE_brush_alpha_get(ps->scene, brush); - const float brush_radius = (float)BKE_brush_size_get(ps->scene, brush); + const float brush_radius = ps->brush_size; const float brush_radius_sq = brush_radius * brush_radius; /* avoid a square root with every dist comparison */ short lock_alpha = ELEM(brush->blend, IMB_BLEND_ERASE_ALPHA, IMB_BLEND_ADD_ALPHA) ? 0 : brush->flag & BRUSH_LOCK_ALPHA; @@ -3838,32 +4021,102 @@ static void *do_projectpaint_thread(void *ph_v) } /* end copy */ - if (is_floatbuf) { - /* re-project buffer is assumed byte - TODO, allow float */ - bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL, - projPixel->projCoSS[0], projPixel->projCoSS[1]); - if (projPixel->newColor.ch[3]) { - float newColor_f[4]; - float mask = ((float)projPixel->mask) * (1.0f / 65535.0f); + /* fill tools */ + if (ps->source == PROJ_SRC_VIEW_FILL) { + if (brush->flag & BRUSH_USE_GRADIENT) { + /* these could probably be cached instead of being done per pixel */ + float tangent[2]; + float line_len_sq_inv, line_len; + float f; + float color_f[4]; + float p[2] = {projPixel->projCoSS[0] - lastpos[0], projPixel->projCoSS[1] - lastpos[1]}; + + sub_v2_v2v2(tangent, pos, lastpos); + line_len = len_squared_v2(tangent); + line_len_sq_inv = 1.0f / line_len; + line_len = sqrt(line_len); + + switch (brush->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(brush->gradient, f, color_f); + color_f[3] *= ((float)projPixel->mask) * (1.0f / 65535.0f) * brush->alpha; + + if (is_floatbuf) { + /* convert to premultipied */ + mul_v3_fl(color_f, color_f[3]); + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f_pt, + color_f, ps->blend); + } + else { + rgba_float_to_uchar(projPixel->newColor.ch, color_f); + IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch_pt, + projPixel->newColor.ch, ps->blend); + } + } + else { + if (is_floatbuf) { + float newColor_f[4]; + newColor_f[3] = ((float)projPixel->mask) * (1.0f / 65535.0f) * brush->alpha; + copy_v3_v3(newColor_f, ps->paint_color_linear); - straight_uchar_to_premul_float(newColor_f, projPixel->newColor.ch); - IMB_colormanagement_colorspace_to_scene_linear_v4(newColor_f, true, ps->reproject_ibuf->rect_colorspace); - mul_v4_v4fl(newColor_f, newColor_f, mask); + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f_pt, + newColor_f, ps->blend); + } + else { + float mask = ((float)projPixel->mask) * (1.0f / 65535.0f); + projPixel->newColor.ch[3] = mask * 255 * brush->alpha; - blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, - newColor_f); + rgb_float_to_uchar(projPixel->newColor.ch, ps->paint_color); + IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch_pt, + projPixel->newColor.ch, ps->blend); + } } + + last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index; + last_partial_redraw_cell->x1 = min_ii(last_partial_redraw_cell->x1, (int)projPixel->x_px); + last_partial_redraw_cell->y1 = min_ii(last_partial_redraw_cell->y1, (int)projPixel->y_px); + + last_partial_redraw_cell->x2 = max_ii(last_partial_redraw_cell->x2, (int)projPixel->x_px + 1); + last_partial_redraw_cell->y2 = max_ii(last_partial_redraw_cell->y2, (int)projPixel->y_px + 1); } else { - /* re-project buffer is assumed byte - TODO, allow float */ - bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL, - projPixel->projCoSS[0], projPixel->projCoSS[1]); - if (projPixel->newColor.ch[3]) { - float mask = ((float)projPixel->mask) * (1.0f / 65535.0f); - projPixel->newColor.ch[3] *= mask; - - blend_color_mix_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch, - projPixel->newColor.ch); + if (is_floatbuf) { + /* re-project buffer is assumed byte - TODO, allow float */ + bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL, + projPixel->projCoSS[0], projPixel->projCoSS[1]); + if (projPixel->newColor.ch[3]) { + float newColor_f[4]; + float mask = ((float)projPixel->mask) * (1.0f / 65535.0f); + + straight_uchar_to_premul_float(newColor_f, projPixel->newColor.ch); + IMB_colormanagement_colorspace_to_scene_linear_v4(newColor_f, true, ps->reproject_ibuf->rect_colorspace); + mul_v4_v4fl(newColor_f, newColor_f, mask); + + blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f_pt, + newColor_f); + } + } + else { + /* re-project buffer is assumed byte - TODO, allow float */ + bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL, + projPixel->projCoSS[0], projPixel->projCoSS[1]); + if (projPixel->newColor.ch[3]) { + float mask = ((float)projPixel->mask) * (1.0f / 65535.0f); + projPixel->newColor.ch[3] *= mask; + + blend_color_mix_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch_pt, + projPixel->newColor.ch); + } } } } @@ -3885,7 +4138,7 @@ static void *do_projectpaint_thread(void *ph_v) if (falloff > 0.0f) { float texrgb[3]; - float mask = falloff; + float mask; if (ps->do_masking) { /* masking to keep brush contribution to a pixel limited. note we do not do @@ -3894,20 +4147,24 @@ static void *do_projectpaint_thread(void *ph_v) * * Instead we use a formula that adds up but approaches brush_alpha slowly * and never exceeds it, which gives nice smooth results. */ - float mask_accum = projPixel->mask_accum; + float mask_accum = *projPixel->mask_accum; + float max_mask = brush_alpha * falloff * 65535.0f; if (ps->is_maskbrush) { float texmask = BKE_brush_sample_masktex(ps->scene, ps->brush, projPixel->projCoSS, thread_index, pool); - CLAMP(texmask, 0.0f, 1.0f); - mask = mask_accum + (brush_alpha * texmask * 65535.0f - mask_accum) * mask; - } - else { - mask = mask_accum + (brush_alpha * 65535.0f - mask_accum) * mask; + max_mask *= texmask; } + + if (brush->flag & BRUSH_ACCUMULATE) + mask = mask_accum + max_mask; + else + mask = mask_accum + (max_mask - mask_accum * falloff); + + mask = min_ff(mask, 65535.0f); mask_short = (unsigned short)mask; - if (mask_short > projPixel->mask_accum) { - projPixel->mask_accum = mask_short; + if (mask_short > *projPixel->mask_accum) { + *projPixel->mask_accum = mask_short; mask = mask_short * (1.0f / 65535.0f); } else { @@ -3916,7 +4173,7 @@ static void *do_projectpaint_thread(void *ph_v) } } else { - mask *= brush_alpha; + mask = brush_alpha * falloff; if (ps->is_maskbrush) { float texmask = BKE_brush_sample_masktex(ps->scene, ps->brush, projPixel->projCoSS, thread_index, pool); CLAMP(texmask, 0.0f, 1.0f); @@ -3945,10 +4202,6 @@ static void *do_projectpaint_thread(void *ph_v) mask *= texrgba[3]; } - if (ps->is_maskbrush_tiled) { - mask *= BKE_brush_sample_masktex(ps->scene, ps->brush, projPixel->projCoSS, thread_index, pool); - } - /* extra mask for normal, layer stencil, .. */ mask *= ((float)projPixel->mask) * (1.0f / 65535.0f); @@ -3964,6 +4217,9 @@ static void *do_projectpaint_thread(void *ph_v) } /* end copy */ + /* validate undo tile, since we will modify t*/ + *projPixel->valid = true; + last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index; last_partial_redraw_cell->x1 = min_ii(last_partial_redraw_cell->x1, (int)projPixel->x_px); last_partial_redraw_cell->y1 = min_ii(last_partial_redraw_cell->y1, (int)projPixel->y_px); @@ -3987,6 +4243,10 @@ static void *do_projectpaint_thread(void *ph_v) if (is_floatbuf) do_projectpaint_soften_f(ps, projPixel, mask, softenArena, &softenPixels_f); else do_projectpaint_soften(ps, projPixel, mask, softenArena, &softenPixels); break; + case PAINT_TOOL_MASK: + if (is_floatbuf) do_projectpaint_mask_f(ps, projPixel, mask); + else do_projectpaint_mask(ps, projPixel, mask); + break; default: if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, texrgb, mask); else do_projectpaint_draw(ps, projPixel, texrgb, mask); @@ -3995,8 +4255,8 @@ static void *do_projectpaint_thread(void *ph_v) } if (lock_alpha) { - if (is_floatbuf) projPixel->pixel.f_pt[3] = projPixel->origColor.f[3]; - else projPixel->pixel.ch_pt[3] = projPixel->origColor.ch[3]; + if (is_floatbuf) projPixel->pixel.f_pt[3] = projPixel->origColor.f_pt[3]; + else projPixel->pixel.ch_pt[3] = projPixel->origColor.ch_pt[3]; } /* done painting */ @@ -4114,14 +4374,17 @@ static bool project_paint_op(void *state, const float lastpos[2], const float po } -void paint_proj_stroke(bContext *C, void *pps, const float prev_pos[2], const float pos[2]) +void paint_proj_stroke(const bContext *C, void *pps, const float prev_pos[2], const float pos[2], float pressure, float distance, float size) { ProjPaintState *ps = pps; + Brush *brush = ps->brush; + Scene *scene = ps->scene; int a; + ps->brush_size = size; + /* clone gets special treatment here to avoid going through image initialization */ if (ps->tool == PAINT_TOOL_CLONE && ps->mode == BRUSH_STROKE_INVERT) { - Scene *scene = ps->scene; View3D *v3d = ps->v3d; float *cursor = ED_view3d_cursor3d_get(scene, v3d); int mval_i[2] = {(int)pos[0], (int)pos[1]}; @@ -4136,6 +4399,25 @@ void paint_proj_stroke(bContext *C, void *pps, const float prev_pos[2], const fl return; } + /* handle gradient and inverted stroke color here */ + if (ps->tool == PAINT_TOOL_DRAW) { + paint_brush_color_get(scene, brush, false, ps->mode == BRUSH_STROKE_INVERT, distance, pressure, ps->paint_color, NULL); + srgb_to_linearrgb_v3_v3(ps->paint_color_linear, ps->paint_color); + } + else if (ps->tool == PAINT_TOOL_FILL) { + copy_v3_v3(ps->paint_color, BKE_brush_color_get(scene, brush)); + srgb_to_linearrgb_v3_v3(ps->paint_color_linear, ps->paint_color); + } + else if (ps->tool == PAINT_TOOL_MASK) { + ps->stencil_value = brush->weight; + + if ((ps->mode == BRUSH_STROKE_INVERT) ^ + ((scene->toolsettings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) != 0)) + { + ps->stencil_value = 1.0f - ps->stencil_value; + } + } + /* continue adding to existing partial redraw rects until redraw */ if (!ps->need_redraw) { for (a = 0; a < ps->image_tot; a++) @@ -4160,30 +4442,24 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int Brush *brush = ps->brush; ps->tool = brush->imagepaint_tool; ps->blend = brush->blend; + /* only check for inversion for the soften tool, elsewhere, a resident brush inversion flag can cause issues */ + if (brush->imagepaint_tool == PAINT_TOOL_SOFTEN) { + ps->mode = ((ps->mode == BRUSH_STROKE_INVERT) ^ ((brush->flag & BRUSH_DIR_IN) != 0) ? + BRUSH_STROKE_INVERT : BRUSH_STROKE_NORMAL); + + ps->blurkernel = paint_new_blur_kernel(brush); + } /* disable for 3d mapping also because painting on mirrored mesh can create "stripes" */ - ps->do_masking = (brush->flag & BRUSH_AIRBRUSH || - (brush->imagepaint_tool == PAINT_TOOL_SMEAR) || - (brush->mtex.tex && !ELEM(brush->mtex.brush_map_mode, MTEX_MAP_MODE_TILED, MTEX_MAP_MODE_STENCIL, MTEX_MAP_MODE_3D))) - ? false : true; + ps->do_masking = paint_use_opacity_masking(brush); ps->is_texbrush = (brush->mtex.tex && brush->imagepaint_tool == PAINT_TOOL_DRAW) ? true : false; - ps->is_maskbrush = false; - ps->is_maskbrush_tiled = false; - if (brush->mask_mtex.tex) { - if (ELEM(brush->mask_mtex.brush_map_mode, MTEX_MAP_MODE_STENCIL, MTEX_MAP_MODE_TILED)) { - ps->is_maskbrush_tiled = true; - } - else { - ps->is_maskbrush = true; - } - } + ps->is_maskbrush = (brush->mask_mtex.tex) ? true : false; } else { /* brush may be NULL*/ ps->do_masking = false; ps->is_texbrush = false; ps->is_maskbrush = false; - ps->is_maskbrush_tiled = false; } /* sizeof(ProjPixel), since we alloc this a _lot_ */ @@ -4198,6 +4474,7 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int ps->scene = scene; ps->ob = ob; /* allow override of active object */ + ps->stencil_ima = settings->imapaint.stencil; /* setup projection painting data */ ps->do_backfacecull = (settings->imapaint.flag & IMAGEPAINT_PROJECT_BACKFACE) ? 0 : 1; ps->do_occlude = (settings->imapaint.flag & IMAGEPAINT_PROJECT_XRAY) ? 0 : 1; @@ -4207,8 +4484,11 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int if (ps->tool == PAINT_TOOL_CLONE) ps->do_layer_clone = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_CLONE) ? 1 : 0; - ps->do_layer_stencil = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL) ? 1 : 0; - ps->do_layer_stencil_inv = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) ? 1 : 0; + ps->do_stencil_brush = ps->brush->imagepaint_tool == PAINT_TOOL_MASK; + /* deactivate stenciling for the stencil brush :) */ + ps->do_layer_stencil = ((settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL) && + !(ps->do_stencil_brush) && ps->stencil_ima); + ps->do_layer_stencil_inv = ((settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) != 0); #ifndef PROJ_DEBUG_NOSEAMBLEED @@ -4236,6 +4516,7 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int mode) { ProjPaintState *ps = MEM_callocN(sizeof(ProjPaintState), "ProjectionPaintState"); + project_state_init(C, ob, ps, mode); if (ps->tool == PAINT_TOOL_CLONE && mode == BRUSH_STROKE_INVERT) { @@ -4268,6 +4549,10 @@ void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int m paint_proj_begin_clone(ps, mouse); + /* special full screen draw mode for fill tool */ + if (ps->tool == PAINT_TOOL_FILL) + ps->source = PROJ_SRC_VIEW_FILL; + return ps; } @@ -4316,8 +4601,11 @@ static int texture_paint_camera_project_exec(bContext *C, wmOperator *op) int orig_brush_size; IDProperty *idgroup; IDProperty *view_data = NULL; + Object *ob = OBACT; - project_state_init(C, OBACT, &ps, BRUSH_STROKE_NORMAL); + paint_proj_mesh_data_ensure(C, ob, op); + + project_state_init(C, ob, &ps, BRUSH_STROKE_NORMAL); if (ps.ob == NULL || ps.ob->type != OB_MESH) { BKE_report(op->reports, RPT_ERROR, "No active mesh object"); @@ -4365,7 +4653,6 @@ static int texture_paint_camera_project_exec(bContext *C, wmOperator *op) /* override */ ps.is_texbrush = false; ps.is_maskbrush = false; - ps.is_maskbrush_tiled = false; ps.do_masking = false; orig_brush_size = BKE_brush_size_get(scene, ps.brush); BKE_brush_size_set(scene, ps.brush, 32); /* cover the whole image */ @@ -4375,7 +4662,7 @@ static int texture_paint_camera_project_exec(bContext *C, wmOperator *op) scene->toolsettings->imapaint.flag |= IMAGEPAINT_DRAWING; ED_undo_paint_push_begin(UNDO_PAINT_IMAGE, op->type->name, - ED_image_undo_restore, ED_image_undo_free); + ED_image_undo_restore, ED_image_undo_free, NULL); /* allocate and initialize spatial data structures */ project_paint_begin(&ps); @@ -4508,3 +4795,132 @@ void PAINT_OT_image_from_view(wmOperatorType *ot) RNA_def_string_file_name(ot->srna, "filepath", NULL, FILE_MAX, "File Path", "Name of the file"); } + +/* Add layer operator */ + +static EnumPropertyItem layer_type_items[] = { + {MAP_COL, "DIFFUSE_COLOR", 0, "Diffuse Color", ""}, + {MAP_REF, "DIFFUSE_INTENSITY", 0, "Diffuse Intensity", ""}, + {MAP_ALPHA, "ALPHA", 0, "Alpha", ""}, + {MAP_TRANSLU, "TRANSLUCENCY", 0, "Translucency", ""}, + {MAP_COLSPEC, "SPECULAR_COLOR", 0, "Specular Color", ""}, + {MAP_SPEC, "SPECULAR_INTENSITY", 0, "Specular Intensity", ""}, + {MAP_HAR, "SPECULAR_HARDNESS", 0, "Specular Hardness", ""}, + {MAP_AMB, "AMBIENT", 0, "Ambient", ""}, + {MAP_EMIT, "EMMIT", 0, "Emmit", ""}, + {MAP_COLMIR, "MIRROR_COLOR", 0, "Mirror Color", ""}, + {MAP_RAYMIRR, "RAYMIRROR", 0, "Ray Mirror", ""}, + {MAP_NORM, "NORMAL", 0, "Normal", ""}, + {MAP_WARP, "WARP", 0, "Warp", ""}, + {MAP_DISPLACE, "DISPLACE", 0, "Displace", ""}, + {0, NULL, 0, NULL, NULL} +}; + +bool proj_paint_add_slot(bContext *C, int type, Material *ma) +{ + Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + int i; + ImagePaintSettings *imapaint = &CTX_data_tool_settings(C)->imapaint; + bool use_nodes = BKE_scene_use_new_shading_nodes(scene); + int width; + int height; + + if (!ob) + return false; + + /* should not be allowed, but just in case */ + if (imapaint->slot_xresolution_default == 0) + imapaint->slot_xresolution_default = 1024; + if (imapaint->slot_yresolution_default == 0) + imapaint->slot_yresolution_default = 1024; + + width = imapaint->slot_xresolution_default; + height = imapaint->slot_yresolution_default; + + if (!ma) + ma = give_current_material(ob, ob->actcol); + + if (ma) { + + if (use_nodes) { + /* not supported for now */ + } + else { + MTex *mtex = add_mtex_id(&ma->id, -1); + + /* successful creation of mtex layer, now create set */ + if (mtex) { + Main *bmain = CTX_data_main(C); + Image *ima; + const char *name; + + /* get the name of the texture layer type */ + i = RNA_enum_from_value(layer_type_items, type); + BLI_assert(i != -1); + name = layer_type_items[i].name; + + mtex->tex = add_texture(bmain, DATA_(name)); + mtex->mapto = type; + + if (mtex->tex) { + char imagename[FILE_MAX]; + float color[4]; + bool use_float = type == MAP_NORM; + + copy_v4_v4(color, imapaint->slot_color_default); + if (use_float) { + mul_v3_fl(color, color[3]); + } + else { + /* crappy workaround because we only upload straight color to OpenGL and that makes + * painting result on viewport too opaque */ + color[3] = 1.0; + } + + /* take the second letter to avoid the ID identifier */ + BLI_snprintf(imagename, FILE_MAX, "%s_%s", &ma->id.name[2], name); + + ima = mtex->tex->ima = BKE_image_add_generated(bmain, width, height, imagename, 32, use_float, + IMA_GENTYPE_BLANK, color); + + BKE_texpaint_slot_refresh_cache(ma, false); + BKE_image_signal(ima, NULL, IMA_SIGNAL_USER_NEW_IMAGE); + WM_event_add_notifier(C, NC_TEXTURE | NA_ADDED, mtex->tex); + WM_event_add_notifier(C, NC_IMAGE | NA_ADDED, ima); + ED_area_tag_redraw(CTX_wm_area(C)); + } + + WM_event_add_notifier(C, NC_TEXTURE, CTX_data_scene(C)); + return true; + } + } + } + + return false; +} + +static int texture_paint_add_texture_paint_slot_exec(bContext *C, wmOperator *op) +{ + int type = RNA_enum_get(op->ptr, "type"); + + return proj_paint_add_slot(C, type, NULL) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +void PAINT_OT_add_texture_paint_slot(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Texture Paint Slot"; + ot->description = "Add a texture paint slot"; + ot->idname = "PAINT_OT_add_texture_paint_slot"; + + /* api callbacks */ + ot->exec = texture_paint_add_texture_paint_slot_exec; + ot->poll = ED_operator_region_view3d_active; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "type", layer_type_items, 0, "Type", "Merge method to use"); +} |