From 4251912b541eddc5ada25f70db5614beb61b6aca Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 9 Dec 2008 08:21:53 +0000 Subject: * tablet pressure changing opacity while painting didnt work * pixels with <= the current opacity are not painted onto, speeds up painting especially with low spacing. (only when airbrush is disabled) * use qsort rather then own crufty sorting function * grouped projection painting functions together. --- source/blender/blenkernel/intern/customdata.c | 6 + source/blender/src/buttons_editing.c | 33 +- source/blender/src/imagepaint.c | 1730 +++++++++++++------------ 3 files changed, 922 insertions(+), 847 deletions(-) diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index f7fca8c1412..05271aa59a7 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -964,9 +964,13 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data, if(index > 0 && data->layers[index-1].type == type) { data->layers[index].active = data->layers[index-1].active; data->layers[index].active_rnd = data->layers[index-1].active_rnd; + data->layers[index].active_clone = data->layers[index-1].active_clone; + data->layers[index].active_mask = data->layers[index-1].active_mask; } else { data->layers[index].active = 0; data->layers[index].active_rnd = 0; + data->layers[index].active_clone = 0; + data->layers[index].active_mask = 0; } customData_update_offsets(data); @@ -1026,6 +1030,8 @@ int CustomData_free_layer(CustomData *data, int type, int totelem, int index) for (; i < data->totlayer && data->layers[i].type == type; i++) { data->layers[i].active--; data->layers[i].active_rnd--; + data->layers[i].active_clone--; + data->layers[i].active_mask--; } } diff --git a/source/blender/src/buttons_editing.c b/source/blender/src/buttons_editing.c index 1157dc7b26a..3d8bdb749bc 100644 --- a/source/blender/src/buttons_editing.c +++ b/source/blender/src/buttons_editing.c @@ -722,10 +722,10 @@ static void delete_customdata_layer(void *data1, void *data2) Mesh *me= (Mesh*)data1; CustomData *data= (G.obedit)? &G.editMesh->fdata: &me->fdata; CustomDataLayer *layer= (CustomDataLayer*)data2; - void *actlayerdata, *rndlayerdata, *layerdata=layer->data; + void *actlayerdata, *rndlayerdata, *clonelayerdata, *masklayerdata, *layerdata=layer->data; int type= layer->type; int index= CustomData_get_layer_index(data, type); - int i, actindex, rndindex; + int i, actindex, rndindex, cloneindex, maskindex; /*ok, deleting a non-active layer needs to preserve the active layer indices. to do this, we store a pointer to the .data member of both layer and the active layer, @@ -736,6 +736,8 @@ static void delete_customdata_layer(void *data1, void *data2) layer. */ actlayerdata = data->layers[CustomData_get_active_layer_index(data, type)].data; rndlayerdata = data->layers[CustomData_get_render_layer_index(data, type)].data; + clonelayerdata = data->layers[CustomData_get_clone_layer_index(data, type)].data; + masklayerdata = data->layers[CustomData_get_mask_layer_index(data, type)].data; CustomData_set_layer_active(data, type, layer - &data->layers[index]); /* Multires is handled seperately because the display data is separate @@ -787,6 +789,33 @@ static void delete_customdata_layer(void *data1, void *data2) CustomData_set_layer_render(data, type, rndindex); } + if (clonelayerdata != layerdata) { + /*find index. . .*/ + cloneindex = CustomData_get_layer_index(data, type); + for (i=cloneindex; itotlayer; i++) { + if (data->layers[i].data == clonelayerdata) { + cloneindex = i - cloneindex; + break; + } + } + + /*set index. . .*/ + CustomData_set_layer_clone(data, type, cloneindex); + } + + if (masklayerdata != layerdata) { + /*find index. . .*/ + maskindex = CustomData_get_layer_index(data, type); + for (i=maskindex; itotlayer; i++) { + if (data->layers[i].data == masklayerdata) { + maskindex = i - maskindex; + break; + } + } + + /*set index. . .*/ + CustomData_set_layer_mask(data, type, maskindex); + } DAG_object_flush_update(G.scene, OBACT, OB_RECALC_DATA); diff --git a/source/blender/src/imagepaint.c b/source/blender/src/imagepaint.c index d3efeba772c..46a91f772e0 100644 --- a/source/blender/src/imagepaint.c +++ b/source/blender/src/imagepaint.c @@ -118,6 +118,9 @@ #define MAXUNDONAME 64 +static void imapaint_image_update(Image *image, ImBuf *ibuf, short texpaint); + + typedef struct ImagePaintState { Brush *brush; short tool, blend; @@ -293,7 +296,15 @@ typedef union pixelStore typedef struct ProjPixel { float projCoSS[2]; /* the floating point screen projection of this pixel */ - float mask; /* for various reasons we may want to mask out painting onto this pixel */ + + /* 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; + + /* for various reasons we may want to mask out painting onto this pixel */ + unsigned short mask; short x_px, y_px; @@ -943,10 +954,26 @@ static int line_isect_x(const float p1[2], const float p2[2], const float x_leve } } -/* simple func use for comparing UV locations to check if there are seams */ +/* simple func use for comparing UV locations to check if there are seams. + * Its possible this gives incorrect results, when the UVs for 1 face go into the next + * tile, but do not do this for the adjacent face, it could return a false positive. + * This is so unlikely that Id not worry about it. */ static int cmp_uv(const float vec2a[2], const float vec2b[2]) { - return ((fabs(vec2a[0]-vec2b[0]) < 0.0001f) && (fabs(vec2a[1]-vec2b[1]) < 0.0001f)) ? 1:0; + /* if the UV's are not between 0.0 and 1.0 */ + float xa = (float)fmod(vec2a[0], 1.0f); + float ya = (float)fmod(vec2a[1], 1.0f); + + float xb = (float)fmod(vec2b[0], 1.0f); + float yb = (float)fmod(vec2b[1], 1.0f); + + if (xa < 0.0f) xa += 1.0f; + if (ya < 0.0f) ya += 1.0f; + + if (xb < 0.0f) xb += 1.0f; + if (yb < 0.0f) yb += 1.0f; + + return ((fabs(xa-xb) < PROJ_GEOM_TOLERANCE) && (fabs(ya-yb) < PROJ_GEOM_TOLERANCE)) ? 1:0; } @@ -1440,9 +1467,9 @@ float project_paint_uvpixel_mask( } } - if (ps->is_airbrush==0) { - mask *= ps->brush->alpha; - } + // This only works when the opacity dosnt change while paintnig, stylus pressure messes with this + // so dont use it. + // if (ps->is_airbrush==0) mask *= ps->brush->alpha; return mask; } @@ -1501,7 +1528,8 @@ static ProjPixel *project_paint_uvpixel_init( projPixel->x_px = x_px; projPixel->y_px = y_px; - projPixel->mask = mask; + projPixel->mask = (unsigned short)(mask * 65535); + projPixel->mask_max = 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)) + ((int)(((float)y_px/(float)ibuf->y) * PROJ_BOUNDBOX_DIV)) * PROJ_BOUNDBOX_DIV ; @@ -1934,7 +1962,20 @@ static float angle_2d_clockwise(const float p1[2], const float p2[2], const floa static int IsectPT2Df_limit(float pt[2], float v1[2], float v2[2], float v3[2], float limit) { - return (AreaF2Dfl(v1,v2,v3) + limit) > (AreaF2Dfl(pt,v1,v2) + AreaF2Dfl(pt,v2,v3) + AreaF2Dfl(pt,v3,v1)) ? 1 : 0; + return (AreaF2Dfl(v1,v2,v3) + limit) > (AreaF2Dfl(pt,v1,v2) + AreaF2Dfl(pt,v2,v3) + AreaF2Dfl(pt,v3,v1)); +} + +/* Clip the face by a bucket and set the uv-space bucket_bounds_uv + * so we have the clipped UV's to do pixel intersection tests with + * */ + + +static int float_z_sort_flip(const void *p1, const void *p2) { + return (((float *)p1)[2] < ((float *)p2)[2] ? 1:-1); +} + +static int float_z_sort(const void *p1, const void *p2) { + return (((float *)p1)[2] < ((float *)p2)[2] ?-1:1); } static void project_bucket_clip_face( @@ -1949,7 +1990,6 @@ static void project_bucket_clip_face( int inside_face_flag = 0; const int flip = ((SIDE_OF_LINE(v1coSS, v2coSS, v3coSS) > 0.0f) != (SIDE_OF_LINE(uv1co, uv2co, uv3co) > 0.0f)); - float uv[2]; float bucket_bounds_ss[4][2]; float w[3]; @@ -2019,17 +2059,14 @@ static void project_bucket_clip_face( /* Maximum possible 6 intersections when using a rectangle and triangle */ - float isectVCosSS[8][2]; - float isectVAngles[8]; - - float vClipSS_A[2], vClipSS_B[2]; + float isectVCosSS[8][3]; /* The 3rd float is used to store angle for qsort(), NOT as a Z location */ + float v1_clipSS[2], v2_clipSS[2]; /* calc center*/ float cent[2] = {0.0f, 0.0f}; /*float up[2] = {0.0f, 1.0f};*/ - float tmp_f; int i; - short unsorted, doubles; + short doubles; (*tot) = 0; @@ -2043,23 +2080,23 @@ static void project_bucket_clip_face( if (inside_bucket_flag & ISECT_3) { VECCOPY2D(isectVCosSS[*tot], v3coSS); (*tot)++; } if ((inside_bucket_flag & (ISECT_1|ISECT_2)) != (ISECT_1|ISECT_2)) { - if (line_clip_rect2f(bucket_bounds, v1coSS, v2coSS, vClipSS_A, vClipSS_B)) { - if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], vClipSS_A); (*tot)++; } - if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], vClipSS_B); (*tot)++; } + if (line_clip_rect2f(bucket_bounds, v1coSS, v2coSS, v1_clipSS, v2_clipSS)) { + if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; } + if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; } } } if ((inside_bucket_flag & (ISECT_2|ISECT_3)) != (ISECT_2|ISECT_3)) { - if (line_clip_rect2f(bucket_bounds, v2coSS, v3coSS, vClipSS_A, vClipSS_B)) { - if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], vClipSS_A); (*tot)++; } - if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], vClipSS_B); (*tot)++; } + if (line_clip_rect2f(bucket_bounds, v2coSS, v3coSS, v1_clipSS, v2_clipSS)) { + if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; } + if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; } } } if ((inside_bucket_flag & (ISECT_3|ISECT_1)) != (ISECT_3|ISECT_1)) { - if (line_clip_rect2f(bucket_bounds, v3coSS, v1coSS, vClipSS_A, vClipSS_B)) { - if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], vClipSS_A); (*tot)++; } - if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], vClipSS_B); (*tot)++; } + if (line_clip_rect2f(bucket_bounds, v3coSS, v1coSS, v1_clipSS, v2_clipSS)) { + if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; } + if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; } } } @@ -2081,49 +2118,26 @@ static void project_bucket_clip_face( /* Collect angles for every point around the center point */ -#if 1 /* starting not so pretty, slightly faster loop */ - - vClipSS_A[0] = cent[0]; /* Abuse this var for the loop below */ - vClipSS_A[1] = cent[1] + 1.0f; - - for(i=0; i<(*tot); i++) { - vClipSS_B[0] = isectVCosSS[i][0] - cent[0]; - vClipSS_B[1] = isectVCosSS[i][1] - cent[1]; - isectVAngles[i] = -atan2(vClipSS_A[0]*vClipSS_B[1] - vClipSS_A[1]*vClipSS_B[0], vClipSS_A[0]*vClipSS_B[0]+vClipSS_A[1]*vClipSS_B[1]); - if (flip) - isectVAngles[i] = -isectVAngles[i]; - } -#endif /* end abuse */ #if 0 /* uses a few more cycles then the above loop */ for(i=0; i<(*tot); i++) { - isectVAngles[i] = angle_2d_clockwise(up, cent, isectVCosSS[i]); + isectVCosSS[i][2] = angle_2d_clockwise(up, cent, isectVCosSS[i]); } #endif + + v1_clipSS[0] = cent[0]; /* Abuse this var for the loop below */ + v1_clipSS[1] = cent[1] + 1.0f; - /* kindof sucks donkeyballs we have to sort an array, just to clip a 2D triangle/quad - * but a lot less hassle then other methods we might calc this. */ - unsorted = TRUE; - while (unsorted==TRUE) { - unsorted = FALSE; - for(i=1; i<(*tot); i++) { - if (isectVAngles[i-1] < isectVAngles[i]) { - - /* swap UV's */ - VECCOPY2D(uv, isectVCosSS[i]); - VECCOPY2D(isectVCosSS[i], isectVCosSS[i-1]); - VECCOPY2D(isectVCosSS[i-1], uv); - - /* swap isectVAngles */ - tmp_f = isectVAngles[i-1]; - isectVAngles[i-1] = isectVAngles[i]; - isectVAngles[i] = tmp_f; - - unsorted = TRUE; - } - } + for(i=0; i<(*tot); i++) { + v2_clipSS[0] = isectVCosSS[i][0] - cent[0]; + v2_clipSS[1] = isectVCosSS[i][1] - cent[1]; + isectVCosSS[i][2] = atan2(v1_clipSS[0]*v2_clipSS[1] - v1_clipSS[1]*v2_clipSS[0], v1_clipSS[0]*v2_clipSS[0]+v1_clipSS[1]*v2_clipSS[1]); } + if (flip) qsort(isectVCosSS, *tot, sizeof(float)*3, float_z_sort_flip); + else qsort(isectVCosSS, *tot, sizeof(float)*3, float_z_sort); + + /* remove doubles */ /* first/last check */ if (fabs(isectVCosSS[0][0]-isectVCosSS[(*tot)-1][0]) < PROJ_GEOM_TOLERANCE && fabs(isectVCosSS[0][1]-isectVCosSS[(*tot)-1][1]) < PROJ_GEOM_TOLERANCE) { @@ -2141,7 +2155,9 @@ static void project_bucket_clip_face( while (doubles==TRUE) { doubles = FALSE; for(i=1; i<(*tot); i++) { - if (fabs(isectVCosSS[i-1][0]-isectVCosSS[i][0]) < PROJ_GEOM_TOLERANCE && fabs(isectVCosSS[i-1][1]-isectVCosSS[i][1]) < PROJ_GEOM_TOLERANCE) { + if (fabs(isectVCosSS[i-1][0]-isectVCosSS[i][0]) < PROJ_GEOM_TOLERANCE && + fabs(isectVCosSS[i-1][1]-isectVCosSS[i][1]) < PROJ_GEOM_TOLERANCE) + { int j; for(j=i+1; j<(*tot); j++) { isectVCosSS[j-1][0] = isectVCosSS[j][0]; @@ -3355,889 +3371,953 @@ static void project_paint_end(ProjPaintState *ps) /* external functions */ /* 1= an undo, -1 is a redo. */ -void undo_imagepaint_step(int step) +static void partial_redraw_array_init(ImagePaintPartialRedraw *pr) { - UndoElem *undo; - - if(step==1) { - if(curundo==NULL) error("No more steps to undo"); - else { - if(G.f & G_DEBUG) printf("undo %s\n", curundo->name); - undo_restore(curundo); - curundo= curundo->prev; - } - } - else if(step==-1) { - if((curundo!=NULL && curundo->next==NULL) || undobase.first==NULL) error("No more steps to redo"); - else { - undo= (curundo && curundo->next)? curundo->next: undobase.first; - undo_restore(undo); - curundo= undo; - if(G.f & G_DEBUG) printf("redo %s\n", undo->name); - } + int tot = PROJ_BOUNDBOX_SQUARED; + while (tot--) { + pr->x1 = 10000000; + pr->y1 = 10000000; + + pr->x2 = -1; + pr->y2 = -1; + + pr->enabled = 1; + + pr++; } - - allqueue(REDRAWVIEW3D, 0); - allqueue(REDRAWIMAGE, 0); } -void undo_imagepaint_clear(void) + +static int partial_redraw_array_merge(ImagePaintPartialRedraw *pr, ImagePaintPartialRedraw *pr_other, int tot) { - UndoElem *uel; - - uel= undobase.first; - while(uel) { - undo_free(uel); - uel= uel->next; + int touch; + while (tot--) { + pr->x1 = MIN2(pr->x1, pr_other->x1); + pr->y1 = MIN2(pr->y1, pr_other->y1); + + pr->x2 = MAX2(pr->x2, pr_other->x2); + pr->y2 = MAX2(pr->y2, pr_other->y2); + + if (pr->x2 != -2) + touch = 1; + + pr++; pr_other++; } - - BLI_freelistN(&undobase); - curundo= NULL; + + return touch; } -/* Imagepaint Partial Redraw & Dirty Region */ - -static void imapaint_clear_partial_redraw() +/* Loop over all images on this mesh and update any we have touched */ +static int project_image_refresh_tagged(ProjPaintState *ps) { - memset(&imapaintpartial, 0, sizeof(imapaintpartial)); + ImagePaintPartialRedraw *pr; + ProjPaintImage *projIma; + int a,i; + int redraw = 0; + + + for (a=0, projIma=ps->projImages; a < ps->image_tot; a++, projIma++) { + if (projIma->touch) { + /* look over each bound cell */ + for (i=0; ipartRedrawRect[i]); + if (pr->x2 != -1) { /* TODO - use 'enabled' ? */ + imapaintpartial = *pr; + imapaint_image_update(projIma->ima, projIma->ibuf, 1); /*last 1 is for texpaint*/ + redraw = 1; + } + } + + projIma->touch = 0; /* clear for reuse */ + } + } + + return redraw; } -static void imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int h) +/* run this per painting onto each mouse location */ +static int project_bucket_iter_init(ProjPaintState *ps, const float mval_f[2]) { - ImBuf *tmpibuf = NULL; - UndoTile *tile; - int srcx= 0, srcy= 0, origx; - - IMB_rectclip(ibuf, NULL, &x, &y, &srcx, &srcy, &w, &h); - - if (w == 0 || h == 0) - return; + float min_brush[2], max_brush[2]; + float size_half = ((float)ps->brush->size) * 0.5f; - if (!imapaintpartial.enabled) { - imapaintpartial.x1 = x; - imapaintpartial.y1 = y; - imapaintpartial.x2 = x+w; - imapaintpartial.y2 = y+h; - imapaintpartial.enabled = 1; - } - else { - imapaintpartial.x1 = MIN2(imapaintpartial.x1, x); - imapaintpartial.y1 = MIN2(imapaintpartial.y1, y); - imapaintpartial.x2 = MAX2(imapaintpartial.x2, x+w); - imapaintpartial.y2 = MAX2(imapaintpartial.y2, y+h); + /* so we dont have a bucket bounds that is way too small to paint into */ + // if (size_half < 1.0f) size_half = 1.0f; // this dosnt work yet :/ + + min_brush[0] = mval_f[0] - size_half; + min_brush[1] = mval_f[1] - size_half; + + max_brush[0] = mval_f[0] + size_half; + max_brush[1] = mval_f[1] + size_half; + + /* offset to make this a valid bucket index */ + project_paint_bucket_bounds(ps, min_brush, max_brush, ps->bucketMin, ps->bucketMax); + + /* mouse outside the model areas? */ + if (ps->bucketMin[0]==ps->bucketMax[0] || ps->bucketMin[1]==ps->bucketMax[1]) { + return 0; } - - w = ((x + w - 1) >> IMAPAINT_TILE_BITS); - h = ((y + h - 1) >> IMAPAINT_TILE_BITS); - origx = (x >> IMAPAINT_TILE_BITS); - y = (y >> IMAPAINT_TILE_BITS); - for (; y <= h; y++) { - for (x=origx; x <= w; x++) { - for(tile=curundo->tiles.first; tile; tile=tile->next) - if(tile->x == x && tile->y == y && strcmp(tile->id.name, ima->id.name)==0) - break; + ps->context_bucket_x = ps->bucketMin[0]; + ps->context_bucket_y = ps->bucketMin[1]; + return 1; +} - if(!tile) { - undo_init_tile(&ima->id, ibuf, &tmpibuf, x, y); +static int project_bucket_iter_next(ProjPaintState *ps, int *bucket_index, rctf *bucket_bounds, const float mval[2]) +{ + if (ps->thread_tot > 1) + BLI_lock_thread(LOCK_CUSTOM1); + + //printf("%d %d \n", ps->context_bucket_x, ps->context_bucket_y); + + for ( ; ps->context_bucket_y < ps->bucketMax[1]; ps->context_bucket_y++) { + for ( ; ps->context_bucket_x < ps->bucketMax[0]; ps->context_bucket_x++) { + + /* use bucket_bounds for project_bucket_isect_circle and project_bucket_init*/ + project_bucket_bounds(ps, ps->context_bucket_x, ps->context_bucket_y, bucket_bounds); + + if (project_bucket_isect_circle(ps->context_bucket_x, ps->context_bucket_y, mval, ps->brush->size * ps->brush->size, bucket_bounds)) { + *bucket_index = ps->context_bucket_x + (ps->context_bucket_y * ps->buckets_x); + ps->context_bucket_x++; + + if (ps->thread_tot > 1) + BLI_unlock_thread(LOCK_CUSTOM1); + + return 1; } } + ps->context_bucket_x = ps->bucketMin[0]; } + + if (ps->thread_tot > 1) + BLI_unlock_thread(LOCK_CUSTOM1); + return 0; +} - ibuf->userflags |= IB_BITMAPDIRTY; +/* Each thread gets one of these, also used as an argument to pass to project_paint_op */ +typedef struct ProjectHandle { + /* args */ + ProjPaintState *ps; + float prevmval[2]; + float mval[2]; - if (tmpibuf) - IMB_freeImBuf(tmpibuf); + /* annoying but we need to have image bounds per thread, then merge into ps->projectPartialRedraws */ + ProjPaintImage *projImages; /* array of partial redraws */ + + /* thread settings */ + int thread_index; +} ProjectHandle; + +static void blend_color_mix(unsigned char *cp, const unsigned char *cp1, const unsigned char *cp2, 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; } -static void imapaint_image_update(Image *image, ImBuf *ibuf, short texpaint) +static void blend_color_mix_float(float *cp, const float *cp1, const float *cp2, const float fac) { - if(ibuf->rect_float) - imb_freerectImBuf(ibuf); /* force recreate of char rect */ /* TODO - should just update a portion from imapaintpartial! */ - if(ibuf->mipmap[0]) - imb_freemipmapImBuf(ibuf); + const float mfac= 1.0-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]; +} - /* todo: should set_tpage create ->rect? */ - if(texpaint || G.sima->lock) { - int w = imapaintpartial.x2 - imapaintpartial.x1; - int h = imapaintpartial.y2 - imapaintpartial.y1; - // printf("%d, %d, \n", w, h); - GPU_paint_update_image(image, imapaintpartial.x1, imapaintpartial.y1, w, h); +static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, short blend) +{ + if (ps->is_airbrush==0 && mask < 1.0f) { + projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*255), 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), blend); } } -/* note; gets called for both 2d image paint and 3d texture paint. in the - latter case image may be NULL and G.sima may not exist */ -static void imapaint_redraw(int final, int texpaint, Image *image) +static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, short blend) { - if(final) { - if(texpaint) - allqueue(REDRAWIMAGE, 0); - else if(!G.sima->lock) { - if(image) - GPU_free_image(image); /* force OpenGL reload */ - allqueue(REDRAWVIEW3D, 0); - } - allqueue(REDRAWHEADERS, 0); - - if(!texpaint && image) { - /* after paint, tag Image or RenderResult nodes changed */ - if(G.scene->nodetree) { - imagepaint_composite_tags(G.scene->nodetree, image, &G.sima->iuser); - } - /* signal composite (hurmf, need an allqueue?) */ - if(G.sima->lock) { - ScrArea *sa; - for(sa=G.curscreen->areabase.first; sa; sa= sa->next) { - if(sa->spacetype==SPACE_NODE) { - if(((SpaceNode *)sa->spacedata.first)->treetype==NTREE_COMPOSIT) { - addqueue(sa->win, UI_BUT_EVENT, B_NODE_TREE_EXEC); - break; - } - } - } - } - } - } - else if(!texpaint && G.sima->lock) - force_draw_plus(SPACE_VIEW3D, 0); - else - force_draw(0); -} - -/* Image Paint Operations */ - -static void imapaint_ibuf_get_set_rgb(ImBuf *ibuf, int x, int y, short torus, short set, float *rgb) -{ - if (torus) { - x %= ibuf->x; - if (x < 0) x += ibuf->x; - y %= ibuf->y; - if (y < 0) y += ibuf->y; - } - - if (ibuf->rect_float) { - float *rrgbf = ibuf->rect_float + (ibuf->x*y + x)*4; - - if (set) { - IMAPAINT_FLOAT_RGB_COPY(rrgbf, rgb); - } else { - IMAPAINT_FLOAT_RGB_COPY(rgb, rrgbf); - } + if (ps->is_airbrush==0 && mask < 1.0f) { + IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, ((ProjPixelClone *)projPixel)->clonepx.f, alpha, blend); + blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, mask); } else { - char *rrgb = (char*)ibuf->rect + (ibuf->x*y + x)*4; - - if (set) { - IMAPAINT_FLOAT_RGB_TO_CHAR(rrgb, rgb) - } else { - IMAPAINT_CHAR_RGB_TO_FLOAT(rgb, rrgb) - } + IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.f, alpha*mask, blend); } } -static int imapaint_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, float *outrgb, short torus) +/* do_projectpaint_smear* + * + * note, mask is used to modify the alpha here, this is not correct since it allows + * 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 *rgba, float alpha, float mask, short blend, MemArena *smearArena, LinkNode **smearPixels, float co[2]) { - float inrgb[3]; - - if ((x >= ibuf->x) || (y >= ibuf->y)) { - if (torus) imapaint_ibuf_get_set_rgb(ibuf, x, y, 1, 0, inrgb); - else return 0; - } - else imapaint_ibuf_get_set_rgb(ibuf, x, y, 0, 0, inrgb); - - outrgb[0] += inrgb[0]; - outrgb[1] += inrgb[1]; - outrgb[2] += inrgb[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), blend); + BLI_linklist_prepend_arena(smearPixels, (void *)projPixel, smearArena); +} - return 1; +static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, short blend, MemArena *smearArena, LinkNode **smearPixels_f, float co[2]) +{ + unsigned char rgba_ub[4]; + unsigned char rgba_smear[4]; + + if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1)==0) + return; + + IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_smear, projPixel->pixel.f_pt); + ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*mask*255), blend); + BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena); } -static void imapaint_lift_soften(ImBuf *ibuf, ImBuf *ibufb, int *pos, short torus) +static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, short blend) { - int x, y, count, xi, yi, xo, yo; - int out_off[2], in_off[2], dim[2]; - float outrgb[3]; - - dim[0] = ibufb->x; - dim[1] = ibufb->y; - in_off[0] = pos[0]; - in_off[1] = pos[1]; - out_off[0] = out_off[1] = 0; - - if (!torus) { - IMB_rectclip(ibuf, ibufb, &in_off[0], &in_off[1], &out_off[0], - &out_off[1], &dim[0], &dim[1]); - - if ((dim[0] == 0) || (dim[1] == 0)) - return; + unsigned char rgba_ub[4]; + + if (ps->is_texbrush) { + rgba_ub[0] = FTOCHAR(rgba[0] * ps->brush->rgb[0]); + rgba_ub[1] = FTOCHAR(rgba[1] * ps->brush->rgb[1]); + rgba_ub[2] = FTOCHAR(rgba[2] * ps->brush->rgb[2]); + rgba_ub[3] = FTOCHAR(rgba[3]); } - - for (y=0; y < dim[1]; y++) { - for (x=0; x < dim[0]; x++) { - /* get input pixel */ - xi = in_off[0] + x; - yi = in_off[1] + y; - - count = 1; - imapaint_ibuf_get_set_rgb(ibuf, xi, yi, torus, 0, outrgb); - - count += imapaint_ibuf_add_if(ibuf, xi-1, yi-1, outrgb, torus); - count += imapaint_ibuf_add_if(ibuf, xi-1, yi , outrgb, torus); - count += imapaint_ibuf_add_if(ibuf, xi-1, yi+1, outrgb, torus); - - count += imapaint_ibuf_add_if(ibuf, xi , yi-1, outrgb, torus); - count += imapaint_ibuf_add_if(ibuf, xi , yi+1, outrgb, torus); - - count += imapaint_ibuf_add_if(ibuf, xi+1, yi-1, outrgb, torus); - count += imapaint_ibuf_add_if(ibuf, xi+1, yi , outrgb, torus); - count += imapaint_ibuf_add_if(ibuf, xi+1, yi+1, outrgb, torus); - - outrgb[0] /= count; - outrgb[1] /= count; - outrgb[2] /= count; - - /* write into brush buffer */ - xo = out_off[0] + x; - yo = out_off[1] + y; - imapaint_ibuf_get_set_rgb(ibufb, xo, yo, 0, 1, outrgb); - } + else { + IMAPAINT_FLOAT_RGB_TO_CHAR(rgba_ub, ps->brush->rgb); + rgba_ub[3] = 255; + } + + if (ps->is_airbrush==0 && mask < 1.0f) { + projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, *((unsigned int *)rgba_ub), (int)(alpha*255), 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, *((unsigned int *)rgba_ub), (int)(alpha*mask*255), blend); } } -static void imapaint_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos) -{ - IMB_rectblend_torus(ibufb, ibuf, 0, 0, pos[0], pos[1], - ibufb->x, ibufb->y, IMB_BLEND_COPY_RGB); -} - -static ImBuf *imapaint_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos) -{ - /* note: allocImbuf returns zero'd memory, so regions outside image will - have zero alpha, and hence not be blended onto the image */ - int w=ibufb->x, h=ibufb->y, destx=0, desty=0, srcx=pos[0], srcy=pos[1]; - ImBuf *clonebuf= IMB_allocImBuf(w, h, ibufb->depth, ibufb->flags, 0); - - IMB_rectclip(clonebuf, ibuf, &destx, &desty, &srcx, &srcy, &w, &h); - IMB_rectblend(clonebuf, ibuf, destx, desty, srcx, srcy, w, h, - IMB_BLEND_COPY_RGB); - IMB_rectblend(clonebuf, ibufb, destx, desty, destx, desty, w, h, - IMB_BLEND_COPY_ALPHA); - - return clonebuf; -} - -static void imapaint_convert_brushco(ImBuf *ibufb, float *pos, int *ipos) -{ - ipos[0]= (int)(pos[0] - ibufb->x/2); - ipos[1]= (int)(pos[1] - ibufb->y/2); -} - -/* dosnt run for projection painting - * only the old style painting in the 3d view */ -static int imapaint_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos) -{ - ImagePaintState *s= ((ImagePaintState*)state); - ImBuf *clonebuf= NULL; - short torus= s->brush->flag & BRUSH_TORUS; - short blend= s->blend; - float *offset= s->brush->clone.offset; - float liftpos[2]; - int bpos[2], blastpos[2], bliftpos[2]; - - imapaint_convert_brushco(ibufb, pos, bpos); - - /* lift from canvas */ - if(s->tool == PAINT_TOOL_SOFTEN) { - imapaint_lift_soften(s->canvas, ibufb, bpos, torus); +static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, short blend) { + if (ps->is_texbrush) { + rgba[0] *= ps->brush->rgb[0]; + rgba[1] *= ps->brush->rgb[1]; + rgba[2] *= ps->brush->rgb[2]; } - else if(s->tool == PAINT_TOOL_SMEAR) { - if (lastpos[0]==pos[0] && lastpos[1]==pos[1]) - return 0; - - imapaint_convert_brushco(ibufb, lastpos, blastpos); - imapaint_lift_smear(s->canvas, ibufb, blastpos); + else { + VECCOPY(rgba, ps->brush->rgb); } - else if(s->tool == PAINT_TOOL_CLONE && s->clonecanvas) { - liftpos[0]= pos[0] - offset[0]*s->canvas->x; - liftpos[1]= pos[1] - offset[1]*s->canvas->y; - - imapaint_convert_brushco(ibufb, liftpos, bliftpos); - clonebuf= imapaint_lift_clone(s->clonecanvas, ibufb, bliftpos); + + if (ps->is_airbrush==0 && mask < 1.0f) { + IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, rgba, alpha, 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, rgba, alpha*mask, blend); } - - imapaint_dirty_region(s->image, s->canvas, bpos[0], bpos[1], ibufb->x, ibufb->y); - - /* blend into canvas */ - if(torus) - IMB_rectblend_torus(s->canvas, (clonebuf)? clonebuf: ibufb, - bpos[0], bpos[1], 0, 0, ibufb->x, ibufb->y, blend); - else - IMB_rectblend(s->canvas, (clonebuf)? clonebuf: ibufb, - bpos[0], bpos[1], 0, 0, ibufb->x, ibufb->y, blend); - - if(clonebuf) IMB_freeImBuf(clonebuf); - - return 1; -} - -/* 2D ImagePaint */ - -static void imapaint_compute_uvco(short *mval, float *uv) -{ - areamouseco_to_ipoco(G.v2d, mval, &uv[0], &uv[1]); } -/* 3D TexturePaint */ -int facesel_face_pick(Mesh *me, short *mval, unsigned int *index, short rect); -void texpaint_pick_uv(Object *ob, Mesh *mesh, unsigned int faceindex, short *xy, float *mousepos); -static int texpaint_break_stroke(float *prevuv, float *fwuv, float *bkuv, float *uv) +/* run this for single and multithreaded painting */ +static void *do_projectpaint_thread(void *ph_v) { - float d1[2], d2[2]; - float mismatch = Vec2Lenf(fwuv, uv); - float len1 = Vec2Lenf(prevuv, fwuv); - float len2 = Vec2Lenf(bkuv, uv); - - Vec2Subf(d1, fwuv, prevuv); - Vec2Subf(d2, uv, bkuv); - - return ((Inp2f(d1, d2) < 0.0f) || (mismatch > MAX2(len1, len2)*2)); -} - -/* ImagePaint Common */ + /* First unpack args from the struct */ + ProjPaintState *ps = ((ProjectHandle *)ph_v)->ps; + ProjPaintImage *projImages = ((ProjectHandle *)ph_v)->projImages; + const float *lastpos = ((ProjectHandle *)ph_v)->prevmval; + const float *pos = ((ProjectHandle *)ph_v)->mval; + const int thread_index = ((ProjectHandle *)ph_v)->thread_index; + /* Done with args from ProjectHandle */ -static int imapaint_canvas_set(ImagePaintState *s, Image *ima) -{ - ImBuf *ibuf= BKE_image_get_ibuf(ima, G.sima?&G.sima->iuser:NULL); + LinkNode *node; + ProjPixel *projPixel; - /* verify that we can paint and set canvas */ - if(ima->packedfile && ima->rr) { - s->warnpackedfile = ima->id.name + 2; - return 0; - } - else if(ibuf && ibuf->channels!=4) { - s->warnmultifile = ima->id.name + 2; - return 0; + int last_index = -1; + ProjPaintImage *last_projIma; + ImagePaintPartialRedraw *last_partial_redraw_cell; + + float rgba[4], alpha, dist, dist_nosqrt; + + float brush_size_sqared; + int bucket_index; + int is_floatbuf = 0; + short blend= ps->blend; + const short tool = ps->tool; + rctf bucket_bounds; + + /* for smear only */ + float pos_ofs[2]; + float co[2]; + float mask = 1.0f; /* airbrush wont use mask */ + unsigned short mask_short; + + LinkNode *smearPixels = NULL; + LinkNode *smearPixels_f = NULL; + MemArena *smearArena = NULL; /* mem arena for this brush projection only */ + + + if (tool==PAINT_TOOL_SMEAR) { + pos_ofs[0] = pos[0] - lastpos[0]; + pos_ofs[1] = pos[1] - lastpos[1]; + + smearArena = BLI_memarena_new(1<<16); } - else if(!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) - return 0; - - s->image= ima; - s->canvas= ibuf; - - /* set clone canvas */ - if(s->tool == PAINT_TOOL_CLONE) { - ima= s->brush->clone.image; - ibuf= BKE_image_get_ibuf(ima, G.sima?&G.sima->iuser:NULL); + + /* avoid a square root with every dist comparison */ + brush_size_sqared = ps->brush->size * ps->brush->size; + + /* printf("brush bounds %d %d %d %d\n", bucketMin[0], bucketMin[1], bucketMax[0], bucketMax[1]); */ + + while (project_bucket_iter_next(ps, &bucket_index, &bucket_bounds, pos)) { - if(!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) - return 0; - - s->clonecanvas= ibuf; + /* Check this bucket and its faces are initialized */ + if (ps->bucketFlags[bucket_index] == PROJ_BUCKET_NULL) { + /* No pixels initialized */ + project_bucket_init(ps, thread_index, bucket_index, &bucket_bounds); + } - if(s->canvas->rect_float && !s->clonecanvas->rect_float) { - /* temporarily add float rect for cloning */ - IMB_float_from_rect(s->clonecanvas); - s->clonefreefloat= 1; + for (node = ps->bucketRect[bucket_index]; node; node = node->next) { + + projPixel = (ProjPixel *)node->link; + + /*dist = Vec2Lenf(projPixel->projCoSS, pos);*/ /* correct but uses a sqrt */ + dist_nosqrt = Vec2Lenf_nosqrt(projPixel->projCoSS, pos); + + /*if (dist < s->brush->size) {*/ /* correct but uses a sqrt */ + if (dist_nosqrt < brush_size_sqared) { + dist = (float)sqrt(dist_nosqrt); + + if (ps->is_texbrush) { + brush_sample_tex(ps->brush, projPixel->projCoSS, rgba); + alpha = rgba[3]; + } else { + alpha = 1.0f; + } + + if (ps->is_airbrush) { + /* for an aurbrush there is no real mask, so just multiply the alpha by it */ + alpha *= brush_sample_falloff(ps->brush, dist); + mask = ((float)projPixel->mask)/65535.0f; + } + else { + alpha *= brush_sample_falloff_noalpha(ps->brush, dist); + mask_short = projPixel->mask * (alpha*ps->brush->alpha); + if (mask_short > projPixel->mask_max) { + mask = ((float)mask_short)/65535.0f; + projPixel->mask_max = mask_short; + } + else { + /* Go onto the next pixel */ + continue; + } + } + + if (alpha >= 0.0f) { + + if (last_index != projPixel->image_index) { + last_index = projPixel->image_index; + last_projIma = projImages + last_index; + + last_projIma->touch = 1; + is_floatbuf = last_projIma->ibuf->rect_float ? 1 : 0; + } + + last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index; + last_partial_redraw_cell->x1 = MIN2(last_partial_redraw_cell->x1, projPixel->x_px); + last_partial_redraw_cell->y1 = MIN2(last_partial_redraw_cell->y1, projPixel->y_px); + + last_partial_redraw_cell->x2 = MAX2(last_partial_redraw_cell->x2, projPixel->x_px+1); + last_partial_redraw_cell->y2 = MAX2(last_partial_redraw_cell->y2, projPixel->y_px+1); + + + switch(tool) { + case PAINT_TOOL_CLONE: + if (is_floatbuf) { + if (((ProjPixelClone *)projPixel)->clonepx.f[3]) { + do_projectpaint_clone_f(ps, projPixel, rgba, alpha, mask, blend); + } + } + else { + if (((ProjPixelClone*)projPixel)->clonepx.ch[3]) { + do_projectpaint_clone(ps, projPixel, rgba, alpha, mask, blend); + } + } + break; + case PAINT_TOOL_SMEAR: + Vec2Subf(co, projPixel->projCoSS, pos_ofs); + + if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, rgba, alpha, mask, blend, smearArena, &smearPixels_f, co); + else do_projectpaint_smear(ps, projPixel, rgba, alpha, mask, blend, smearArena, &smearPixels, co); + break; + default: + if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, rgba, alpha, mask, blend); + else do_projectpaint_draw(ps, projPixel, rgba, alpha, mask, blend); + break; + } + } + /* done painting */ + } } - else if(!s->canvas->rect_float && !s->clonecanvas->rect) - IMB_rect_from_float(s->clonecanvas); } - return 1; -} - -static void imapaint_canvas_free(ImagePaintState *s) -{ - if (s->clonefreefloat) - imb_freerectfloatImBuf(s->clonecanvas); + + if (tool==PAINT_TOOL_SMEAR) { + + for (node= smearPixels; node; node= node->next) { /* this wont run for a float image */ + projPixel = node->link; + *projPixel->pixel.uint_pt = ((ProjPixelClone *)projPixel)->clonepx.uint; + } + + for (node= smearPixels_f; node; node= node->next) { /* this wont run for a float image */ + projPixel = node->link; + IMAPAINT_CHAR_RGBA_TO_FLOAT(projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.ch); + node = node->next; + } + + BLI_memarena_free(smearArena); + } + + return NULL; } -static int imapaint_paint_sub_stroke(ImagePaintState *s, BrushPainter *painter, Image *image, short texpaint, float *uv, double time, int update, float pressure) +static int project_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos) { - ImBuf *ibuf= BKE_image_get_ibuf(image, G.sima?&G.sima->iuser:NULL); - float pos[2]; - - if(!ibuf) + /* First unpack args from the struct */ + ProjPaintState *ps = (ProjPaintState *)state; + int touch_any = 0; + + ProjectHandle handles[BLENDER_MAX_THREADS]; + ListBase threads; + int a,i; + + if (!project_bucket_iter_init(ps, pos)) { return 0; - - pos[0] = uv[0]*ibuf->x; - pos[1] = uv[1]*ibuf->y; - - brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0); - - if (brush_painter_paint(painter, imapaint_paint_op, pos, time, pressure, s)) { - if (update) - imapaint_image_update(image, ibuf, texpaint); - return 1; } - else return 0; -} - -static void partial_redraw_array_init(ImagePaintPartialRedraw *pr) -{ - int tot = PROJ_BOUNDBOX_SQUARED; - while (tot--) { - pr->x1 = 10000000; - pr->y1 = 10000000; + + if (ps->thread_tot > 1) + BLI_init_threads(&threads, do_projectpaint_thread, ps->thread_tot); + + /* get the threads running */ + for(a=0; a < ps->thread_tot; a++) { - pr->x2 = -1; - pr->y2 = -1; + /* set defaults in handles */ + //memset(&handles[a], 0, sizeof(BakeShade)); - pr->enabled = 1; + handles[a].ps = ps; + VECCOPY2D(handles[a].mval, pos); + VECCOPY2D(handles[a].prevmval, lastpos); - pr++; - } -} - -static int partial_redraw_array_merge(ImagePaintPartialRedraw *pr, ImagePaintPartialRedraw *pr_other, int tot) -{ - int touch; - while (tot--) { - pr->x1 = MIN2(pr->x1, pr_other->x1); - pr->y1 = MIN2(pr->y1, pr_other->y1); + /* thread spesific */ + handles[a].thread_index = a; - pr->x2 = MAX2(pr->x2, pr_other->x2); - pr->y2 = MAX2(pr->y2, pr_other->y2); + handles[a].projImages = (ProjPaintImage *)BLI_memarena_alloc(ps->arena_mt[a], ps->image_tot * sizeof(ProjPaintImage)); - if (pr->x2 != -2) - touch = 1; + memcpy(handles[a].projImages, ps->projImages, ps->image_tot * sizeof(ProjPaintImage)); + + /* image bounds */ + for (i=0; i< ps->image_tot; i++) { + handles[a].projImages[i].partRedrawRect = (ImagePaintPartialRedraw *)BLI_memarena_alloc(ps->arena_mt[a], sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); + memcpy(handles[a].projImages[i].partRedrawRect, ps->projImages[i].partRedrawRect, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); + } + + if (ps->thread_tot > 1) + BLI_insert_thread(&threads, &handles[a]); + } + + if (ps->thread_tot > 1) /* wait for everything to be done */ + BLI_end_threads(&threads); + else + do_projectpaint_thread(&handles[0]); + + + /* move threaded bounds back into ps->projectPartialRedraws */ + for(i=0; i < ps->image_tot; i++) { + int touch = 0; + for(a=0; a < ps->thread_tot; a++) { + touch |= partial_redraw_array_merge(ps->projImages[i].partRedrawRect, handles[a].projImages[i].partRedrawRect, PROJ_BOUNDBOX_SQUARED); + } - pr++; pr_other++; + if (touch) { + ps->projImages[i].touch = 1; + touch_any = 1; + } } - return touch; + return touch_any; } -/* Loop over all images on this mesh and update any we have touched */ -static int imapaint_refresh_tagged(ProjPaintState *ps) + +static int project_paint_sub_stroke(ProjPaintState *ps, BrushPainter *painter, short *prevmval_i, short *mval_i, double time, float pressure) { - ImagePaintPartialRedraw *pr; - ProjPaintImage *projIma; - int a,i; - int redraw = 0; + /* Use mouse coords as floats for projection painting */ + float pos[2]; - for (a=0, projIma=ps->projImages; a < ps->image_tot; a++, projIma++) { - if (projIma->touch) { - /* look over each bound cell */ - for (i=0; ipartRedrawRect[i]); - if (pr->x2 != -1) { /* TODO - use 'enabled' ? */ - imapaintpartial = *pr; - imapaint_image_update(projIma->ima, projIma->ibuf, 1); /*last 1 is for texpaint*/ - redraw = 1; - } - } - - projIma->touch = 0; /* clear for reuse */ - } - } + pos[0] = mval_i[0]; + pos[1] = mval_i[1]; - return redraw; + // we may want to use this later + // brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0); + + if (brush_painter_paint(painter, project_paint_op, pos, time, pressure, ps)) { + return 1; + } + else return 0; } -/* run this per painting onto each mouse location */ -static int project_bucket_iter_init(ProjPaintState *ps, const float mval_f[2]) + +static int project_paint_stroke(ProjPaintState *ps, BrushPainter *painter, short *prevmval_i, short *mval_i, double time, int update, float pressure) { - float min_brush[2], max_brush[2]; - float size_half = ((float)ps->brush->size) * 0.5f; - - /* so we dont have a bucket bounds that is way too small to paint into */ - // if (size_half < 1.0f) size_half = 1.0f; // this dosnt work yet :/ + int a, redraw = 0; - min_brush[0] = mval_f[0] - size_half; - min_brush[1] = mval_f[1] - size_half; + for (a=0; a < ps->image_tot; a++) { + partial_redraw_array_init(ps->projImages[a].partRedrawRect); + } - max_brush[0] = mval_f[0] + size_half; - max_brush[1] = mval_f[1] + size_half; + redraw |= project_paint_sub_stroke(ps, painter, prevmval_i, mval_i, time, pressure); - /* offset to make this a valid bucket index */ - project_paint_bucket_bounds(ps, min_brush, max_brush, ps->bucketMin, ps->bucketMax); + if (update) { + if (project_image_refresh_tagged(ps)) { + if (redraw) { + force_draw(0); /* imapaint_redraw just calls this in viewport paint anyway */ + /* imapaint_redraw(0, 1, NULL); */ + /* imapaint_clear_partial_redraw(); */ /* not needed since we use our own array */ + } + } + } - /* mouse outside the model areas? */ - if (ps->bucketMin[0]==ps->bucketMax[0] || ps->bucketMin[1]==ps->bucketMax[1]) { - return 0; + return redraw; +} + +void undo_imagepaint_step(int step) +{ + UndoElem *undo; + + if(step==1) { + if(curundo==NULL) error("No more steps to undo"); + else { + if(G.f & G_DEBUG) printf("undo %s\n", curundo->name); + undo_restore(curundo); + curundo= curundo->prev; + } + } + else if(step==-1) { + if((curundo!=NULL && curundo->next==NULL) || undobase.first==NULL) error("No more steps to redo"); + else { + undo= (curundo && curundo->next)? curundo->next: undobase.first; + undo_restore(undo); + curundo= undo; + if(G.f & G_DEBUG) printf("redo %s\n", undo->name); + } } + + allqueue(REDRAWVIEW3D, 0); + allqueue(REDRAWIMAGE, 0); +} + +void undo_imagepaint_clear(void) +{ + UndoElem *uel; - ps->context_bucket_x = ps->bucketMin[0]; - ps->context_bucket_y = ps->bucketMin[1]; - return 1; + uel= undobase.first; + while(uel) { + undo_free(uel); + uel= uel->next; + } + + BLI_freelistN(&undobase); + curundo= NULL; } -static int project_bucket_iter_next(ProjPaintState *ps, int *bucket_index, rctf *bucket_bounds, const float mval[2]) +/* Imagepaint Partial Redraw & Dirty Region */ + +static void imapaint_clear_partial_redraw() { - if (ps->thread_tot > 1) - BLI_lock_thread(LOCK_CUSTOM1); + memset(&imapaintpartial, 0, sizeof(imapaintpartial)); +} + +static void imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int h) +{ + ImBuf *tmpibuf = NULL; + UndoTile *tile; + int srcx= 0, srcy= 0, origx; + + IMB_rectclip(ibuf, NULL, &x, &y, &srcx, &srcy, &w, &h); + + if (w == 0 || h == 0) + return; - //printf("%d %d \n", ps->context_bucket_x, ps->context_bucket_y); + if (!imapaintpartial.enabled) { + imapaintpartial.x1 = x; + imapaintpartial.y1 = y; + imapaintpartial.x2 = x+w; + imapaintpartial.y2 = y+h; + imapaintpartial.enabled = 1; + } + else { + imapaintpartial.x1 = MIN2(imapaintpartial.x1, x); + imapaintpartial.y1 = MIN2(imapaintpartial.y1, y); + imapaintpartial.x2 = MAX2(imapaintpartial.x2, x+w); + imapaintpartial.y2 = MAX2(imapaintpartial.y2, y+h); + } + + w = ((x + w - 1) >> IMAPAINT_TILE_BITS); + h = ((y + h - 1) >> IMAPAINT_TILE_BITS); + origx = (x >> IMAPAINT_TILE_BITS); + y = (y >> IMAPAINT_TILE_BITS); - for ( ; ps->context_bucket_y < ps->bucketMax[1]; ps->context_bucket_y++) { - for ( ; ps->context_bucket_x < ps->bucketMax[0]; ps->context_bucket_x++) { - - /* use bucket_bounds for project_bucket_isect_circle and project_bucket_init*/ - project_bucket_bounds(ps, ps->context_bucket_x, ps->context_bucket_y, bucket_bounds); - - if (project_bucket_isect_circle(ps->context_bucket_x, ps->context_bucket_y, mval, ps->brush->size * ps->brush->size, bucket_bounds)) { - *bucket_index = ps->context_bucket_x + (ps->context_bucket_y * ps->buckets_x); - ps->context_bucket_x++; - - if (ps->thread_tot > 1) - BLI_unlock_thread(LOCK_CUSTOM1); - - return 1; + for (; y <= h; y++) { + for (x=origx; x <= w; x++) { + for(tile=curundo->tiles.first; tile; tile=tile->next) + if(tile->x == x && tile->y == y && strcmp(tile->id.name, ima->id.name)==0) + break; + + if(!tile) { + undo_init_tile(&ima->id, ibuf, &tmpibuf, x, y); } } - ps->context_bucket_x = ps->bucketMin[0]; } - - if (ps->thread_tot > 1) - BLI_unlock_thread(LOCK_CUSTOM1); - return 0; -} -/* Each thread gets one of these, also used as an argument to pass to project_paint_op */ -typedef struct ProjectHandle { - /* args */ - ProjPaintState *ps; - float prevmval[2]; - float mval[2]; - - /* annoying but we need to have image bounds per thread, then merge into ps->projectPartialRedraws */ - ProjPaintImage *projImages; /* array of partial redraws */ + ibuf->userflags |= IB_BITMAPDIRTY; - /* thread settings */ - int thread_index; -} ProjectHandle; + if (tmpibuf) + IMB_freeImBuf(tmpibuf); +} -static void blend_color_mix(unsigned char *cp, const unsigned char *cp1, const unsigned char *cp2, const int fac) +static void imapaint_image_update(Image *image, ImBuf *ibuf, short texpaint) { - /* 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; + if(ibuf->rect_float) + imb_freerectImBuf(ibuf); /* force recreate of char rect */ /* TODO - should just update a portion from imapaintpartial! */ + if(ibuf->mipmap[0]) + imb_freemipmapImBuf(ibuf); - 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; + /* todo: should set_tpage create ->rect? */ + if(texpaint || G.sima->lock) { + int w = imapaintpartial.x2 - imapaintpartial.x1; + int h = imapaintpartial.y2 - imapaintpartial.y1; + // printf("%d, %d, \n", w, h); + GPU_paint_update_image(image, imapaintpartial.x1, imapaintpartial.y1, w, h); + } } -static void blend_color_mix_float(float *cp, const float *cp1, const float *cp2, const float fac) +/* note; gets called for both 2d image paint and 3d texture paint. in the + latter case image may be NULL and G.sima may not exist */ +static void imapaint_redraw(int final, int texpaint, Image *image) { - const float mfac= 1.0-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]; + if(final) { + if(texpaint) + allqueue(REDRAWIMAGE, 0); + else if(!G.sima->lock) { + if(image) + GPU_free_image(image); /* force OpenGL reload */ + allqueue(REDRAWVIEW3D, 0); + } + allqueue(REDRAWHEADERS, 0); + + if(!texpaint && image) { + /* after paint, tag Image or RenderResult nodes changed */ + if(G.scene->nodetree) { + imagepaint_composite_tags(G.scene->nodetree, image, &G.sima->iuser); + } + /* signal composite (hurmf, need an allqueue?) */ + if(G.sima->lock) { + ScrArea *sa; + for(sa=G.curscreen->areabase.first; sa; sa= sa->next) { + if(sa->spacetype==SPACE_NODE) { + if(((SpaceNode *)sa->spacedata.first)->treetype==NTREE_COMPOSIT) { + addqueue(sa->win, UI_BUT_EVENT, B_NODE_TREE_EXEC); + break; + } + } + } + } + } + } + else if(!texpaint && G.sima->lock) + force_draw_plus(SPACE_VIEW3D, 0); + else + force_draw(0); } -static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, short blend) +/* Image Paint Operations */ + +static void imapaint_ibuf_get_set_rgb(ImBuf *ibuf, int x, int y, short torus, short set, float *rgb) { - if (ps->is_airbrush==0 && projPixel->mask < 1.0f) { - projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*255), blend); - blend_color_mix(projPixel->pixel.ch_pt, projPixel->origColor.ch, projPixel->newColor.ch, (int)(projPixel->mask*255)); - } - else { - *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*255), blend); + if (torus) { + x %= ibuf->x; + if (x < 0) x += ibuf->x; + y %= ibuf->y; + if (y < 0) y += ibuf->y; } -} -static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, short blend) -{ - if (ps->is_airbrush==0 && projPixel->mask < 1.0f) { - IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, ((ProjPixelClone *)projPixel)->clonepx.f, alpha, blend); - blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, projPixel->mask); + if (ibuf->rect_float) { + float *rrgbf = ibuf->rect_float + (ibuf->x*y + x)*4; + + if (set) { + IMAPAINT_FLOAT_RGB_COPY(rrgbf, rgb); + } else { + IMAPAINT_FLOAT_RGB_COPY(rgb, rrgbf); + } } else { - IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.f, alpha, blend); + char *rrgb = (char*)ibuf->rect + (ibuf->x*y + x)*4; + + if (set) { + IMAPAINT_FLOAT_RGB_TO_CHAR(rrgb, rgb) + } else { + IMAPAINT_CHAR_RGB_TO_FLOAT(rgb, rrgb) + } } } -/* do_projectpaint_smear* - * - * note, mask is used to modify the alpha here, this is not correct since it allows - * 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 *rgba, float alpha, short blend, MemArena *smearArena, LinkNode **smearPixels, float co[2]) +static int imapaint_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, float *outrgb, short torus) { - 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*projPixel->mask*255), blend); - BLI_linklist_prepend_arena(smearPixels, (void *)projPixel, smearArena); -} + float inrgb[3]; -static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, short blend, MemArena *smearArena, LinkNode **smearPixels_f, float co[2]) -{ - unsigned char rgba_ub[4]; - unsigned char rgba_smear[4]; - - if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1)==0) - return; - - IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_smear, projPixel->pixel.f_pt); - ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*projPixel->mask*255), blend); - BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena); + if ((x >= ibuf->x) || (y >= ibuf->y)) { + if (torus) imapaint_ibuf_get_set_rgb(ibuf, x, y, 1, 0, inrgb); + else return 0; + } + else imapaint_ibuf_get_set_rgb(ibuf, x, y, 0, 0, inrgb); + + outrgb[0] += inrgb[0]; + outrgb[1] += inrgb[1]; + outrgb[2] += inrgb[2]; + + return 1; } -static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, short blend) +static void imapaint_lift_soften(ImBuf *ibuf, ImBuf *ibufb, int *pos, short torus) { - unsigned char rgba_ub[4]; - - if (ps->is_texbrush) { - rgba_ub[0] = FTOCHAR(rgba[0] * ps->brush->rgb[0]); - rgba_ub[1] = FTOCHAR(rgba[1] * ps->brush->rgb[1]); - rgba_ub[2] = FTOCHAR(rgba[2] * ps->brush->rgb[2]); - rgba_ub[3] = FTOCHAR(rgba[3]); - } - else { - IMAPAINT_FLOAT_RGB_TO_CHAR(rgba_ub, ps->brush->rgb); - rgba_ub[3] = 255; - } - - if (ps->is_airbrush==0 && projPixel->mask < 1.0f) { - projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, *((unsigned int *)rgba_ub), (int)(alpha*255), blend); - blend_color_mix(projPixel->pixel.ch_pt, projPixel->origColor.ch, projPixel->newColor.ch, (int)(projPixel->mask*255)); - } - else { - *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha*255), blend); - } -} + int x, y, count, xi, yi, xo, yo; + int out_off[2], in_off[2], dim[2]; + float outrgb[3]; -static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, short blend) { - if (ps->is_texbrush) { - rgba[0] *= ps->brush->rgb[0]; - rgba[1] *= ps->brush->rgb[1]; - rgba[2] *= ps->brush->rgb[2]; - } - else { - VECCOPY(rgba, ps->brush->rgb); - } - - if (ps->is_airbrush==0 && projPixel->mask < 1.0f) { - IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, rgba, alpha, blend); - blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, projPixel->mask); - } - else { - IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, rgba, alpha, blend); + dim[0] = ibufb->x; + dim[1] = ibufb->y; + in_off[0] = pos[0]; + in_off[1] = pos[1]; + out_off[0] = out_off[1] = 0; + + if (!torus) { + IMB_rectclip(ibuf, ibufb, &in_off[0], &in_off[1], &out_off[0], + &out_off[1], &dim[0], &dim[1]); + + if ((dim[0] == 0) || (dim[1] == 0)) + return; } -} + for (y=0; y < dim[1]; y++) { + for (x=0; x < dim[0]; x++) { + /* get input pixel */ + xi = in_off[0] + x; + yi = in_off[1] + y; + count = 1; + imapaint_ibuf_get_set_rgb(ibuf, xi, yi, torus, 0, outrgb); -/* run this for single and multithreaded painting */ -static void *do_projectpaint_thread(void *ph_v) -{ - /* First unpack args from the struct */ - ProjPaintState *ps = ((ProjectHandle *)ph_v)->ps; - ProjPaintImage *projImages = ((ProjectHandle *)ph_v)->projImages; - const float *lastpos = ((ProjectHandle *)ph_v)->prevmval; - const float *pos = ((ProjectHandle *)ph_v)->mval; - const int thread_index = ((ProjectHandle *)ph_v)->thread_index; - /* Done with args from ProjectHandle */ + count += imapaint_ibuf_add_if(ibuf, xi-1, yi-1, outrgb, torus); + count += imapaint_ibuf_add_if(ibuf, xi-1, yi , outrgb, torus); + count += imapaint_ibuf_add_if(ibuf, xi-1, yi+1, outrgb, torus); - LinkNode *node; - ProjPixel *projPixel; - - int last_index = -1; - ProjPaintImage *last_projIma; - ImagePaintPartialRedraw *last_partial_redraw_cell; - - float rgba[4], alpha, dist, dist_nosqrt; - - float brush_size_sqared; - int bucket_index; - int is_floatbuf = 0; - short blend= ps->blend; - const short tool = ps->tool; - rctf bucket_bounds; - - /* for smear only */ - float pos_ofs[2]; - float co[2]; - - LinkNode *smearPixels = NULL; - LinkNode *smearPixels_f = NULL; - MemArena *smearArena = NULL; /* mem arena for this brush projection only */ - - - if (tool==PAINT_TOOL_SMEAR) { - pos_ofs[0] = pos[0] - lastpos[0]; - pos_ofs[1] = pos[1] - lastpos[1]; - - smearArena = BLI_memarena_new(1<<16); - } - - /* avoid a square root with every dist comparison */ - brush_size_sqared = ps->brush->size * ps->brush->size; - - /* printf("brush bounds %d %d %d %d\n", bucketMin[0], bucketMin[1], bucketMax[0], bucketMax[1]); */ - - while (project_bucket_iter_next(ps, &bucket_index, &bucket_bounds, pos)) { - - /* Check this bucket and its faces are initialized */ - if (ps->bucketFlags[bucket_index] == PROJ_BUCKET_NULL) { - /* No pixels initialized */ - project_bucket_init(ps, thread_index, bucket_index, &bucket_bounds); - } + count += imapaint_ibuf_add_if(ibuf, xi , yi-1, outrgb, torus); + count += imapaint_ibuf_add_if(ibuf, xi , yi+1, outrgb, torus); - for (node = ps->bucketRect[bucket_index]; node; node = node->next) { - - projPixel = (ProjPixel *)node->link; - - /*dist = Vec2Lenf(projPixel->projCoSS, pos);*/ /* correct but uses a sqrt */ - dist_nosqrt = Vec2Lenf_nosqrt(projPixel->projCoSS, pos); - - /*if (dist < s->brush->size) {*/ /* correct but uses a sqrt */ - if (dist_nosqrt < brush_size_sqared) { - - if (last_index != projPixel->image_index) { - last_index = projPixel->image_index; - last_projIma = projImages + last_index; - - last_projIma->touch = 1; - is_floatbuf = last_projIma->ibuf->rect_float ? 1 : 0; - } - - last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index; - last_partial_redraw_cell->x1 = MIN2(last_partial_redraw_cell->x1, projPixel->x_px); - last_partial_redraw_cell->y1 = MIN2(last_partial_redraw_cell->y1, projPixel->y_px); - - last_partial_redraw_cell->x2 = MAX2(last_partial_redraw_cell->x2, projPixel->x_px+1); - last_partial_redraw_cell->y2 = MAX2(last_partial_redraw_cell->y2, projPixel->y_px+1); - - dist = (float)sqrt(dist_nosqrt); - - if (ps->is_airbrush) alpha = brush_sample_falloff(ps->brush, dist) * projPixel->mask; - else alpha = brush_sample_falloff_noalpha(ps->brush, dist); - - if (ps->is_texbrush) { - brush_sample_tex(ps->brush, projPixel->projCoSS, rgba); - alpha *= rgba[3]; - } - - if (alpha >= 0.0f) { - switch(tool) { - case PAINT_TOOL_CLONE: - if (is_floatbuf) { - if (((ProjPixelClone *)projPixel)->clonepx.f[3]) { - do_projectpaint_clone_f(ps, projPixel, rgba, alpha, blend); - } - } - else { - if (((ProjPixelClone*)projPixel)->clonepx.ch[3]) { - do_projectpaint_clone(ps, projPixel, rgba, alpha, blend); - } - } - break; - case PAINT_TOOL_SMEAR: - Vec2Subf(co, projPixel->projCoSS, pos_ofs); - - if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, rgba, alpha, blend, smearArena, &smearPixels_f, co); - else do_projectpaint_smear(ps, projPixel, rgba, alpha, blend, smearArena, &smearPixels, co); - break; - default: - if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, rgba, alpha, blend); - else do_projectpaint_draw(ps, projPixel, rgba, alpha, blend); - break; - } - } - /* done painting */ - } + count += imapaint_ibuf_add_if(ibuf, xi+1, yi-1, outrgb, torus); + count += imapaint_ibuf_add_if(ibuf, xi+1, yi , outrgb, torus); + count += imapaint_ibuf_add_if(ibuf, xi+1, yi+1, outrgb, torus); + + outrgb[0] /= count; + outrgb[1] /= count; + outrgb[2] /= count; + + /* write into brush buffer */ + xo = out_off[0] + x; + yo = out_off[1] + y; + imapaint_ibuf_get_set_rgb(ibufb, xo, yo, 0, 1, outrgb); } } +} + +static void imapaint_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos) +{ + IMB_rectblend_torus(ibufb, ibuf, 0, 0, pos[0], pos[1], + ibufb->x, ibufb->y, IMB_BLEND_COPY_RGB); +} + +static ImBuf *imapaint_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos) +{ + /* note: allocImbuf returns zero'd memory, so regions outside image will + have zero alpha, and hence not be blended onto the image */ + int w=ibufb->x, h=ibufb->y, destx=0, desty=0, srcx=pos[0], srcy=pos[1]; + ImBuf *clonebuf= IMB_allocImBuf(w, h, ibufb->depth, ibufb->flags, 0); + + IMB_rectclip(clonebuf, ibuf, &destx, &desty, &srcx, &srcy, &w, &h); + IMB_rectblend(clonebuf, ibuf, destx, desty, srcx, srcy, w, h, + IMB_BLEND_COPY_RGB); + IMB_rectblend(clonebuf, ibufb, destx, desty, destx, desty, w, h, + IMB_BLEND_COPY_ALPHA); + + return clonebuf; +} + +static void imapaint_convert_brushco(ImBuf *ibufb, float *pos, int *ipos) +{ + ipos[0]= (int)(pos[0] - ibufb->x/2); + ipos[1]= (int)(pos[1] - ibufb->y/2); +} + +/* dosnt run for projection painting + * only the old style painting in the 3d view */ +static int imapaint_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos) +{ + ImagePaintState *s= ((ImagePaintState*)state); + ImBuf *clonebuf= NULL; + short torus= s->brush->flag & BRUSH_TORUS; + short blend= s->blend; + float *offset= s->brush->clone.offset; + float liftpos[2]; + int bpos[2], blastpos[2], bliftpos[2]; + + imapaint_convert_brushco(ibufb, pos, bpos); + + /* lift from canvas */ + if(s->tool == PAINT_TOOL_SOFTEN) { + imapaint_lift_soften(s->canvas, ibufb, bpos, torus); + } + else if(s->tool == PAINT_TOOL_SMEAR) { + if (lastpos[0]==pos[0] && lastpos[1]==pos[1]) + return 0; - - if (tool==PAINT_TOOL_SMEAR) { - - for (node= smearPixels; node; node= node->next) { /* this wont run for a float image */ - projPixel = node->link; - *projPixel->pixel.uint_pt = ((ProjPixelClone *)projPixel)->clonepx.uint; - } - - for (node= smearPixels_f; node; node= node->next) { /* this wont run for a float image */ - projPixel = node->link; - IMAPAINT_CHAR_RGBA_TO_FLOAT(projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.ch); - node = node->next; - } - - BLI_memarena_free(smearArena); + imapaint_convert_brushco(ibufb, lastpos, blastpos); + imapaint_lift_smear(s->canvas, ibufb, blastpos); } - - return NULL; + else if(s->tool == PAINT_TOOL_CLONE && s->clonecanvas) { + liftpos[0]= pos[0] - offset[0]*s->canvas->x; + liftpos[1]= pos[1] - offset[1]*s->canvas->y; + + imapaint_convert_brushco(ibufb, liftpos, bliftpos); + clonebuf= imapaint_lift_clone(s->clonecanvas, ibufb, bliftpos); + } + + imapaint_dirty_region(s->image, s->canvas, bpos[0], bpos[1], ibufb->x, ibufb->y); + + /* blend into canvas */ + if(torus) + IMB_rectblend_torus(s->canvas, (clonebuf)? clonebuf: ibufb, + bpos[0], bpos[1], 0, 0, ibufb->x, ibufb->y, blend); + else + IMB_rectblend(s->canvas, (clonebuf)? clonebuf: ibufb, + bpos[0], bpos[1], 0, 0, ibufb->x, ibufb->y, blend); + + if(clonebuf) IMB_freeImBuf(clonebuf); + + return 1; } -static int project_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos) +/* 2D ImagePaint */ + +static void imapaint_compute_uvco(short *mval, float *uv) { - /* First unpack args from the struct */ - ProjPaintState *ps = (ProjPaintState *)state; - int touch_any = 0; - - ProjectHandle handles[BLENDER_MAX_THREADS]; - ListBase threads; - int a,i; + areamouseco_to_ipoco(G.v2d, mval, &uv[0], &uv[1]); +} + +/* 3D TexturePaint */ + +int facesel_face_pick(Mesh *me, short *mval, unsigned int *index, short rect); +void texpaint_pick_uv(Object *ob, Mesh *mesh, unsigned int faceindex, short *xy, float *mousepos); + +static int texpaint_break_stroke(float *prevuv, float *fwuv, float *bkuv, float *uv) +{ + float d1[2], d2[2]; + float mismatch = Vec2Lenf(fwuv, uv); + float len1 = Vec2Lenf(prevuv, fwuv); + float len2 = Vec2Lenf(bkuv, uv); + + Vec2Subf(d1, fwuv, prevuv); + Vec2Subf(d2, uv, bkuv); + + return ((Inp2f(d1, d2) < 0.0f) || (mismatch > MAX2(len1, len2)*2)); +} + +/* ImagePaint Common */ + +static int imapaint_canvas_set(ImagePaintState *s, Image *ima) +{ + ImBuf *ibuf= BKE_image_get_ibuf(ima, G.sima?&G.sima->iuser:NULL); - if (!project_bucket_iter_init(ps, pos)) { + /* verify that we can paint and set canvas */ + if(ima->packedfile && ima->rr) { + s->warnpackedfile = ima->id.name + 2; + return 0; + } + else if(ibuf && ibuf->channels!=4) { + s->warnmultifile = ima->id.name + 2; return 0; } - - if (ps->thread_tot > 1) - BLI_init_threads(&threads, do_projectpaint_thread, ps->thread_tot); - - /* get the threads running */ - for(a=0; a < ps->thread_tot; a++) { - - /* set defaults in handles */ - //memset(&handles[a], 0, sizeof(BakeShade)); - - handles[a].ps = ps; - VECCOPY2D(handles[a].mval, pos); - VECCOPY2D(handles[a].prevmval, lastpos); - - /* thread spesific */ - handles[a].thread_index = a; - - handles[a].projImages = (ProjPaintImage *)BLI_memarena_alloc(ps->arena_mt[a], ps->image_tot * sizeof(ProjPaintImage)); - - memcpy(handles[a].projImages, ps->projImages, ps->image_tot * sizeof(ProjPaintImage)); - - /* image bounds */ - for (i=0; i< ps->image_tot; i++) { - handles[a].projImages[i].partRedrawRect = (ImagePaintPartialRedraw *)BLI_memarena_alloc(ps->arena_mt[a], sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); - memcpy(handles[a].projImages[i].partRedrawRect, ps->projImages[i].partRedrawRect, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); - } + else if(!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) + return 0; - if (ps->thread_tot > 1) - BLI_insert_thread(&threads, &handles[a]); - } - - if (ps->thread_tot > 1) /* wait for everything to be done */ - BLI_end_threads(&threads); - else - do_projectpaint_thread(&handles[0]); - - - /* move threaded bounds back into ps->projectPartialRedraws */ - for(i=0; i < ps->image_tot; i++) { - int touch = 0; - for(a=0; a < ps->thread_tot; a++) { - touch |= partial_redraw_array_merge(ps->projImages[i].partRedrawRect, handles[a].projImages[i].partRedrawRect, PROJ_BOUNDBOX_SQUARED); - } + s->image= ima; + s->canvas= ibuf; + + /* set clone canvas */ + if(s->tool == PAINT_TOOL_CLONE) { + ima= s->brush->clone.image; + ibuf= BKE_image_get_ibuf(ima, G.sima?&G.sima->iuser:NULL); - if (touch) { - ps->projImages[i].touch = 1; - touch_any = 1; + if(!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) + return 0; + + s->clonecanvas= ibuf; + + if(s->canvas->rect_float && !s->clonecanvas->rect_float) { + /* temporarily add float rect for cloning */ + IMB_float_from_rect(s->clonecanvas); + s->clonefreefloat= 1; } + else if(!s->canvas->rect_float && !s->clonecanvas->rect) + IMB_rect_from_float(s->clonecanvas); } - - return touch_any; + + return 1; +} + +static void imapaint_canvas_free(ImagePaintState *s) +{ + if (s->clonefreefloat) + imb_freerectfloatImBuf(s->clonecanvas); } +static int imapaint_paint_sub_stroke(ImagePaintState *s, BrushPainter *painter, Image *image, short texpaint, float *uv, double time, int update, float pressure) +{ + ImBuf *ibuf= BKE_image_get_ibuf(image, G.sima?&G.sima->iuser:NULL); + float pos[2]; + + if(!ibuf) + return 0; + + pos[0] = uv[0]*ibuf->x; + pos[1] = uv[1]*ibuf->y; + + brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0); + + if (brush_painter_paint(painter, imapaint_paint_op, pos, time, pressure, s)) { + if (update) + imapaint_image_update(image, ibuf, texpaint); + return 1; + } + else return 0; +} +/* this is only useful for debugging at the moment */ static void imapaint_paint_stroke(ImagePaintState *s, BrushPainter *painter, short texpaint, short *prevmval, short *mval, double time, float pressure) { Image *newimage = NULL; @@ -4319,49 +4399,6 @@ static void imapaint_paint_stroke(ImagePaintState *s, BrushPainter *painter, sho } -static int project_paint_sub_stroke(ProjPaintState *ps, BrushPainter *painter, short *prevmval_i, short *mval_i, double time, float pressure) -{ - - /* Use mouse coords as floats for projection painting */ - float pos[2]; - - pos[0] = mval_i[0]; - pos[1] = mval_i[1]; - - // we may want to use this later - // brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0); - - if (brush_painter_paint(painter, project_paint_op, pos, time, pressure, ps)) { - return 1; - } - else return 0; -} - - -static int project_paint_stroke(ProjPaintState *ps, BrushPainter *painter, short *prevmval_i, short *mval_i, double time, int update, float pressure) -{ - int a, redraw = 0; - - for (a=0; a < ps->image_tot; a++) { - partial_redraw_array_init(ps->projImages[a].partRedrawRect); - } - - redraw |= project_paint_sub_stroke(ps, painter, prevmval_i, mval_i, time, pressure); - - if (update) { - if (imapaint_refresh_tagged(ps)) { - if (redraw) { - force_draw(0); /* imapaint_redraw just calls this in viewport paint anyway */ - /* imapaint_redraw(0, 1, NULL); */ - /* imapaint_clear_partial_redraw(); */ /* not needed since we use our own array */ - } - } - } - - return redraw; -} - -/* this is only useful for debugging at the moment */ static int imapaint_paint_gp_to_stroke(float **points_gp) { bGPdata *gpd; bGPDlayer *gpl; @@ -4561,6 +4598,8 @@ void imagepaint_paint(short mousebutton, short texpaint) prevmval[1]= (short)vec_gp[1]; } } else { + /* special exception here for too high pressure values on first touch in + windows for some tablets */ if (!((s.brush->flag & (BRUSH_ALPHA_PRESSURE|BRUSH_SIZE_PRESSURE| BRUSH_SPACING_PRESSURE|BRUSH_RAD_PRESSURE)) && (get_activedevice() != 0) && (pressure >= 0.99f))) imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure); @@ -4586,6 +4625,7 @@ void imagepaint_paint(short mousebutton, short texpaint) if (project) { /* Projection Painting */ int redraw = 1; + if (((s.brush->flag & BRUSH_AIRBRUSH) || init) || ((mval[0] != prevmval[0]) || (mval[1] != prevmval[1]))) { redraw = project_paint_stroke(&ps, painter, prevmval, mval, time, stroke_gp ? 0 : 1, pressure); prevmval[0]= mval[0]; @@ -4605,7 +4645,7 @@ void imagepaint_paint(short mousebutton, short texpaint) init = 0; } - else { + else { if((mval[0] != prevmval[0]) || (mval[1] != prevmval[1])) { imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure); prevmval[0]= mval[0]; -- cgit v1.2.3