Merging r58475 through r58700 from trunk into soc-2013-depsgraph_mt
[blender.git] / source / blender / editors / sculpt_paint / paint_image_proj.c
index e594bf25d0cc5eb5e55990d531bc59053f7939a7..db55dc271f1f8d6a64d8e3a6c81b84b4d92dbfce 100644 (file)
@@ -24,7 +24,7 @@
  * ***** END GPL LICENSE BLOCK *****
  */
 
-/** \file blender/editors/sculpt_paint/paint_image.c
+/** \file blender/editors/sculpt_paint/paint_image_proj.c
  *  \ingroup edsculpt
  *  \brief Functions to paint images in 2D and 3D.
  */
 #  include "BLI_winstuff.h"
 #endif
 
-#include "BLI_math.h"
 #include "BLI_blenlib.h"
 #include "BLI_linklist.h"
+#include "BLI_math.h"
+#include "BLI_math_color_blend.h"
 #include "BLI_memarena.h"
 #include "BLI_threads.h"
 #include "BLI_utildefines.h"
@@ -74,7 +75,7 @@
 #include "BKE_scene.h"
 #include "BKE_colortools.h"
 
-#include "BKE_tessmesh.h"
+#include "BKE_editmesh.h"
 
 #include "BIF_gl.h"
 #include "BIF_glutil.h"
@@ -88,6 +89,8 @@
 #include "ED_view3d.h"
 #include "ED_mesh.h"
 
+#include "GPU_extensions.h"
+
 #include "WM_api.h"
 #include "WM_types.h"
 
@@ -108,26 +111,6 @@ BLI_INLINE unsigned char f_to_char(const float val)
        return FTOCHAR(val);
 }
 
-#define IMAPAINT_FLOAT_RGBA_TO_CHAR(c, f)  {                                  \
-       (c)[0] = f_to_char((f)[0]);                                               \
-       (c)[1] = f_to_char((f)[1]);                                               \
-       (c)[2] = f_to_char((f)[2]);                                               \
-       (c)[3] = f_to_char((f)[3]);                                               \
-} (void)0
-
-#define IMAPAINT_CHAR_RGBA_TO_FLOAT(f, c)  {                                  \
-       (f)[0] = IMAPAINT_CHAR_TO_FLOAT((c)[0]);                                   \
-       (f)[1] = IMAPAINT_CHAR_TO_FLOAT((c)[1]);                                   \
-       (f)[2] = IMAPAINT_CHAR_TO_FLOAT((c)[2]);                                   \
-       (f)[3] = IMAPAINT_CHAR_TO_FLOAT((c)[3]);                                   \
-} (void)0
-
-#define IMAPAINT_FLOAT_RGB_TO_CHAR(c, f)  {                                   \
-       (c)[0] = f_to_char((f)[0]);                                               \
-       (c)[1] = f_to_char((f)[1]);                                               \
-       (c)[2] = f_to_char((f)[2]);                                               \
-} (void)0
-
 /* ProjectionPaint defines */
 
 /* approx the number of buckets to have under the brush,
@@ -205,7 +188,8 @@ typedef struct ProjPaintState {
        int source; /* PROJ_SRC_**** */
 
        Brush *brush;
-       short tool, blend;
+       short tool, blend, mode;
+       int orig_brush_size;
        Object *ob;
        /* end similarities with ImagePaintState */
 
@@ -261,9 +245,12 @@ typedef struct ProjPaintState {
        float normal_angle_inner;
        float normal_angle_range;       /* difference between normal_angle and normal_angle_inner, for easy access */
 
-       short is_ortho;
+       bool do_face_sel;               /* quick access to (me->editflag & ME_EDIT_PAINT_FACE_SEL) */
+       bool is_ortho;
        bool do_masking;              /* use masking during painting. Some operations such as airbrush may disable */
-       short is_texbrush;              /* only to avoid running  */
+       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
@@ -279,12 +266,14 @@ typedef struct ProjPaintState {
        Image *reproject_image;
        ImBuf *reproject_ibuf;
 
-
        /* threads */
        int thread_tot;
        int bucketMin[2];
        int bucketMax[2];
        int context_bucket_x, context_bucket_y; /* must lock threads while accessing these */
+
+       /* redraw */
+       bool need_redraw;
 } ProjPaintState;
 
 typedef union pixelPointer {
@@ -305,8 +294,8 @@ typedef struct ProjPixel {
        /* Only used when the airbrush is disabled.
         * 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_max */
-       unsigned short mask_max;
+        * new mask value is lower then mask_accum */
+       unsigned short mask_accum;
 
        /* for various reasons we may want to mask out painting onto this pixel */
        unsigned short mask;
@@ -317,7 +306,7 @@ typedef struct ProjPixel {
        PixelStore newColor;
        PixelPointer pixel;
 
-       short image_index; /* if anyone wants to paint onto more then 32768 images they can bite me */
+       short image_index; /* if anyone wants to paint onto more than 32768 images they can bite me */
        unsigned char bb_cell_index;
 } ProjPixel;
 
@@ -340,117 +329,6 @@ static const float proj_pixel_soften_v2[PROJ_PIXEL_SOFTEN_TOT][2] = {
 
 /* Finish projection painting structs */
 
-typedef struct UndoImageTile {
-       struct UndoImageTile *next, *prev;
-
-       char idname[MAX_ID_NAME];  /* name instead of pointer*/
-       char ibufname[IB_FILENAME_SIZE];
-
-       union {
-               float        *fp;
-               unsigned int *uint;
-               void         *pt;
-       } rect;
-       int x, y;
-
-       short source, use_float;
-       char gen_type;
-} UndoImageTile;
-
-/* UNDO */
-
-static void undo_copy_tile(UndoImageTile *tile, ImBuf *tmpibuf, ImBuf *ibuf, int restore)
-{
-       /* copy or swap contents of tile->rect and region in ibuf->rect */
-       IMB_rectcpy(tmpibuf, ibuf, 0, 0, tile->x * IMAPAINT_TILE_SIZE,
-                   tile->y * IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE);
-
-       if (ibuf->rect_float) {
-               SWAP(float *, tmpibuf->rect_float, tile->rect.fp);
-       }
-       else {
-               SWAP(unsigned int *, tmpibuf->rect, tile->rect.uint);
-       }
-
-       if (restore)
-               IMB_rectcpy(ibuf, tmpibuf, tile->x * IMAPAINT_TILE_SIZE,
-                           tile->y * IMAPAINT_TILE_SIZE, 0, 0, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE);
-}
-
-static void image_undo_restore(bContext *C, ListBase *lb)
-{
-       Main *bmain = CTX_data_main(C);
-       Image *ima = NULL;
-       ImBuf *ibuf, *tmpibuf;
-       UndoImageTile *tile;
-
-       tmpibuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32,
-                                IB_rectfloat | IB_rect);
-
-       for (tile = lb->first; tile; tile = tile->next) {
-               short use_float;
-
-               /* find image based on name, pointer becomes invalid with global undo */
-               if (ima && strcmp(tile->idname, ima->id.name) == 0) {
-                       /* ima is valid */
-               }
-               else {
-                       ima = BLI_findstring(&bmain->image, tile->idname, offsetof(ID, name));
-               }
-
-               ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
-
-               if (ima && ibuf && strcmp(tile->ibufname, ibuf->name) != 0) {
-                       /* current ImBuf filename was changed, probably current frame
-                        * was changed when paiting on image sequence, rather than storing
-                        * full image user (which isn't so obvious, btw) try to find ImBuf with
-                        * matched file name in list of already loaded images */
-
-                       BKE_image_release_ibuf(ima, ibuf, NULL);
-
-                       ibuf = BLI_findstring(&ima->ibufs, tile->ibufname, offsetof(ImBuf, name));
-               }
-
-               if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) {
-                       BKE_image_release_ibuf(ima, ibuf, NULL);
-                       continue;
-               }
-
-               if (ima->gen_type != tile->gen_type || ima->source != tile->source) {
-                       BKE_image_release_ibuf(ima, ibuf, NULL);
-                       continue;
-               }
-
-               use_float = ibuf->rect_float ? 1 : 0;
-
-               if (use_float != tile->use_float) {
-                       BKE_image_release_ibuf(ima, ibuf, NULL);
-                       continue;
-               }
-
-               undo_copy_tile(tile, tmpibuf, ibuf, 1);
-
-               GPU_free_image(ima); /* force OpenGL reload */
-               if (ibuf->rect_float)
-                       ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */
-               if (ibuf->mipmap[0])
-                       ibuf->userflags |= IB_MIPMAP_INVALID;  /* force mipmap recreatiom */
-               ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
-
-               BKE_image_release_ibuf(ima, ibuf, NULL);
-       }
-
-       IMB_freeImBuf(tmpibuf);
-}
-
-static void image_undo_free(ListBase *lb)
-{
-       UndoImageTile *tile;
-
-       for (tile = lb->first; tile; tile = tile->next)
-               MEM_freeN(tile->rect.pt);
-}
-
 static Image *project_paint_face_image(const ProjPaintState *ps, MTFace *dm_mtface, int face_index)
 {
        Image *ima;
@@ -560,7 +438,7 @@ static float VecZDepthPersp(const float pt[2],
 
 
 /* Return the top-most face index that the screen space coord 'pt' touches (or -1) */
-static int project_paint_PickFace(const ProjPaintState *ps, float pt[2], float w[3], int *side)
+static int project_paint_PickFace(const ProjPaintState *ps, const float pt[2], float w[3], int *side)
 {
        LinkNode *node;
        float w_tmp[3];
@@ -622,7 +500,7 @@ static int project_paint_PickFace(const ProjPaintState *ps, float pt[2], float w
 }
 
 /* Converts a uv coord into a pixel location wrapping if the uv is outside 0-1 range */
-static void uvco_to_wrapped_pxco(float uv[2], int ibuf_x, int ibuf_y, float *x, float *y)
+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);
@@ -636,7 +514,8 @@ static void uvco_to_wrapped_pxco(float uv[2], int ibuf_x, int ibuf_y, float *x,
 }
 
 /* Set the top-most face color that the screen space coord 'pt' touches (or return 0 if none touch) */
-static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float *rgba_fp, unsigned char *rgba, const int interp)
+static int project_paint_PickColor(const ProjPaintState *ps, const float pt[2],
+                                   float *rgba_fp, unsigned char *rgba, const int interp)
 {
        float w[3], uv[2];
        int side;
@@ -676,7 +555,7 @@ static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float
                        else {
                                float rgba_tmp_f[4];
                                bilinear_interpolation_color_wrap(ibuf, NULL, rgba_tmp_f, x, y);
-                               IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_f);
+                               premul_float_to_straight_uchar(rgba, rgba_tmp_f);
                        }
                }
                else {
@@ -686,7 +565,7 @@ static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float
                        else {
                                unsigned char rgba_tmp[4];
                                bilinear_interpolation_color_wrap(ibuf, rgba_tmp, NULL, x, y);
-                               IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, rgba_tmp);
+                               straight_uchar_to_premul_float(rgba_fp, rgba_tmp);
                        }
                }
        }
@@ -705,7 +584,7 @@ static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float
                if (rgba) {
                        if (ibuf->rect_float) {
                                float *rgba_tmp_fp = ibuf->rect_float + (xi + yi * ibuf->x * 4);
-                               IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_fp);
+                               premul_float_to_straight_uchar(rgba, rgba_tmp_fp);
                        }
                        else {
                                *((unsigned int *)rgba) = *(unsigned int *)(((char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4));
@@ -717,8 +596,8 @@ static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float
                                copy_v4_v4(rgba_fp, (ibuf->rect_float + ((xi + yi * ibuf->x) * 4)));
                        }
                        else {
-                               char *tmp_ch = ((char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4);
-                               IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, tmp_ch);
+                               unsigned char *tmp_ch = ((unsigned char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4);
+                               straight_uchar_to_premul_float(rgba_fp, tmp_ch);
                        }
                }
        }
@@ -1061,7 +940,8 @@ static int check_seam(const ProjPaintState *ps, const int orig_face, const int o
 /* Calculate outset UV's, this is not the same as simply scaling the UVs,
  * since the outset coords are a margin that keep an even distance from the original UV's,
  * note that the image aspect is taken into account */
-static void uv_image_outset(float (*orig_uv)[2], float (*outset_uv)[2], const float scaler, const int ibuf_x, const int ibuf_y, const int is_quad)
+static void uv_image_outset(float (*orig_uv)[2], float (*outset_uv)[2], const float scaler,
+                            const int ibuf_x, const int ibuf_y, const int is_quad)
 {
        float a1, a2, a3, a4 = 0.0f;
        float puv[4][2]; /* pixelspace uv's */
@@ -1242,7 +1122,46 @@ static void screen_px_from_persp(
        interp_v3_v3v3v3(pixelScreenCo, v1co, v2co, v3co, w);
 }
 
-static void project_face_pixel(const MTFace *tf_other, ImBuf *ibuf_other, const float w[3], int side, unsigned char rgba_ub[4], float rgba_f[4])
+
+/* same as screen_px_from_persp except we return ortho weights back to the caller.
+ * These weights will be used to determine correct interpolation of uvs in cloned uv layer */
+static void screen_px_from_persp_ortho_weights(
+        float uv[2],
+        float v1co[4], float v2co[4], float v3co[4],  /* screenspace coords */
+        float uv1co[2], float uv2co[2], float uv3co[2],
+        float pixelScreenCo[4],
+        float w[3])
+{
+       float w_int[3];
+       float wtot_inv, wtot;
+       barycentric_weights_v2(uv1co, uv2co, uv3co, uv, w);
+
+       /* re-weight from the 4th coord of each screen vert */
+       w_int[0] = w[0] * v1co[3];
+       w_int[1] = w[1] * v2co[3];
+       w_int[2] = w[2] * v3co[3];
+
+       wtot = w_int[0] + w_int[1] + w_int[2];
+
+       if (wtot > 0.0f) {
+               wtot_inv = 1.0f / wtot;
+               w_int[0] *= wtot_inv;
+               w_int[1] *= wtot_inv;
+               w_int[2] *= wtot_inv;
+       }
+       else {
+               w[0] = w[1] = w[2] =
+               w_int[0] = w_int[1] = w_int[2] = 1.0f / 3.0f;  /* dummy values for zero area face */
+       }
+       /* done re-weighting */
+
+       /* do interpolation based on projected weight */
+       interp_v3_v3v3v3(pixelScreenCo, v1co, v2co, v3co, w_int);
+}
+
+
+static void project_face_pixel(const MTFace *tf_other, ImBuf *ibuf_other, const float w[3],
+                               int side, unsigned char rgba_ub[4], float rgba_f[4])
 {
        float *uvCo1, *uvCo2, *uvCo3;
        float uv_other[2], x, y;
@@ -1296,10 +1215,10 @@ static float project_paint_uvpixel_mask(
                        project_face_pixel(tf_other, ibuf_other, w, side, rgba_ub, rgba_f);
 
                        if (ibuf_other->rect_float) { /* from float to float */
-                               mask = ((rgba_f[0] + rgba_f[1] + rgba_f[2]) / 3.0f) * rgba_f[3];
+                               mask = ((rgba_f[0] + rgba_f[1] + rgba_f[2]) * (1.0f / 3.0f)) * rgba_f[3];
                        }
                        else { /* from char to float */
-                               mask = ((rgba_ub[0] + rgba_ub[1] + rgba_ub[2]) / (256 * 3.0f)) * (rgba_ub[3] / 256.0f);
+                               mask = ((rgba_ub[0] + rgba_ub[1] + rgba_ub[2]) * (1.0f / (255.0f * 3.0f))) * (rgba_ub[3] * (1.0f / 255.0f));
                        }
 
                        BKE_image_release_ibuf(other_tpage, ibuf_other, NULL);
@@ -1443,14 +1362,16 @@ static ProjPixel *project_paint_uvpixel_init(
 
        if (ibuf->rect_float) {
                projPixel->pixel.f_pt = ibuf->rect_float + ((x_px + y_px * ibuf->x) * 4);
-               projPixel->origColor.f[0] = projPixel->newColor.f[0] = projPixel->pixel.f_pt[0];
-               projPixel->origColor.f[1] = projPixel->newColor.f[1] = projPixel->pixel.f_pt[1];
-               projPixel->origColor.f[2] = projPixel->newColor.f[2] = projPixel->pixel.f_pt[2];
-               projPixel->origColor.f[3] = projPixel->newColor.f[3] = projPixel->pixel.f_pt[3];
+               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];
+               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->newColor.uint = *projPixel->pixel.uint_pt;
+               projPixel->origColor.uint = *projPixel->pixel.uint_pt;
+               projPixel->newColor.uint = 0;
        }
 
        /* screenspace unclamped, we could keep its z and w values but don't need them at the moment */
@@ -1464,7 +1385,7 @@ static ProjPixel *project_paint_uvpixel_init(
        projPixel->y_px = y_px;
 
        projPixel->mask = (unsigned short)(mask * 65535);
-       projPixel->mask_max = 0;
+       projPixel->mask_accum = 0;
 
        /* which bounding box cell are we in?, needed for undo */
        projPixel->bb_cell_index = ((int)(((float)x_px / (float)ibuf->x) * PROJ_BOUNDBOX_DIV)) +
@@ -1486,15 +1407,18 @@ static ProjPixel *project_paint_uvpixel_init(
                                        }
                                        else { /* from char to float */
                                                unsigned char rgba_ub[4];
+                                               float rgba[4];
                                                project_face_pixel(tf_other, ibuf_other, w, side, rgba_ub, NULL);
-                                               IMAPAINT_CHAR_RGBA_TO_FLOAT(((ProjPixelClone *)projPixel)->clonepx.f, rgba_ub);
+                                               srgb_to_linearrgb_uchar4(rgba, rgba_ub);
+                                               straight_to_premul_v4_v4(((ProjPixelClone *)projPixel)->clonepx.f, rgba);
                                        }
                                }
                                else {
                                        if (ibuf_other->rect_float) { /* float to char */
                                                float rgba[4];
                                                project_face_pixel(tf_other, ibuf_other, w, side, NULL, rgba);
-                                               IMAPAINT_FLOAT_RGBA_TO_CHAR(((ProjPixelClone *)projPixel)->clonepx.ch, rgba);
+                                               premul_to_straight_v4(rgba);
+                                               linearrgb_to_srgb_uchar3(((ProjPixelClone *)projPixel)->clonepx.ch, rgba);
                                        }
                                        else { /* char to char */
                                                project_face_pixel(tf_other, ibuf_other, w, side, ((ProjPixelClone *)projPixel)->clonepx.ch, NULL);
@@ -1703,9 +1627,9 @@ static int line_clip_rect2f(
 static void scale_quad(float insetCos[4][3], float *origCos[4], const float inset)
 {
        float cent[3];
-       cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0] + origCos[3][0]) / 4.0f;
-       cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1] + origCos[3][1]) / 4.0f;
-       cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2] + origCos[3][2]) / 4.0f;
+       cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0] + origCos[3][0]) * (1.0f / 4.0f);
+       cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1] + origCos[3][1]) * (1.0f / 4.0f);
+       cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2] + origCos[3][2]) * (1.0f / 4.0f);
 
        sub_v3_v3v3(insetCos[0], origCos[0], cent);
        sub_v3_v3v3(insetCos[1], origCos[1], cent);
@@ -1727,9 +1651,9 @@ static void scale_quad(float insetCos[4][3], float *origCos[4], const float inse
 static void scale_tri(float insetCos[4][3], float *origCos[4], const float inset)
 {
        float cent[3];
-       cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0]) / 3.0f;
-       cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1]) / 3.0f;
-       cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2]) / 3.0f;
+       cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0]) * (1.0f / 3.0f);
+       cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1]) * (1.0f / 3.0f);
+       cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2]) * (1.0f / 3.0f);
 
        sub_v3_v3v3(insetCos[0], origCos[0], cent);
        sub_v3_v3v3(insetCos[1], origCos[1], cent);
@@ -2283,6 +2207,7 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i
        float uv_clip[8][2];
        int uv_clip_tot;
        const short is_ortho = ps->is_ortho;
+       const short is_clone_other = ((ps->brush->imagepaint_tool == PAINT_TOOL_CLONE) && ps->dm_mtface_clone);
        const short do_backfacecull = ps->do_backfacecull;
        const short do_clip = ps->rv3d ? ps->rv3d->rflag & RV3D_CLIPPING : 0;
 
@@ -2294,8 +2219,8 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i
        /* Use tf_uv_pxoffset instead of tf->uv so we can offset the UV half a pixel
         * this is done so we can avoid offsetting all the pixels by 0.5 which causes
         * problems when wrapping negative coords */
-       xhalfpx = (0.5f + (PROJ_GEOM_TOLERANCE / 3.0f)) / ibuf_xf;
-       yhalfpx = (0.5f + (PROJ_GEOM_TOLERANCE / 4.0f)) / ibuf_yf;
+       xhalfpx = (0.5f + (PROJ_GEOM_TOLERANCE * (1.0f / 3.0f))) / ibuf_xf;
+       yhalfpx = (0.5f + (PROJ_GEOM_TOLERANCE * (1.0f / 4.0f))) / ibuf_yf;
 
        /* Note about (PROJ_GEOM_TOLERANCE/x) above...
         * Needed to add this offset since UV coords are often quads aligned to pixels.
@@ -2306,8 +2231,6 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i
         * but since the first thing most people try is painting onto a quad- better make it work.
         */
 
-
-
        tf_uv_pxoffset[0][0] = tf->uv[0][0] - xhalfpx;
        tf_uv_pxoffset[0][1] = tf->uv[0][1] - yhalfpx;
 
@@ -2392,7 +2315,8 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i
                                                has_x_isect = has_isect = 1;
 
                                                if (is_ortho) screen_px_from_ortho(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
-                                               else          screen_px_from_persp(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
+                                               else if (is_clone_other) screen_px_from_persp_ortho_weights(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
+                                               else screen_px_from_persp(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
 
                                                /* a pity we need to get the worldspace pixel location here */
                                                if (do_clip || do_3d_mapping) {
@@ -2578,8 +2502,8 @@ static void project_paint_face_init(const ProjPaintState *ps, const int thread_i
                                                                                if (!is_ortho) {
                                                                                        pixelScreenCo[3] = 1.0f;
                                                                                        mul_m4_v4((float(*)[4])ps->projectMat, pixelScreenCo); /* cast because of const */
-                                                                                       pixelScreenCo[0] = (float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * pixelScreenCo[0] / pixelScreenCo[3];
-                                                                                       pixelScreenCo[1] = (float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * pixelScreenCo[1] / pixelScreenCo[3];
+                                                                                       pixelScreenCo[0] = (float)(ps->winx * 0.5f) + (ps->winx * 0.5f) * pixelScreenCo[0] / pixelScreenCo[3];
+                                                                                       pixelScreenCo[1] = (float)(ps->winy * 0.5f) + (ps->winy * 0.5f) * pixelScreenCo[1] / pixelScreenCo[3];
                                                                                        pixelScreenCo[2] = pixelScreenCo[2] / pixelScreenCo[3]; /* Use the depth for bucket point occlusion */
                                                                                }
 
@@ -2867,19 +2791,6 @@ static void project_paint_delayed_face_init(ProjPaintState *ps, const MFace *mf,
 #endif
 }
 
-static int project_paint_view_clip(View3D *v3d, RegionView3D *rv3d, float *clipsta, float *clipend)
-{
-       int orth = ED_view3d_clip_range_get(v3d, rv3d, clipsta, clipend);
-
-       if (orth) { /* only needed for ortho */
-               float fac = 2.0f / ((*clipend) - (*clipsta));
-               *clipsta *= fac;
-               *clipend *= fac;
-       }
-
-       return orth;
-}
-
 /* run once per stroke before projection painting */
 static void project_paint_begin(ProjPaintState *ps)
 {
@@ -2899,22 +2810,32 @@ static void project_paint_begin(ProjPaintState *ps)
        Image *tpage_last = NULL, *tpage;
 
        /* Face vars */
+       MPoly *mpoly_orig;
        MFace *mf;
        MTFace *tf;
 
        int a, i; /* generic looping vars */
        int image_index = -1, face_index;
+
+       /* double lookup */
+       const int *index_mf_to_mpoly = NULL;
+       const int *index_mp_to_orig  = NULL;
+
        MVert *mv;
 
        MemArena *arena; /* at the moment this is just ps->arena_mt[0], but use this to show were not multithreading */
 
        const int diameter = 2 * BKE_brush_size_get(ps->scene, ps->brush);
 
+       bool reset_threads = false;
+
        /* ---- end defines ---- */
 
        if (ps->source == PROJ_SRC_VIEW)
                ED_view3d_clipping_local(ps->rv3d, ps->ob->obmat);  /* faster clipping lookups */
 
+       ps->do_face_sel = ((((Mesh *)ps->ob->data)->editflag & ME_EDIT_PAINT_FACE_SEL) != 0);
+
        /* paint onto the derived mesh */
 
        /* Workaround for subsurf selection, try the display mesh first */
@@ -2923,12 +2844,17 @@ static void project_paint_begin(ProjPaintState *ps)
                ps->dm = mesh_create_derived_render(ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE);
                ps->dm_release = TRUE;
        }
-       else if (ps->ob->derivedFinal && CustomData_has_layer(&ps->ob->derivedFinal->faceData, CD_MTFACE)) {
+       else if (ps->ob->derivedFinal &&
+                CustomData_has_layer(&ps->ob->derivedFinal->faceData, CD_MTFACE) &&
+                (ps->do_face_sel == false || CustomData_has_layer(&ps->ob->derivedFinal->polyData, CD_ORIGINDEX)))
+       {
                ps->dm = ps->ob->derivedFinal;
                ps->dm_release = FALSE;
        }
        else {
-               ps->dm = mesh_get_derived_final(ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE);
+               ps->dm = mesh_get_derived_final(
+                            ps->scene, ps->ob,
+                            ps->scene->customdata_mask | CD_MASK_MTFACE | (ps->do_face_sel ? CD_ORIGINDEX : 0));
                ps->dm_release = TRUE;
        }
 
@@ -2948,6 +2874,20 @@ static void project_paint_begin(ProjPaintState *ps)
        ps->dm_totvert = ps->dm->getNumVerts(ps->dm);
        ps->dm_totface = ps->dm->getNumTessFaces(ps->dm);
 
+       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);
+               if (index_mf_to_mpoly == NULL) {
+                       index_mp_to_orig = NULL;
+               }
+               else {
+                       mpoly_orig = ((Mesh *)ps->ob->data)->mpoly;
+               }
+       }
+       else {
+               mpoly_orig = NULL;
+       }
+
        /* use clone mtface? */
 
 
@@ -2955,20 +2895,19 @@ static void project_paint_begin(ProjPaintState *ps)
         * 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)->fdata, 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;
-                       printf("ACK!\n");
                }
        }
 
        if (ps->do_layer_stencil) {
                //int layer_num = CustomData_get_stencil_layer(&ps->dm->faceData, CD_MTFACE);
-               int layer_num = CustomData_get_stencil_layer(&((Mesh *)ps->ob->data)->fdata, 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);
 
@@ -3010,7 +2949,7 @@ static void project_paint_begin(ProjPaintState *ps)
 
                        ED_view3d_ob_project_mat_get(ps->rv3d, ps->ob, ps->projectMat);
 
-                       ps->is_ortho = project_paint_view_clip(ps->v3d, ps->rv3d, &ps->clipsta, &ps->clipend);
+                       ps->is_ortho = ED_view3d_clip_range_get(ps->v3d, ps->rv3d, &ps->clipsta, &ps->clipend, true);
                }
                else {
                        /* re-projection */
@@ -3058,8 +2997,8 @@ static void project_paint_begin(ProjPaintState *ps)
                        }
 
                        /* same as #ED_view3d_ob_project_mat_get */
-                       mult_m4_m4m4(vmat, viewmat, ps->ob->obmat);
-                       mult_m4_m4m4(ps->projectMat, winmat, vmat);
+                       mul_m4_m4m4(vmat, viewmat, ps->ob->obmat);
+                       mul_m4_m4m4(ps->projectMat, winmat, vmat);
                }
 
 
@@ -3090,8 +3029,8 @@ static void project_paint_begin(ProjPaintState *ps)
                        mul_v3_m4v3(projScreenCo, ps->projectMat, mv->co);
 
                        /* screen space, not clamped */
-                       projScreenCo[0] = (float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * projScreenCo[0];
-                       projScreenCo[1] = (float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * projScreenCo[1];
+                       projScreenCo[0] = (float)(ps->winx * 0.5f) + (ps->winx * 0.5f) * projScreenCo[0];
+                       projScreenCo[1] = (float)(ps->winy * 0.5f) + (ps->winy * 0.5f) * projScreenCo[1];
                        minmax_v2v2_v2(ps->screenMin, ps->screenMax, projScreenCo);
                }
        }
@@ -3104,8 +3043,8 @@ static void project_paint_begin(ProjPaintState *ps)
 
                        if (projScreenCo[3] > ps->clipsta) {
                                /* screen space, not clamped */
-                               projScreenCo[0] = (float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * projScreenCo[0] / projScreenCo[3];
-                               projScreenCo[1] = (float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * projScreenCo[1] / projScreenCo[3];
+                               projScreenCo[0] = (float)(ps->winx * 0.5f) + (ps->winx * 0.5f) * projScreenCo[0] / projScreenCo[3];
+                               projScreenCo[1] = (float)(ps->winy * 0.5f) + (ps->winy * 0.5f) * projScreenCo[1] / projScreenCo[3];
                                projScreenCo[2] = projScreenCo[2] / projScreenCo[3]; /* Use the depth for bucket point occlusion */
                                minmax_v2v2_v2(ps->screenMin, ps->screenMax, projScreenCo);
                        }
@@ -3155,6 +3094,10 @@ static void project_paint_begin(ProjPaintState *ps)
 
        /* printf("\tscreenspace bucket division x:%d y:%d\n", ps->buckets_x, ps->buckets_y); */
 
+       if (ps->buckets_x > PROJ_BUCKET_RECT_MAX || ps->buckets_y > PROJ_BUCKET_RECT_MAX) {
+               reset_threads = true;
+       }
+
        /* really high values could cause problems since it has to allocate a few
         * (ps->buckets_x*ps->buckets_y) sized arrays  */
        CLAMP(ps->buckets_x, PROJ_BUCKET_RECT_MIN, PROJ_BUCKET_RECT_MAX);
@@ -3178,12 +3121,13 @@ static void project_paint_begin(ProjPaintState *ps)
         * threads is being able to fill in multiple buckets at once.
         * Only use threads for bigger brushes. */
 
-       if (ps->scene->r.mode & R_FIXED_THREADS) {
-               ps->thread_tot = ps->scene->r.threads;
-       }
-       else {
-               ps->thread_tot = BLI_system_thread_count();
-       }
+       ps->thread_tot = BKE_scene_num_threads(ps->scene);
+
+       /* workaround for #35057, disable threading if diameter is less than is possible for
+        * optimum bucket number generation */
+       if (reset_threads)
+               ps->thread_tot = 1;
+
        for (a = 0; a < ps->thread_tot; a++) {
                ps->arena_mt[a] = BLI_memarena_new(1 << 16, "project paint arena");
        }
@@ -3213,8 +3157,8 @@ static void project_paint_begin(ProjPaintState *ps)
                }
        }
 
-
        for (face_index = 0, tf = ps->dm_mtface, mf = ps->dm_mface; face_index < ps->dm_totface; mf++, tf++, face_index++) {
+               bool is_face_sel;
 
 #ifndef PROJ_DEBUG_NOSEAMBLEED
                /* add face user if we have bleed enabled, set the UV seam flags later */
@@ -3229,10 +3173,23 @@ static void project_paint_begin(ProjPaintState *ps)
                }
 #endif
 
-               tpage = project_paint_face_image(ps, ps->dm_mtface, face_index);
-
-               if (tpage && ((((Mesh *)ps->ob->data)->editflag & ME_EDIT_PAINT_FACE_SEL) == 0 || mf->flag & ME_FACE_SEL)) {
+               if (ps->do_face_sel) {
+                       int orig_index;
+                       if (index_mp_to_orig && ((orig_index = DM_origindex_mface_mpoly(index_mf_to_mpoly, index_mp_to_orig,
+                                                                                       face_index))) != ORIGINDEX_NONE)
+                       {
+                               MPoly *mp = &mpoly_orig[orig_index];
+                               is_face_sel = ((mp->flag & ME_FACE_SEL) != 0);
+                       }
+                       else {
+                               is_face_sel = ((mf->flag & ME_FACE_SEL) != 0);
+                       }
+               }
+               else {
+                       is_face_sel = true;
+               }
 
+               if (is_face_sel && (tpage = project_paint_face_image(ps, ps->dm_mtface, face_index))) {
                        float *v1coSS, *v2coSS, *v3coSS, *v4coSS = NULL;
 
                        v1coSS = ps->screenCoords[mf->v1];
@@ -3341,7 +3298,7 @@ static void project_paint_begin(ProjPaintState *ps)
        BLI_linklist_free(image_LinkList, NULL);
 }
 
-static void project_paint_begin_clone(ProjPaintState *ps, int mouse[2])
+static void paint_proj_begin_clone(ProjPaintState *ps, const float mouse[2])
 {
        /* setup clone offset */
        if (ps->tool == PAINT_TOOL_CLONE) {
@@ -3351,8 +3308,8 @@ static void project_paint_begin_clone(ProjPaintState *ps, int mouse[2])
 
                projCo[3] = 1.0f;
                mul_m4_v4(ps->projectMat, projCo);
-               ps->cloneOffset[0] = mouse[0] - ((float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * projCo[0] / projCo[3]);
-               ps->cloneOffset[1] = mouse[1] - ((float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * projCo[1] / projCo[3]);
+               ps->cloneOffset[0] = mouse[0] - ((float)(ps->winx * 0.5f) + (ps->winx * 0.5f) * projCo[0] / projCo[3]);
+               ps->cloneOffset[1] = mouse[1] - ((float)(ps->winy * 0.5f) + (ps->winy * 0.5f) * projCo[1] / projCo[3]);
        }
 }
 
@@ -3647,72 +3604,42 @@ typedef struct ProjectHandle {
        struct ImagePool *pool;
 } ProjectHandle;
 
-static void blend_color_mix(unsigned char cp[4], const unsigned char cp1[4], const unsigned char cp2[4], const int fac)
-{
-       /* this and other blending modes previously used >>8 instead of /255. both
-        * are not equivalent (>>8 is /256), and the former results in rounding
-        * errors that can turn colors black fast after repeated blending */
-       const int mfac = 255 - fac;
-
-       cp[0] = (mfac * cp1[0] + fac * cp2[0]) / 255;
-       cp[1] = (mfac * cp1[1] + fac * cp2[1]) / 255;
-       cp[2] = (mfac * cp1[2] + fac * cp2[2]) / 255;
-       cp[3] = (mfac * cp1[3] + fac * cp2[3]) / 255;
-}
-
-static void blend_color_mix_float(float cp[4], const float cp1[4], const float cp2[4], const float fac)
-{
-       const float mfac = 1.0f - fac;
-       cp[0] = mfac * cp1[0] + fac * cp2[0];
-       cp[1] = mfac * cp1[1] + fac * cp2[1];
-       cp[2] = mfac * cp1[2] + fac * cp2[2];
-       cp[3] = mfac * cp1[3] + fac * cp2[3];
-}
-
-static void blend_color_mix_accum(unsigned char cp[4], const unsigned char cp1[4], const unsigned char cp2[4], const int fac)
-{
-       /* this and other blending modes previously used >>8 instead of /255. both
-        * are not equivalent (>>8 is /256), and the former results in rounding
-        * errors that can turn colors black fast after repeated blending */
-       const int mfac = 255 - fac;
-       const int alpha = cp1[3] + ((fac * cp2[3]) / 255);
-
-       cp[0] = (mfac * cp1[0] + fac * cp2[0]) / 255;
-       cp[1] = (mfac * cp1[1] + fac * cp2[1]) / 255;
-       cp[2] = (mfac * cp1[2] + fac * cp2[2]) / 255;
-       cp[3] = alpha > 255 ? 255 : alpha;
-}
-static void blend_color_mix_accum_float(float cp[4], const float cp1[4], const unsigned char cp2[4], const float fac)
+static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float mask)
 {
-       const float mfac = 1.0f - fac;
-       const float alpha = cp1[3] + (fac * (cp2[3] / 255.0f));
+       const unsigned char *clone_pt = ((ProjPixelClone *)projPixel)->clonepx.ch;
 
-       cp[0] = (mfac * cp1[0] + (fac * (cp2[0] / 255.0f)));
-       cp[1] = (mfac * cp1[1] + (fac * (cp2[1] / 255.0f)));
-       cp[2] = (mfac * cp1[2] + (fac * (cp2[2] / 255.0f)));
-       cp[3] = alpha > 1.0f ? 1.0f : alpha;
-}
+       if (clone_pt[3]) {
+               unsigned char clone_rgba[4];
 
+               clone_rgba[0] = clone_pt[0];
+               clone_rgba[1] = clone_pt[1];
+               clone_rgba[2] = clone_pt[2];
+               clone_rgba[3] = (unsigned char)(clone_pt[3] * mask);
 
-static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask)
-{
-       if (ps->do_masking && mask < 1.0f) {
-               projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, ((ProjPixelClone *)projPixel)->clonepx.uint, (int)(alpha * 255), ps->blend);
-               blend_color_mix(projPixel->pixel.ch_pt,  projPixel->origColor.ch, projPixel->newColor.ch, (int)(mask * 255));
-       }
-       else {
-               *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, ((ProjPixelClone *)projPixel)->clonepx.uint, (int)(alpha * mask * 255), ps->blend);
+               if (ps->do_masking) {
+                       IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch, clone_rgba, ps->blend);
+               }
+               else {
+                       IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->pixel.ch_pt, clone_rgba, ps->blend);
+               }
        }
 }
 
-static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask)
+static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, float mask)
 {
-       if (ps->do_masking && mask < 1.0f) {
-               IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, ((ProjPixelClone *)projPixel)->clonepx.f, alpha, ps->blend);
-               blend_color_mix_float(projPixel->pixel.f_pt,  projPixel->origColor.f, projPixel->newColor.f, mask);
-       }
-       else {
-               IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.f, alpha * mask, ps->blend);
+       const float *clone_pt = ((ProjPixelClone *)projPixel)->clonepx.f;
+
+       if (clone_pt[3]) {
+               float clone_rgba[4];
+
+               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);
+               }
+               else {
+                       IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, clone_rgba, ps->blend);
+               }
        }
 }
 
@@ -3722,26 +3649,27 @@ static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, fl
  * accumulation of color greater then 'projPixel->mask' however in the case of smear its not
  * really that important to be correct as it is with clone and painting
  */
-static void do_projectpaint_smear(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels, float co[2])
+static void do_projectpaint_smear(ProjPaintState *ps, ProjPixel *projPixel, float mask,
+                                  MemArena *smearArena, LinkNode **smearPixels, const float co[2])
 {
        unsigned char rgba_ub[4];
 
        if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1) == 0)
                return;
-       /* ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend); */
-       blend_color_mix(((ProjPixelClone *)projPixel)->clonepx.ch, projPixel->pixel.ch_pt, rgba_ub, (int)(alpha * mask * 255));
+
+       blend_color_interpolate_byte(((ProjPixelClone *)projPixel)->clonepx.ch, projPixel->pixel.ch_pt, rgba_ub, mask);
        BLI_linklist_prepend_arena(smearPixels, (void *)projPixel, smearArena);
 }
 
-static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels_f, float co[2])
+static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float mask,
+                                    MemArena *smearArena, LinkNode **smearPixels_f, const float co[2])
 {
        float rgba[4];
 
        if (project_paint_PickColor(ps, co, rgba, NULL, 1) == 0)
                return;
 
-       /* (ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend); */
-       blend_color_mix_float(((ProjPixelClone *)projPixel)->clonepx.f, projPixel->pixel.f_pt, rgba, alpha * mask);
+       blend_color_interpolate_float(((ProjPixelClone *)projPixel)->clonepx.f, projPixel->pixel.f_pt, rgba, mask);
        BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena);
 }
 
@@ -3754,16 +3682,16 @@ static float inv_pow2(float f)
        return 1.0f - f;
 }
 
-static void do_projectpaint_soften_f(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *softenArena, LinkNode **softenPixels)
+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 *rgba = projPixel->newColor.f;
 
-       /* sigh, alpha values tend to need to be a _lot_ stronger with blur */
+       /* sigh, mask values tend to need to be a _lot_ stronger with blur */
        mask  = inv_pow2(mask);
-       alpha = inv_pow2(alpha);
 
        /* rather then painting, accumulate surrounding colors */
        zero_v4(rgba);
@@ -3780,22 +3708,21 @@ static void do_projectpaint_soften_f(ProjPaintState *ps, ProjPixel *projPixel, f
 
        if (LIKELY(accum_tot != 0)) {
                mul_v4_fl(rgba, 1.0f / (float)accum_tot);
-               blend_color_mix_float(rgba, projPixel->pixel.f_pt, rgba, alpha);
-               if (mask < 1.0f) blend_color_mix_float(rgba, projPixel->origColor.f, rgba, mask);
+               blend_color_interpolate_float(rgba, rgba, projPixel->pixel.f_pt, mask);
                BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena);
        }
 }
 
-static void do_projectpaint_soften(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *softenArena, LinkNode **softenPixels)
+static void do_projectpaint_soften(ProjPaintState *ps, ProjPixel *projPixel, float mask,
+                                   MemArena *softenArena, LinkNode **softenPixels)
 {
        unsigned int accum_tot = 0;
        unsigned int i;
 
        float rgba[4];  /* convert to byte after */
 
-       /* sigh, alpha values tend to need to be a _lot_ stronger with blur */
+       /* sigh, mask values tend to need to be a _lot_ stronger with blur */
        mask  = inv_pow2(mask);
-       alpha = inv_pow2(alpha);
 
        /* rather then painting, accumulate surrounding colors */
        zero_v4(rgba);
@@ -3814,77 +3741,56 @@ 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);
-               IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_ub, rgba);
+               premul_float_to_straight_uchar(rgba_ub, rgba);
 
-               blend_color_mix(rgba_ub, projPixel->pixel.ch_pt, rgba_ub, (int)(alpha * 255));
-               if (mask != 1.0f) blend_color_mix(rgba_ub, projPixel->origColor.ch, rgba_ub, (int)(mask * 255));
+               blend_color_interpolate_byte(rgba_ub, rgba_ub, projPixel->pixel.ch_pt, mask);
                BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena);
        }
 }
 
-BLI_INLINE void rgba_float_to_uchar__mul_v3(unsigned char rgba_ub[4], const float rgba[4], const float rgb[3])
-{
-       rgba_ub[0] = f_to_char(rgba[0] * rgb[0]);
-       rgba_ub[1] = f_to_char(rgba[1] * rgb[1]);
-       rgba_ub[2] = f_to_char(rgba[2] * rgb[2]);
-       rgba_ub[3] = f_to_char(rgba[3]);
-}
-
-static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, const float rgba[4], float alpha, float mask)
+static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, const float texrgb[3], float mask)
 {
+       float rgb[3];
        unsigned char rgba_ub[4];
 
+       copy_v3_v3(rgb, ps->brush->rgb);
+
        if (ps->is_texbrush) {
-               rgba_float_to_uchar__mul_v3(rgba_ub, rgba, ps->brush->rgb);
-       }
-       else {
-               IMAPAINT_FLOAT_RGB_TO_CHAR(rgba_ub, ps->brush->rgb);
-               rgba_ub[3] = 255;
+               /* XXX actually should convert texrgb from linear to srgb here */
+               mul_v3_v3(rgb, texrgb);
        }
 
-       if (ps->do_masking && mask < 1.0f) {
-               projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, *((unsigned int *)rgba_ub), (int)(alpha * 255), ps->blend);
-               blend_color_mix(projPixel->pixel.ch_pt,  projPixel->origColor.ch, projPixel->newColor.ch, (int)(mask * 255));
+       rgb_float_to_uchar(rgba_ub, rgb);
+       rgba_ub[3] = FTOCHAR(mask);
+
+       if (ps->do_masking) {
+               IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->origColor.ch, rgba_ub, ps->blend);
        }
        else {
-               *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha * mask * 255), ps->blend);
+               IMB_blend_color_byte(projPixel->pixel.ch_pt, projPixel->pixel.ch_pt, rgba_ub, ps->blend);
        }
 }
 
-static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, float rgba[4], float alpha, float mask, int use_color_correction)
+static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, const float texrgb[3], float mask)
 {
-       if (ps->is_texbrush) {
-               /* rgba already holds a texture result here from higher level function */
-               if (use_color_correction) {
-                       float rgba_br[3];
-                       srgb_to_linearrgb_v3_v3(rgba_br, ps->brush->rgb);
-                       mul_v3_v3(rgba, rgba_br);
-               }
-               else {
-                       mul_v3_v3(rgba, ps->brush->rgb);
-               }
-       }
-       else {
-               if (use_color_correction) {
-                       srgb_to_linearrgb_v3_v3(rgba, ps->brush->rgb);
-               }
-               else {
-                       copy_v3_v3(rgba, ps->brush->rgb);
-               }
-               rgba[3] = 1.0;
-       }
+       float rgba[4];
+
+       srgb_to_linearrgb_v3_v3(rgba, ps->brush->rgb);
 
-       if (ps->do_masking && mask < 1.0f) {
-               IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, rgba, alpha, ps->blend);
-               blend_color_mix_float(projPixel->pixel.f_pt,  projPixel->origColor.f, projPixel->newColor.f, mask);
+       if (ps->is_texbrush)
+               mul_v3_v3(rgba, texrgb);
+       
+       mul_v3_fl(rgba, mask);
+       rgba[3] = mask;
+
+       if (ps->do_masking) {
+               IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->origColor.f, rgba, ps->blend);
        }
        else {
-               IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, rgba, alpha * mask, ps->blend);
+               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)
 {
@@ -3905,22 +3811,21 @@ static void *do_projectpaint_thread(void *ph_v)
        ProjPaintImage *last_projIma = NULL;
        ImagePaintPartialRedraw *last_partial_redraw_cell;
 
-       float rgba[4], alpha, dist_nosqrt, dist;
+       float dist_nosqrt, dist;
 
        float falloff;
        int bucket_index;
        int is_floatbuf = 0;
-       int use_color_correction = FALSE;
        const short tool =  ps->tool;
        rctf bucket_bounds;
 
        /* for smear only */
        float pos_ofs[2] = {0};
        float co[2];
-       float mask = 1.0f; /* airbrush wont use mask */
        unsigned short mask_short;
-       const float radius = (float)BKE_brush_size_get(ps->scene, brush);
-       const float radius_squared = radius * radius; /* avoid a square root with every dist comparison */
+       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_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;
 
@@ -3966,7 +3871,6 @@ static void *do_projectpaint_thread(void *ph_v)
 
                                        last_projIma->touch = 1;
                                        is_floatbuf = last_projIma->ibuf->rect_float ? 1 : 0;
-                                       use_color_correction = TRUE;
                                }
                                /* end copy */
 
@@ -3975,9 +3879,15 @@ static void *do_projectpaint_thread(void *ph_v)
                                        bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL,
                                                                    projPixel->projCoSS[0], projPixel->projCoSS[1]);
                                        if (projPixel->newColor.ch[3]) {
-                                               mask = ((float)projPixel->mask) / 65535.0f;
-                                               blend_color_mix_accum_float(projPixel->pixel.f_pt,  projPixel->origColor.f,
-                                                                           projPixel->newColor.ch, (mask * (projPixel->newColor.ch[3] / 255.0f)));
+                                               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,
+                                                                     newColor_f);
                                        }
                                }
                                else {
@@ -3985,9 +3895,11 @@ static void *do_projectpaint_thread(void *ph_v)
                                        bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL,
                                                                    projPixel->projCoSS[0], projPixel->projCoSS[1]);
                                        if (projPixel->newColor.ch[3]) {
-                                               mask = ((float)projPixel->mask) / 65535.0f;
-                                               blend_color_mix_accum(projPixel->pixel.ch_pt,  projPixel->origColor.ch,
-                                                                     projPixel->newColor.ch, (int)(mask * 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);
                                        }
                                }
                        }
@@ -4002,58 +3914,81 @@ static void *do_projectpaint_thread(void *ph_v)
                                dist_nosqrt = len_squared_v2v2(projPixel->projCoSS, pos);
 
                                /*if (dist < radius) {*/ /* correct but uses a sqrtf */
-                               if (dist_nosqrt <= radius_squared) {
-                                       float samplecos[3];
+                               if (dist_nosqrt <= brush_radius_sq) {
                                        dist = sqrtf(dist_nosqrt);
 
-                                       falloff = BKE_brush_curve_strength_clamp(ps->brush, dist, radius);
-
-                                       if (ps->is_texbrush) {
-                                               MTex *mtex = &brush->mtex;
-                                               if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) {
-                                                       sub_v2_v2v2(samplecos, projPixel->projCoSS, pos);
-                                               }
-                                               /* taking 3d copy to account for 3D mapping too. It gets concatenated during sampling */
-                                               else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D)
-                                                       copy_v3_v3(samplecos, projPixel->worldCoSS);
-                                               else
-                                                       copy_v3_v3(samplecos, projPixel->projCoSS);
-                                       }
+                                       falloff = BKE_brush_curve_strength_clamp(ps->brush, dist, brush_radius);
 
                                        if (falloff > 0.0f) {
-                                               if (ps->is_texbrush) {
-                                                       /* note, for clone and smear, we only use the alpha, could be a special function */
-                                                       BKE_brush_sample_tex(ps->scene, brush, samplecos, rgba, thread_index, pool);
-                                                       alpha = rgba[3];
+                                               float texrgb[3];
+                                               float mask = falloff;
+
+                                               if (ps->do_masking) {
+                                                       /* masking to keep brush contribution to a pixel limited. note we do not do
+                                                        * a simple max(mask, mask_accum), as this is very sensitive to spacing and
+                                                        * gives poor results for strokes crossing themselves.
+                                                        * 
+                                                        * 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;
+
+                                                       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;
+                                                       }
+                                                       mask_short = (unsigned short)mask;
+
+                                                       if (mask_short > projPixel->mask_accum) {
+                                                               projPixel->mask_accum = mask_short;
+                                                               mask = mask_short * (1.0f / 65535.0f);
+                                                       }
+                                                       else {
+                                                               /* Go onto the next pixel */
+                                                               continue;
+                                                       }
                                                }
                                                else {
-                                                       alpha = 1.0f;
+                                                       mask *= brush_alpha;
+                                                       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 *= texmask;
+                                                       }
                                                }
 
-                                               if (!ps->do_masking) {
-                                                       /* for an aurbrush there is no real mask, so just multiply the alpha by it */
-                                                       alpha *= falloff * BKE_brush_alpha_get(ps->scene, brush);
-                                                       mask = ((float)projPixel->mask) / 65535.0f;
-                                               }
-                                               else {
-                                                       /* This brush dosnt accumulate so add some curve to the brushes falloff */
-                                                       falloff = 1.0f - falloff;
-                                                       falloff = 1.0f - (falloff * falloff);
-
-                                                       mask_short = (unsigned short)(projPixel->mask * (BKE_brush_alpha_get(ps->scene, brush) * falloff));
-                                                       if (mask_short > projPixel->mask_max) {
-                                                               mask = ((float)mask_short) / 65535.0f;
-                                                               projPixel->mask_max = mask_short;
+                                               if (ps->is_texbrush) {
+                                                       MTex *mtex = &brush->mtex;
+                                                       float samplecos[3];
+                                                       float texrgba[4];
+
+                                                       /* taking 3d copy to account for 3D mapping too. It gets concatenated during sampling */
+                                                       if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) {
+                                                               copy_v3_v3(samplecos, projPixel->worldCoSS);
                                                        }
                                                        else {
-                                                               /*mask = ((float)projPixel->mask_max)/65535.0f;*/
-
-                                                               /* Go onto the next pixel */
-                                                               continue;
+                                                               copy_v2_v2(samplecos, projPixel->projCoSS);
+                                                               samplecos[2] = 0.0f;
                                                        }
+
+                                                       /* note, for clone and smear, we only use the alpha, could be a special function */
+                                                       BKE_brush_sample_tex_3D(ps->scene, brush, samplecos, texrgba, thread_index, pool);
+
+                                                       copy_v3_v3(texrgb, texrgba);
+                                                       mask *= texrgba[3];
+                                               }
+
+                                               if (ps->is_maskbrush_tiled) {
+                                                       mask *= BKE_brush_sample_masktex(ps->scene, ps->brush, projPixel->projCoSS, thread_index, pool);
                                                }
 
-                                               if (alpha > 0.0f) {
+                                               /* extra mask for normal, layer stencil, .. */
+                                               mask *= ((float)projPixel->mask) * (1.0f / 65535.0f);
+
+                                               if (mask > 0.0f) {
 
                                                        /* copy of code above */
                                                        if (last_index != projPixel->image_index) {
@@ -4062,7 +3997,6 @@ static void *do_projectpaint_thread(void *ph_v)
 
                                                                last_projIma->touch = 1;
                                                                is_floatbuf = last_projIma->ibuf->rect_float ? 1 : 0;
-                                                               use_color_correction = TRUE;
                                                        }
                                                        /* end copy */
 
@@ -4073,33 +4007,25 @@ static void *do_projectpaint_thread(void *ph_v)
                                                        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);
 
-
+                                                       /* texrgb is not used for clone, smear or soften */
                                                        switch (tool) {
                                                                case PAINT_TOOL_CLONE:
-                                                                       if (is_floatbuf) {
-                                                                               if (((ProjPixelClone *)projPixel)->clonepx.f[3]) {
-                                                                                       do_projectpaint_clone_f(ps, projPixel, alpha, mask); /* rgba isn't used for cloning, only alpha */
-                                                                               }
-                                                                       }
-                                                                       else {
-                                                                               if (((ProjPixelClone *)projPixel)->clonepx.ch[3]) {
-                                                                                       do_projectpaint_clone(ps, projPixel, alpha, mask); /* rgba isn't used for cloning, only alpha */
-                                                                               }
-                                                                       }
+                                                                       if (is_floatbuf) do_projectpaint_clone_f(ps, projPixel, mask);
+                                                                       else             do_projectpaint_clone(ps, projPixel, mask);
                                                                        break;
                                                                case PAINT_TOOL_SMEAR:
                                                                        sub_v2_v2v2(co, projPixel->projCoSS, pos_ofs);
 
-                                                                       if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, alpha, mask, smearArena, &smearPixels_f, co);
-                                                                       else do_projectpaint_smear(ps, projPixel, alpha, mask, smearArena, &smearPixels, co);
+                                                                       if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, mask, smearArena, &smearPixels_f, co);
+                                                                       else             do_projectpaint_smear(ps, projPixel, mask, smearArena, &smearPixels, co);
                                                                        break;
                                                                case PAINT_TOOL_SOFTEN:
-                                                                       if (is_floatbuf) do_projectpaint_soften_f(ps, projPixel, alpha, mask, softenArena, &softenPixels_f);
-                                                                       else do_projectpaint_soften(ps, projPixel, alpha, mask, softenArena, &softenPixels);
+                                                                       if (is_floatbuf) do_projectpaint_soften_f(ps, projPixel, mask, softenArena, &softenPixels_f);
+                                                                       else             do_projectpaint_soften(ps, projPixel, mask, softenArena, &softenPixels);
                                                                        break;
                                                                default:
-                                                                       if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, rgba, alpha, mask, use_color_correction);
-                                                                       else do_projectpaint_draw(ps, projPixel, rgba, alpha, mask);
+                                                                       if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, texrgb, mask);
+                                                                       else             do_projectpaint_draw(ps, projPixel, texrgb, mask);
                                                                        break;
                                                        }
                                                }
@@ -4224,125 +4150,76 @@ static int project_paint_op(void *state, const float lastpos[2], const float pos
 }
 
 
-static int project_paint_stroke(ProjPaintState *ps, const int prevmval_i[2], const int mval_i[2])
+void paint_proj_stroke(bContext *C, void *pps, const float prev_pos[2], const float pos[2])
 {
-       int a, redraw;
-       float pos[2], prev_pos[2];
-
-       pos[0] = (float)(mval_i[0]);
-       pos[1] = (float)(mval_i[1]);
-
-       prev_pos[0] = (float)(prevmval_i[0]);
-       prev_pos[1] = (float)(prevmval_i[1]);
-
-       for (a = 0; a < ps->image_tot; a++)
-               partial_redraw_array_init(ps->projImages[a].partRedrawRect);
-
-       redraw = project_paint_op(ps, prev_pos, pos) ? 1 : 0;
-
-       if (project_image_refresh_tagged(ps))
-               return redraw;
-
-       return 0;
-}
-
-/* Imagepaint Partial Redraw & Dirty Region */
-
-
-/************************ image paint poll ************************/
+       ProjPaintState *ps = pps;
+       int a;
 
-static Brush *image_paint_brush(bContext *C)
-{
-       Scene *scene = CTX_data_scene(C);
-       ToolSettings *settings = scene->toolsettings;
+       /* 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 = give_cursor(scene, v3d);
+               int mval_i[2] = {(int)pos[0], (int)pos[1]};
 
-       return paint_brush(&settings->imapaint.paint);
-}
+               view3d_operator_needs_opengl(C);
 
-static int image_paint_poll(bContext *C)
-{
-       Object *obact = CTX_data_active_object(C);
+               if (!ED_view3d_autodist(scene, ps->ar, v3d, mval_i, cursor, false))
+                       return;
 
-       if (!image_paint_brush(C))
-               return 0;
+               ED_region_tag_redraw(ps->ar);
 
-       if ((obact && obact->mode & OB_MODE_TEXTURE_PAINT) && CTX_wm_region_view3d(C)) {
-               return 1;
+               return;
        }
-       else {
-               SpaceImage *sima = CTX_wm_space_image(C);
-
-               if (sima) {
-                       ARegion *ar = CTX_wm_region(C);
 
-                       if ((sima->mode == SI_MODE_PAINT) && ar->regiontype == RGN_TYPE_WINDOW) {
-                               return 1;
-                       }
-               }
+       /* continue adding to existing partial redraw rects until redraw */
+       if (!ps->need_redraw) {
+               for (a = 0; a < ps->image_tot; a++)
+                       partial_redraw_array_init(ps->projImages[a].partRedrawRect);
        }
 
-       return 0;
+       if (project_paint_op(ps, prev_pos, pos))
+               ps->need_redraw = true;
 }
 
-/************************ paint operator ************************/
-
-typedef enum TexPaintMode {
-       PAINT_MODE_2D,
-       PAINT_MODE_3D_PROJECT
-} TexPaintMode;
-
-typedef struct PaintOperation {
-       TexPaintMode mode;
-
-       void *custom_paint;
-       ProjPaintState ps;
-
-       int first;
-       int prevmouse[2];
-       int orig_brush_size;
-       double starttime;
-
-       ViewContext vc;
-       wmTimer *timer;
-} PaintOperation;
-
-static void paint_redraw(const bContext *C, PaintOperation *pop, int final)
-{
-       if (pop->mode == PAINT_MODE_2D) {
-               paint_2d_redraw(C, pop->custom_paint, final);
-       } else {
-               if (final) {
-                       /* compositor listener deals with updating */
-                       WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, NULL);
-               }
-               else {
-                       ED_region_tag_redraw(CTX_wm_region(C));
-               }
-       }
-}
 
 /* initialize project paint settings from context */
-static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps)
+static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int mode)
 {
        Scene *scene = CTX_data_scene(C);
        ToolSettings *settings = scene->toolsettings;
 
        /* brush */
-       ps->brush = paint_brush(&settings->imapaint.paint);
+       ps->mode = mode;
+       ps->brush = BKE_paint_brush(&settings->imapaint.paint);
        if (ps->brush) {
                Brush *brush = ps->brush;
                ps->tool = brush->imagepaint_tool;
                ps->blend = brush->blend;
 
                /* disable for 3d mapping also because painting on mirrored mesh can create "stripes" */
-               ps->do_masking = (brush->flag & BRUSH_AIRBRUSH || brush->mtex.brush_map_mode == MTEX_MAP_MODE_VIEW ||
-                                 brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D) ? false : true;
-               ps->is_texbrush = (brush->mtex.tex) ? 1 : 0;
+               ps->do_masking = (brush->flag & BRUSH_AIRBRUSH ||
+                                 (brush->imagepaint_tool == PAINT_TOOL_SMEAR) ||
+                                 (brush->mtex.tex && !ELEM3(brush->mtex.brush_map_mode, MTEX_MAP_MODE_TILED, MTEX_MAP_MODE_STENCIL, MTEX_MAP_MODE_3D)))
+                                ? false : true;
+               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;
+                       }
+               }
        }
        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_ */
@@ -4388,195 +4265,284 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps)
 
        if (ps->normal_angle_range <= 0.0f)
                ps->do_mask_normal = FALSE;  /* no need to do blending */
+
+       return;
 }
 
-static PaintOperation * texture_paint_init(bContext *C, wmOperator *op)
+void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int mode)
 {
-       Scene *scene = CTX_data_scene(C);
-       ToolSettings *settings = scene->toolsettings;
-       Brush *brush = paint_brush(&settings->imapaint.paint);
-       PaintOperation *pop = MEM_callocN(sizeof(PaintOperation), "PaintOperation"); /* caller frees */
+       ProjPaintState *ps = MEM_callocN(sizeof(ProjPaintState), "ProjectionPaintState");
+       project_state_init(C, ob, ps, mode);
+
+       if (ps->tool == PAINT_TOOL_CLONE && mode == BRUSH_STROKE_INVERT) {
+               view3d_operator_needs_opengl(C);
+               return ps;
+       }
+
+       /* needed so multiple threads don't try to initialize the brush at once (can leak memory) */
+       curvemapping_initialize(ps->brush->curve);
+
+       paint_brush_init_tex(ps->brush);
+
+       ps->source = PROJ_SRC_VIEW;
+
+       if (ps->ob == NULL || !(ps->ob->lay & ps->v3d->lay)) {
+               MEM_freeN(ps);
+               return NULL;
+       }
+
+       ps->orig_brush_size = BKE_brush_size_get(ps->scene, ps->brush);
+
+       /* Don't allow brush size below 2 */
+       if (BKE_brush_size_get(ps->scene, ps->brush) < 2)
+               BKE_brush_size_set(ps->scene, ps->brush, 2);
 
-       pop->first = 1;
+       /* allocate and initialize spatial data structures */
+       project_paint_begin(ps);
 
-       view3d_set_viewcontext(C, &pop->vc);
+       if (ps->dm == NULL) {
+               MEM_freeN(ps);
+               return NULL;
+       }
+
+       paint_proj_begin_clone(ps, mouse);
+
+       return ps;
+}
+
+void paint_proj_redraw(const bContext *C, void *pps, bool final)
+{
+       ProjPaintState *ps = pps;
+
+       if (ps->need_redraw) {
+               project_image_refresh_tagged(ps);
+
+               ps->need_redraw = false;
+       }
+       else if (!final) {
+               return;
+       }
 
-       /* initialize from context */
-       if (CTX_wm_region_view3d(C)) {
-               pop->mode = PAINT_MODE_3D_PROJECT;
+       if (final) {
+               /* compositor listener deals with updating */
+               WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, NULL);
        }
        else {
-               pop->mode = PAINT_MODE_2D;
-               pop->custom_paint = paint_2d_new_stroke(C, op);
-               if (!pop->custom_paint) {
-                       MEM_freeN(pop);
-                       return NULL;
-               }
+               ED_region_tag_redraw(CTX_wm_region(C));
+       }
+}
+
+void paint_proj_stroke_done(void *pps)
+{
+       ProjPaintState *ps = pps;
+       if (ps->tool == PAINT_TOOL_CLONE && ps->mode == BRUSH_STROKE_INVERT) {
+               MEM_freeN(ps);
+               return;
        }
+       BKE_brush_size_set(ps->scene, ps->brush, ps->orig_brush_size);
 
-       pop->orig_brush_size = BKE_brush_size_get(scene, brush);
+       paint_brush_exit_tex(ps->brush);
 
-       /* note, if we have no UVs on the derived mesh, then we must return here */
-       if (pop->mode == PAINT_MODE_3D_PROJECT) {
+       project_paint_end(ps);
+       MEM_freeN(ps);
+}
+/* use project paint to re-apply an image */
+static int texture_paint_camera_project_exec(bContext *C, wmOperator *op)
+{
+       Image *image = BLI_findlink(&CTX_data_main(C)->image, RNA_enum_get(op->ptr, "image"));
+       Scene *scene = CTX_data_scene(C);
+       ProjPaintState ps = {NULL};
+       int orig_brush_size;
+       IDProperty *idgroup;
+       IDProperty *view_data = NULL;
 
-               /* initialize all data from the context */
-               project_state_init(C, OBACT, &pop->ps);
+       project_state_init(C, OBACT, &ps, BRUSH_STROKE_NORMAL);
 
-               /* needed so multiple threads don't try to initialize the brush at once (can leak memory) */
-               curvemapping_initialize(pop->ps.brush->curve);
+       if (ps.ob == NULL || ps.ob->type != OB_MESH) {
+               BKE_report(op->reports, RPT_ERROR, "No active mesh object");
+               return OPERATOR_CANCELLED;
+       }
 
-               paint_brush_init_tex(pop->ps.brush);
+       if (image == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "Image could not be found");
+               return OPERATOR_CANCELLED;
+       }
 
-               pop->ps.source = PROJ_SRC_VIEW;
+       ps.reproject_image = image;
+       ps.reproject_ibuf = BKE_image_acquire_ibuf(image, NULL, NULL);
 
-               if (pop->ps.ob == NULL || !(pop->ps.ob->lay & pop->ps.v3d->lay)) {
-                       MEM_freeN(pop);
-                       return NULL;
-               }
+       if (ps.reproject_ibuf == NULL || ps.reproject_ibuf->rect == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "Image data could not be found");
+               return OPERATOR_CANCELLED;
+       }
 
-               /* Don't allow brush size below 2 */
-               if (BKE_brush_size_get(scene, brush) < 2)
-                       BKE_brush_size_set(scene, brush, 2);
+       idgroup = IDP_GetProperties(&image->id, 0);
 
-               /* allocate and initialize spatial data structures */
-               project_paint_begin(&pop->ps);
+       if (idgroup) {
+               view_data = IDP_GetPropertyTypeFromGroup(idgroup, PROJ_VIEW_DATA_ID, IDP_ARRAY);
 
-               if (pop->ps.dm == NULL) {
-                       MEM_freeN(pop);
-                       return NULL;
+               /* type check to make sure its ok */
+               if (view_data->len != PROJ_VIEW_DATA_SIZE || view_data->subtype != IDP_FLOAT) {
+                       BKE_report(op->reports, RPT_ERROR, "Image project data invalid");
+                       return OPERATOR_CANCELLED;
                }
        }
 
-       settings->imapaint.flag |= IMAGEPAINT_DRAWING;
-       undo_paint_push_begin(UNDO_PAINT_IMAGE, op->type->name,
-                             image_undo_restore, image_undo_free);
+       if (view_data) {
+               /* image has stored view projection info */
+               ps.source = PROJ_SRC_IMAGE_VIEW;
+       }
+       else {
+               ps.source = PROJ_SRC_IMAGE_CAM;
 
-       {
-               UnifiedPaintSettings *ups = &settings->unified_paint_settings;
-               ups->draw_pressure = true;
+               if (scene->camera == NULL) {
+                       BKE_report(op->reports, RPT_ERROR, "No active camera set");
+                       return OPERATOR_CANCELLED;
+               }
        }
 
-       return pop;
-}
+       /* 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 */
 
-static void paint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr)
-{
-       PaintOperation *pop = paint_stroke_mode_data(stroke);
-       float mousef[2];
-       float pressure;
-       int mouse[2], redraw, eraser;
+       ps.tool = PAINT_TOOL_DRAW; /* so pixels are initialized with minimal info */
 
-       RNA_float_get_array(itemptr, "mouse", mousef);
-       mouse[0] = (int)(mousef[0]);
-       mouse[1] = (int)(mousef[1]);
-       pressure = RNA_float_get(itemptr, "pressure");
-       eraser = RNA_boolean_get(itemptr, "pen_flip");
+       scene->toolsettings->imapaint.flag |= IMAGEPAINT_DRAWING;
 
-       if (pop->mode == PAINT_MODE_3D_PROJECT) {
-               if (pop->first)
-                       project_paint_begin_clone(&pop->ps, mouse);
+       undo_paint_push_begin(UNDO_PAINT_IMAGE, op->type->name,
+                             image_undo_restore, image_undo_free);
 
-               redraw = project_paint_stroke(&pop->ps, pop->prevmouse, mouse);
-               pop->prevmouse[0] = mouse[0];
-               pop->prevmouse[1] = mouse[1];
+       /* allocate and initialize spatial data structures */
+       project_paint_begin(&ps);
 
+       if (ps.dm == NULL) {
+               BKE_brush_size_set(scene, ps.brush, orig_brush_size);
+               return OPERATOR_CANCELLED;
        }
        else {
-               redraw = paint_2d_stroke(pop->custom_paint, mouse, pressure, eraser);
-               pop->prevmouse[0] = mouse[0];
-               pop->prevmouse[1] = mouse[1];
+               float pos[2] = {0.0, 0.0};
+               float lastpos[2] = {0.0, 0.0};
+               int a;
+
+               for (a = 0; a < ps.image_tot; a++)
+                       partial_redraw_array_init(ps.projImages[a].partRedrawRect);
+
+               project_paint_op(&ps, lastpos, pos);
+
+               project_image_refresh_tagged(&ps);
+
+               for (a = 0; a < ps.image_tot; a++) {
+                       GPU_free_image(ps.projImages[a].ima);
+                       WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, ps.projImages[a].ima);
+               }
        }
 
-       if (redraw)
-               paint_redraw(C, pop, 0);
+       project_paint_end(&ps);
+
+       scene->toolsettings->imapaint.flag &= ~IMAGEPAINT_DRAWING;
+       BKE_brush_size_set(scene, ps.brush, orig_brush_size);
 
-       pop->first = 0;
+       return OPERATOR_FINISHED;
 }
 
-static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke)
+void PAINT_OT_project_image(wmOperatorType *ot)
 {
-       Scene *scene = CTX_data_scene(C);
-       ToolSettings *settings = scene->toolsettings;
-       PaintOperation *pop = paint_stroke_mode_data(stroke);
-
-       if (pop->timer)
-               WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), pop->timer);
+       PropertyRNA *prop;
 
-       settings->imapaint.flag &= ~IMAGEPAINT_DRAWING;
+       /* identifiers */
+       ot->name = "Project Image";
+       ot->idname = "PAINT_OT_project_image";
+       ot->description = "Project an edited render from the active camera back onto the object";
 
-       if (pop->mode == PAINT_MODE_3D_PROJECT) {
-               BKE_brush_size_set(scene, pop->ps.brush, pop->orig_brush_size);
-               paint_brush_exit_tex(pop->ps.brush);
+       /* api callbacks */
+       ot->invoke = WM_enum_search_invoke;
+       ot->exec = texture_paint_camera_project_exec;
 
-               project_paint_end(&pop->ps);
-       } else
-               paint_2d_stroke_done(pop->custom_paint);
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
-       paint_redraw(C, pop, 1);
-       undo_paint_push_end(UNDO_PAINT_IMAGE);
+       prop = RNA_def_enum(ot->srna, "image", DummyRNA_NULL_items, 0, "Image", "");
+       RNA_def_enum_funcs(prop, RNA_image_itemf);
+       ot->prop = prop;
+}
 
-       /* duplicate warning, see texpaint_init
-       if (pop->s.warnmultifile)
-               BKE_reportf(op->reports, RPT_WARNING, "Image requires 4 color channels to paint: %s", pop->s.warnmultifile);
-       if (pop->s.warnpackedfile)
-               BKE_reportf(op->reports, RPT_WARNING, "Packed MultiLayer files cannot be painted: %s", pop->s.warnpackedfile);
-       */
-       MEM_freeN(pop);
+static int texture_paint_image_from_view_exec(bContext *C, wmOperator *op)
+{
+       Image *image;
+       ImBuf *ibuf;
+       char filename[FILE_MAX];
 
-       {
-               UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
-               ups->draw_pressure = false;
-       }
-}
+       Scene *scene = CTX_data_scene(C);
+       ToolSettings *settings = scene->toolsettings;
+       int w = settings->imapaint.screen_grab_size[0];
+       int h = settings->imapaint.screen_grab_size[1];
+       int maxsize;
+       char err_out[256] = "unknown";
 
-static int paint_stroke_test_start(bContext * UNUSED(C), wmOperator * UNUSED(op), const float UNUSED(mouse[2])) {
-       return true;
-}
+       RNA_string_get(op->ptr, "filepath", filename);
 
+       maxsize = GPU_max_texture_size();
 
-static int paint_invoke_proj(bContext *C, wmOperator *op, wmEvent *event)
-{
-       PaintOperation *pop;
-       struct PaintStroke *stroke;
-       int retval;
+       if (w > maxsize) w = maxsize;
+       if (h > maxsize) h = maxsize;
 
-       if (!(pop = texture_paint_init(C, op))) {
+       ibuf = ED_view3d_draw_offscreen_imbuf(CTX_data_scene(C), CTX_wm_view3d(C), CTX_wm_region(C), w, h, IB_rect, FALSE, R_ALPHAPREMUL, err_out);
+       if (!ibuf) {
+               /* Mostly happens when OpenGL offscreen buffer was failed to create, */
+               /* but could be other reasons. Should be handled in the future. nazgul */
+               BKE_reportf(op->reports, RPT_ERROR, "Failed to create OpenGL off-screen buffer: %s", err_out);
                return OPERATOR_CANCELLED;
        }
 
-       stroke = op->customdata = paint_stroke_new(C, NULL, paint_stroke_test_start,
-                                         paint_stroke_update_step,
-                                         paint_stroke_done, event->type);
-       paint_stroke_set_mode_data(stroke, pop);
-       /* add modal handler */
-       WM_event_add_modal_handler(C, op);
+       image = BKE_image_add_from_imbuf(ibuf);
 
-       retval = op->type->modal(C, op, event);
-       OPERATOR_RETVAL_CHECK(retval);
-       BLI_assert(retval == OPERATOR_RUNNING_MODAL);
+       if (image) {
+               /* now for the trickyness. store the view projection here!
+                * re-projection will reuse this */
+               View3D *v3d = CTX_wm_view3d(C);
+               RegionView3D *rv3d = CTX_wm_region_view3d(C);
 
-       return OPERATOR_RUNNING_MODAL;
-}
+               IDPropertyTemplate val;
+               IDProperty *idgroup = IDP_GetProperties(&image->id, 1);
+               IDProperty *view_data;
+               bool is_ortho;
+               float *array;
 
+               val.array.len = PROJ_VIEW_DATA_SIZE;
+               val.array.type = IDP_FLOAT;
+               view_data = IDP_New(IDP_ARRAY, &val, PROJ_VIEW_DATA_ID);
 
-void PAINT_OT_image_paint_proj(wmOperatorType *ot)
-{
+               array = (float *)IDP_Array(view_data);
+               memcpy(array, rv3d->winmat, sizeof(rv3d->winmat)); array += sizeof(rv3d->winmat) / sizeof(float);
+               memcpy(array, rv3d->viewmat, sizeof(rv3d->viewmat)); array += sizeof(rv3d->viewmat) / sizeof(float);
+               is_ortho = ED_view3d_clip_range_get(v3d, rv3d, &array[0], &array[1], true);
+               array[2] = is_ortho ? 1.0f : 0.0f; /* using float for a bool is dodgy but since its an extra member in the array... easier then adding a single bool prop */
+
+               IDP_AddToGroup(idgroup, view_data);
+
+               rename_id(&image->id, "image_view");
+       }
+
+       return OPERATOR_FINISHED;
+}
 
+void PAINT_OT_image_from_view(wmOperatorType *ot)
+{
        /* identifiers */
-       ot->name = "Image Paint";
-       ot->idname = "PAINT_OT_image_paint_proj";
-       ot->description = "Paint a stroke into the image";
+       ot->name = "Image from View";
+       ot->idname = "PAINT_OT_image_from_view";
+       ot->description = "Make an image from the current 3D view for re-projection";
 
        /* api callbacks */
-       ot->invoke = paint_invoke_proj;
-       ot->modal = paint_stroke_modal;
-       /* ot->exec = paint_exec; <-- needs stroke property */
-       ot->poll = image_paint_poll;
-       ot->cancel = paint_stroke_cancel;
+       ot->exec = texture_paint_image_from_view_exec;
+       ot->poll = ED_operator_region_view3d_active;
 
        /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
+       ot->flag = OPTYPE_REGISTER;
 
-       RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+       RNA_def_string_file_name(ot->srna, "filepath", "", FILE_MAX, "File Path", "Name of the file");
 }
-