diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint/paint_image.cc')
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image.cc | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc new file mode 100644 index 00000000000..0c73c2e1f43 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -0,0 +1,1012 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edsculpt + * \brief Functions to paint images in 2D and 3D. + */ + +#include <cfloat> +#include <cmath> +#include <cstdio> +#include <cstring> + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "DNA_brush_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "BKE_brush.h" +#include "BKE_colorband.h" +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_node.h" +#include "BKE_paint.h" +#include "BKE_undo_system.h" + +#include "NOD_texture.h" + +#include "DEG_depsgraph.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "ED_image.h" +#include "ED_object.h" +#include "ED_paint.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "GPU_immediate.h" +#include "GPU_state.h" + +#include "IMB_colormanagement.h" + +#include "paint_intern.h" + +extern "C" { + +/** + * This is a static resource for non-global access. + * Maybe it should be exposed as part of the paint operation, + * but for now just give a public interface. + */ +static ImagePaintPartialRedraw imapaintpartial = {{0}}; + +ImagePaintPartialRedraw *get_imapaintpartial(void) +{ + return &imapaintpartial; +} + +void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr) +{ + imapaintpartial = *ippr; +} + +/* Image paint Partial Redraw & Dirty Region. */ + +void ED_imapaint_clear_partial_redraw(void) +{ + BLI_rcti_init_minmax(&imapaintpartial.dirty_region); +} + +void imapaint_region_tiles( + ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th) +{ + int srcx = 0, srcy = 0; + + IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); + + *tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS); + *th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS); + *tx = (x >> ED_IMAGE_UNDO_TILE_BITS); + *ty = (y >> ED_IMAGE_UNDO_TILE_BITS); +} + +void ED_imapaint_dirty_region( + Image *ima, ImBuf *ibuf, ImageUser *iuser, int x, int y, int w, int h, bool find_old) +{ + ImBuf *tmpibuf = nullptr; + int tilex, tiley, tilew, tileh, tx, ty; + int srcx = 0, srcy = 0; + + IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); + + if (w == 0 || h == 0) { + return; + } + + rcti rect_to_merge; + BLI_rcti_init(&rect_to_merge, x, x + w, y, y + h); + BLI_rcti_do_minmax_rcti(&imapaintpartial.dirty_region, &rect_to_merge); + + imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); + + ListBase *undo_tiles = ED_image_paint_tile_list_get(); + + for (ty = tiley; ty <= tileh; ty++) { + for (tx = tilex; tx <= tilew; tx++) { + ED_image_paint_tile_push( + undo_tiles, ima, ibuf, &tmpibuf, iuser, tx, ty, nullptr, nullptr, false, find_old); + } + } + + BKE_image_mark_dirty(ima, ibuf); + + if (tmpibuf) { + IMB_freeImBuf(tmpibuf); + } +} + +void imapaint_image_update( + SpaceImage *sima, Image *image, ImBuf *ibuf, ImageUser *iuser, short texpaint) +{ + if (BLI_rcti_is_empty(&imapaintpartial.dirty_region)) { + return; + } + + if (ibuf->mipmap[0]) { + ibuf->userflags |= IB_MIPMAP_INVALID; + } + + IMB_partial_display_buffer_update_delayed(ibuf, + imapaintpartial.dirty_region.xmin, + imapaintpartial.dirty_region.ymin, + imapaintpartial.dirty_region.xmax, + imapaintpartial.dirty_region.ymax); + + /* TODO: should set_tpage create ->rect? */ + if (texpaint || (sima && sima->lock)) { + const int w = BLI_rcti_size_x(&imapaintpartial.dirty_region); + const int h = BLI_rcti_size_y(&imapaintpartial.dirty_region); + /* Testing with partial update in uv editor too */ + BKE_image_update_gputexture( + image, iuser, imapaintpartial.dirty_region.xmin, imapaintpartial.dirty_region.ymin, w, h); + } +} + +BlurKernel *paint_new_blur_kernel(Brush *br, bool proj) +{ + int i, j; + BlurKernel *kernel = MEM_new<BlurKernel>("BlurKernel"); + + float radius; + int side; + eBlurKernelType type = static_cast<eBlurKernelType>(br->blur_mode); + + if (proj) { + radius = 0.5f; + + side = kernel->side = 2; + kernel->side_squared = kernel->side * kernel->side; + kernel->wdata = static_cast<float *>( + MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data")); + kernel->pixel_len = radius; + } + else { + if (br->blur_kernel_radius <= 0) { + br->blur_kernel_radius = 1; + } + + radius = br->blur_kernel_radius; + + side = kernel->side = radius * 2 + 1; + kernel->side_squared = kernel->side * kernel->side; + kernel->wdata = static_cast<float *>( + MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data")); + kernel->pixel_len = br->blur_kernel_radius; + } + + switch (type) { + case KERNEL_BOX: + for (i = 0; i < kernel->side_squared; i++) { + kernel->wdata[i] = 1.0; + } + break; + + case KERNEL_GAUSSIAN: { + /* at 3.0 standard deviations distance, kernel is about zero */ + float standard_dev = radius / 3.0f; + + /* make the necessary adjustment to the value for use in the normal distribution formula */ + standard_dev = -standard_dev * standard_dev * 2; + + for (i = 0; i < side; i++) { + for (j = 0; j < side; j++) { + float idist = radius - i; + float jdist = radius - j; + float value = exp((idist * idist + jdist * jdist) / standard_dev); + + kernel->wdata[i + j * side] = value; + } + } + + break; + } + + default: + printf("unidentified kernel type, aborting\n"); + paint_delete_blur_kernel(kernel); + MEM_delete(kernel); + return nullptr; + } + + return kernel; +} + +void paint_delete_blur_kernel(BlurKernel *kernel) +{ + if (kernel->wdata) { + MEM_freeN(kernel->wdata); + } +} + +/************************ image paint poll ************************/ + +static Brush *image_paint_brush(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + + return BKE_paint_brush(&settings->imapaint.paint); +} + +static bool image_paint_poll_ex(bContext *C, bool check_tool) +{ + Object *obact; + + if (!image_paint_brush(C)) { + return false; + } + + obact = CTX_data_active_object(C); + if ((obact && obact->mode & OB_MODE_TEXTURE_PAINT) && CTX_wm_region_view3d(C)) { + if (!check_tool || WM_toolsystem_active_tool_is_brush(C)) { + return true; + } + } + else { + SpaceImage *sima = CTX_wm_space_image(C); + + if (sima) { + if (sima->image != nullptr && ID_IS_LINKED(sima->image)) { + return false; + } + ARegion *region = CTX_wm_region(C); + + if ((sima->mode == SI_MODE_PAINT) && region->regiontype == RGN_TYPE_WINDOW) { + return true; + } + } + } + + return false; +} + +bool image_paint_poll(bContext *C) +{ + return image_paint_poll_ex(C, true); +} + +static bool image_paint_poll_ignore_tool(bContext *C) +{ + return image_paint_poll_ex(C, false); +} + +static bool image_paint_2d_clone_poll(bContext *C) +{ + Brush *brush = image_paint_brush(C); + + if (!CTX_wm_region_view3d(C) && image_paint_poll(C)) { + if (brush && (brush->imagepaint_tool == PAINT_TOOL_CLONE)) { + if (brush->clone.image) { + return true; + } + } + } + + return false; +} + +/************************ paint operator ************************/ +bool paint_use_opacity_masking(Brush *brush) +{ + return ((brush->flag & BRUSH_AIRBRUSH) || (brush->flag & BRUSH_DRAG_DOT) || + (brush->flag & BRUSH_ANCHORED) || + (ELEM(brush->imagepaint_tool, PAINT_TOOL_SMEAR, PAINT_TOOL_SOFTEN)) || + (brush->imagepaint_tool == PAINT_TOOL_FILL) || + (brush->flag & BRUSH_USE_GRADIENT) || + (brush->mtex.tex && !ELEM(brush->mtex.brush_map_mode, + MTEX_MAP_MODE_TILED, + MTEX_MAP_MODE_STENCIL, + MTEX_MAP_MODE_3D)) ? + false : + true); +} + +void paint_brush_color_get(struct Scene *scene, + struct Brush *br, + bool color_correction, + bool invert, + float distance, + float pressure, + float color[3], + struct ColorManagedDisplay *display) +{ + if (invert) { + copy_v3_v3(color, BKE_brush_secondary_color_get(scene, br)); + } + else { + if (br->flag & BRUSH_USE_GRADIENT) { + float color_gr[4]; + switch (br->gradient_stroke_mode) { + case BRUSH_GRADIENT_PRESSURE: + BKE_colorband_evaluate(br->gradient, pressure, color_gr); + break; + case BRUSH_GRADIENT_SPACING_REPEAT: { + float coord = fmod(distance / br->gradient_spacing, 1.0); + BKE_colorband_evaluate(br->gradient, coord, color_gr); + break; + } + case BRUSH_GRADIENT_SPACING_CLAMP: { + BKE_colorband_evaluate(br->gradient, distance / br->gradient_spacing, color_gr); + break; + } + } + /* Gradient / Color-band colors are not considered #PROP_COLOR_GAMMA. + * Brush colors are expected to be in sRGB though. */ + IMB_colormanagement_scene_linear_to_srgb_v3(color_gr); + + copy_v3_v3(color, color_gr); + } + else { + copy_v3_v3(color, BKE_brush_color_get(scene, br)); + } + } + if (color_correction) { + IMB_colormanagement_display_to_scene_linear_v3(color, display); + } +} + +void paint_brush_init_tex(Brush *brush) +{ + /* init mtex nodes */ + if (brush) { + MTex *mtex = &brush->mtex; + if (mtex->tex && mtex->tex->nodetree) { + /* has internal flag to detect it only does it once */ + ntreeTexBeginExecTree(mtex->tex->nodetree); + } + mtex = &brush->mask_mtex; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexBeginExecTree(mtex->tex->nodetree); + } + } +} + +void paint_brush_exit_tex(Brush *brush) +{ + if (brush) { + MTex *mtex = &brush->mtex; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } + mtex = &brush->mask_mtex; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } + } +} + +bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy) +{ + ScrArea *area = CTX_wm_area(C); + if (area && area->spacetype == SPACE_IMAGE) { + SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first); + if (sima->mode == SI_MODE_PAINT) { + ARegion *region = CTX_wm_region(C); + ED_space_image_get_zoom(sima, region, zoomx, zoomy); + return true; + } + } + + *zoomx = *zoomy = 1; + + return false; +} + +/************************ cursor drawing *******************************/ + +static void toggle_paint_cursor(Scene *scene, bool enable) +{ + ToolSettings *settings = scene->toolsettings; + Paint *p = &settings->imapaint.paint; + + if (p->paint_cursor && !enable) { + WM_paint_cursor_end(static_cast<wmPaintCursor *>(p->paint_cursor)); + p->paint_cursor = nullptr; + paint_cursor_delete_textures(); + } + else if (enable) { + paint_cursor_start(p, image_paint_poll); + } +} + +void ED_space_image_paint_update(Main *bmain, wmWindowManager *wm, Scene *scene) +{ + ToolSettings *settings = scene->toolsettings; + ImagePaintSettings *imapaint = &settings->imapaint; + bool enabled = false; + + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + bScreen *screen = WM_window_get_active_screen(win); + + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + if (area->spacetype == SPACE_IMAGE) { + if (((SpaceImage *)area->spacedata.first)->mode == SI_MODE_PAINT) { + enabled = true; + } + } + } + } + + if (enabled) { + BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_2D, PAINT_CURSOR_TEXTURE_PAINT); + + paint_cursor_start(&imapaint->paint, image_paint_poll); + } + else { + paint_cursor_delete_textures(); + } +} + +/************************ grab clone operator ************************/ + +struct GrabClone { + float startoffset[2]; + int startx, starty; +}; + +static void grab_clone_apply(bContext *C, wmOperator *op) +{ + Brush *brush = image_paint_brush(C); + float delta[2]; + + RNA_float_get_array(op->ptr, "delta", delta); + add_v2_v2(brush->clone.offset, delta); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static int grab_clone_exec(bContext *C, wmOperator *op) +{ + grab_clone_apply(C, op); + + return OPERATOR_FINISHED; +} + +static int grab_clone_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Brush *brush = image_paint_brush(C); + GrabClone *cmv; + + cmv = MEM_new<GrabClone>("GrabClone"); + copy_v2_v2(cmv->startoffset, brush->clone.offset); + cmv->startx = event->xy[0]; + cmv->starty = event->xy[1]; + op->customdata = cmv; + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int grab_clone_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Brush *brush = image_paint_brush(C); + ARegion *region = CTX_wm_region(C); + GrabClone *cmv = static_cast<GrabClone *>(op->customdata); + float startfx, startfy, fx, fy, delta[2]; + int xmin = region->winrct.xmin, ymin = region->winrct.ymin; + + switch (event->type) { + case LEFTMOUSE: + case MIDDLEMOUSE: + case RIGHTMOUSE: /* XXX hardcoded */ + MEM_freeN(op->customdata); + return OPERATOR_FINISHED; + case MOUSEMOVE: + /* mouse moved, so move the clone image */ + UI_view2d_region_to_view( + ®ion->v2d, cmv->startx - xmin, cmv->starty - ymin, &startfx, &startfy); + UI_view2d_region_to_view(®ion->v2d, event->xy[0] - xmin, event->xy[1] - ymin, &fx, &fy); + + delta[0] = fx - startfx; + delta[1] = fy - startfy; + RNA_float_set_array(op->ptr, "delta", delta); + + copy_v2_v2(brush->clone.offset, cmv->startoffset); + + grab_clone_apply(C, op); + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +static void grab_clone_cancel(bContext *UNUSED(C), wmOperator *op) +{ + GrabClone *cmv = static_cast<GrabClone *>(op->customdata); + MEM_delete(cmv); +} + +void PAINT_OT_grab_clone(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Grab Clone"; + ot->idname = "PAINT_OT_grab_clone"; + ot->description = "Move the clone source image"; + + /* api callbacks */ + ot->exec = grab_clone_exec; + ot->invoke = grab_clone_invoke; + ot->modal = grab_clone_modal; + ot->cancel = grab_clone_cancel; + ot->poll = image_paint_2d_clone_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + RNA_def_float_vector(ot->srna, + "delta", + 2, + nullptr, + -FLT_MAX, + FLT_MAX, + "Delta", + "Delta offset of clone image in 0.0 to 1.0 coordinates", + -1.0f, + 1.0f); +} + +/******************** sample color operator ********************/ +struct SampleColorData { + bool show_cursor; + short launch_event; + float initcolor[3]; + bool sample_palette; +}; + +static void sample_color_update_header(SampleColorData *data, bContext *C) +{ + char msg[UI_MAX_DRAW_STR]; + ScrArea *area = CTX_wm_area(C); + + if (area) { + BLI_snprintf(msg, + sizeof(msg), + TIP_("Sample color for %s"), + !data->sample_palette ? + TIP_("Brush. Use Left Click to sample for palette instead") : + TIP_("Palette. Use Left Click to sample more colors")); + ED_workspace_status_text(C, msg); + } +} + +static int sample_color_exec(bContext *C, wmOperator *op) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + ePaintMode mode = BKE_paintmode_get_active_from_context(C); + ARegion *region = CTX_wm_region(C); + wmWindow *win = CTX_wm_window(C); + const bool show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); + int location[2]; + paint->flags &= ~PAINT_SHOW_BRUSH; + + /* force redraw without cursor */ + WM_paint_cursor_tag_redraw(win, region); + WM_redraw_windows(C); + + RNA_int_get_array(op->ptr, "location", location); + const bool use_palette = RNA_boolean_get(op->ptr, "palette"); + const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && + !RNA_boolean_get(op->ptr, "merged"); + + paint_sample_color(C, region, location[0], location[1], use_sample_texture, use_palette); + + if (show_cursor) { + paint->flags |= PAINT_SHOW_BRUSH; + } + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + + return OPERATOR_FINISHED; +} + +static int sample_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + SampleColorData *data = MEM_new<SampleColorData>("sample color custom data"); + ARegion *region = CTX_wm_region(C); + wmWindow *win = CTX_wm_window(C); + + data->launch_event = WM_userdef_event_type_from_keymap_type(event->type); + data->show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); + copy_v3_v3(data->initcolor, BKE_brush_color_get(scene, brush)); + data->sample_palette = false; + op->customdata = data; + paint->flags &= ~PAINT_SHOW_BRUSH; + + sample_color_update_header(data, C); + + WM_event_add_modal_handler(C, op); + + /* force redraw without cursor */ + WM_paint_cursor_tag_redraw(win, region); + WM_redraw_windows(C); + + RNA_int_set_array(op->ptr, "location", event->mval); + + ePaintMode mode = BKE_paintmode_get_active_from_context(C); + const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && + !RNA_boolean_get(op->ptr, "merged"); + + paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + + return OPERATOR_RUNNING_MODAL; +} + +static int sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + SampleColorData *data = static_cast<SampleColorData *>(op->customdata); + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + + if ((event->type == data->launch_event) && (event->val == KM_RELEASE)) { + if (data->show_cursor) { + paint->flags |= PAINT_SHOW_BRUSH; + } + + if (data->sample_palette) { + BKE_brush_color_set(scene, brush, data->initcolor); + RNA_boolean_set(op->ptr, "palette", true); + } + WM_cursor_modal_restore(CTX_wm_window(C)); + MEM_delete(data); + ED_workspace_status_text(C, nullptr); + + return OPERATOR_FINISHED; + } + + ePaintMode mode = BKE_paintmode_get_active_from_context(C); + const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && + !RNA_boolean_get(op->ptr, "merged"); + + switch (event->type) { + case MOUSEMOVE: { + ARegion *region = CTX_wm_region(C); + RNA_int_set_array(op->ptr, "location", event->mval); + paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + break; + } + + case LEFTMOUSE: + if (event->val == KM_PRESS) { + ARegion *region = CTX_wm_region(C); + RNA_int_set_array(op->ptr, "location", event->mval); + paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, true); + if (!data->sample_palette) { + data->sample_palette = true; + sample_color_update_header(data, C); + } + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +static bool sample_color_poll(bContext *C) +{ + return (image_paint_poll_ignore_tool(C) || vertex_paint_poll_ignore_tool(C)); +} + +void PAINT_OT_sample_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Sample Color"; + ot->idname = "PAINT_OT_sample_color"; + ot->description = "Use the mouse to sample a color in the image"; + + /* api callbacks */ + ot->exec = sample_color_exec; + ot->invoke = sample_color_invoke; + ot->modal = sample_color_modal; + ot->poll = sample_color_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + PropertyRNA *prop; + + prop = RNA_def_int_vector( + ot->srna, "location", 2, nullptr, 0, INT_MAX, "Location", "", 0, 16384); + RNA_def_property_flag(prop, static_cast<PropertyFlag>(PROP_SKIP_SAVE | PROP_HIDDEN)); + + RNA_def_boolean(ot->srna, "merged", false, "Sample Merged", "Sample the output display color"); + RNA_def_boolean(ot->srna, "palette", false, "Add to Palette", ""); +} + +/******************** texture paint toggle operator ********************/ + +void ED_object_texture_paint_mode_enter_ex(Main *bmain, Scene *scene, Object *ob) +{ + Image *ima = nullptr; + ImagePaintSettings *imapaint = &scene->toolsettings->imapaint; + + /* This has to stay here to regenerate the texture paint + * cache in case we are loading a file */ + BKE_texpaint_slots_refresh_object(scene, ob); + + ED_paint_proj_mesh_data_check(scene, ob, nullptr, nullptr, nullptr, nullptr); + + /* entering paint mode also sets image to editors */ + if (imapaint->mode == IMAGEPAINT_MODE_MATERIAL) { + /* set the current material active paint slot on image editor */ + Material *ma = BKE_object_material_get(ob, ob->actcol); + + if (ma && ma->texpaintslot) { + ima = ma->texpaintslot[ma->paint_active_slot].ima; + } + } + else if (imapaint->mode == IMAGEPAINT_MODE_IMAGE) { + ima = imapaint->canvas; + } + + if (ima) { + wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first); + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + const bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first); + if (sl->spacetype == SPACE_IMAGE) { + SpaceImage *sima = (SpaceImage *)sl; + + if (!sima->pin) { + ED_space_image_set(bmain, sima, ima, true); + } + } + } + } + } + + ob->mode |= OB_MODE_TEXTURE_PAINT; + + BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_3D, PAINT_CURSOR_TEXTURE_PAINT); + + BKE_paint_toolslots_brush_validate(bmain, &imapaint->paint); + + if (U.glreslimit != 0) { + BKE_image_free_all_gputextures(bmain); + } + BKE_image_paint_set_mipmap(bmain, false); + + toggle_paint_cursor(scene, true); + + Mesh *me = BKE_mesh_from_object(ob); + BLI_assert(me != nullptr); + DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); + WM_main_add_notifier(NC_SCENE | ND_MODE, scene); +} + +void ED_object_texture_paint_mode_enter(bContext *C) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); +} + +void ED_object_texture_paint_mode_exit_ex(Main *bmain, Scene *scene, Object *ob) +{ + ob->mode &= ~OB_MODE_TEXTURE_PAINT; + + if (U.glreslimit != 0) { + BKE_image_free_all_gputextures(bmain); + } + BKE_image_paint_set_mipmap(bmain, true); + toggle_paint_cursor(scene, false); + + Mesh *me = BKE_mesh_from_object(ob); + BLI_assert(me != nullptr); + DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); + WM_main_add_notifier(NC_SCENE | ND_MODE, scene); +} + +void ED_object_texture_paint_mode_exit(bContext *C) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); +} + +static bool texture_paint_toggle_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == nullptr || ob->type != OB_MESH) { + return false; + } + if (!ob->data || ID_IS_LINKED(ob->data)) { + return false; + } + + return true; +} + +static int texture_paint_toggle_exec(bContext *C, wmOperator *op) +{ + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + const int mode_flag = OB_MODE_TEXTURE_PAINT; + const bool is_mode_set = (ob->mode & mode_flag) != 0; + + if (!is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, static_cast<eObjectMode>(mode_flag), op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (ob->mode & mode_flag) { + ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); + } + else { + ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); + } + + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + + WM_toolsystem_update_from_context_view3d(C); + + return OPERATOR_FINISHED; +} + +void PAINT_OT_texture_paint_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Texture Paint Toggle"; + ot->idname = "PAINT_OT_texture_paint_toggle"; + ot->description = "Toggle texture paint mode in 3D view"; + + /* api callbacks */ + ot->exec = texture_paint_toggle_exec; + ot->poll = texture_paint_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int brush_colors_flip_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *br = BKE_paint_brush(paint); + + if (ups->flag & UNIFIED_PAINT_COLOR) { + swap_v3_v3(ups->rgb, ups->secondary_rgb); + } + else if (br) { + swap_v3_v3(br->rgb, br->secondary_rgb); + } + else { + return OPERATOR_CANCELLED; + } + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br); + + return OPERATOR_FINISHED; +} + +static bool brush_colors_flip_poll(bContext *C) +{ + if (image_paint_poll(C)) { + Brush *br = image_paint_brush(C); + if (ELEM(br->imagepaint_tool, PAINT_TOOL_DRAW, PAINT_TOOL_FILL)) { + return true; + } + } + else { + Object *ob = CTX_data_active_object(C); + if (ob != nullptr) { + if (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_TEXTURE_PAINT | OB_MODE_SCULPT)) { + return true; + } + } + } + return false; +} + +void PAINT_OT_brush_colors_flip(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Swap Colors"; + ot->idname = "PAINT_OT_brush_colors_flip"; + ot->description = "Swap primary and secondary brush colors"; + + /* api callbacks */ + ot->exec = brush_colors_flip_exec; + ot->poll = brush_colors_flip_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +void ED_imapaint_bucket_fill(struct bContext *C, + float color[3], + wmOperator *op, + const int mouse[2]) +{ + SpaceImage *sima = CTX_wm_space_image(C); + + if (sima && sima->image) { + Image *ima = sima->image; + + ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); + + const float mouse_init[2] = {static_cast<float>(mouse[0]), static_cast<float>(mouse[1])}; + paint_2d_bucket_fill(C, color, nullptr, mouse_init, nullptr, nullptr); + + ED_image_undo_push_end(); + + DEG_id_tag_update(&ima->id, 0); + } +} + +static bool texture_paint_poll(bContext *C) +{ + if (texture_paint_toggle_poll(C)) { + if (CTX_data_active_object(C)->mode & OB_MODE_TEXTURE_PAINT) { + return true; + } + } + + return false; +} + +bool image_texture_paint_poll(bContext *C) +{ + return (texture_paint_poll(C) || image_paint_poll(C)); +} + +bool facemask_paint_poll(bContext *C) +{ + return BKE_paint_select_face_test(CTX_data_active_object(C)); +} + +bool vert_paint_poll(bContext *C) +{ + return BKE_paint_select_vert_test(CTX_data_active_object(C)); +} + +bool mask_paint_poll(bContext *C) +{ + return BKE_paint_select_elem_test(CTX_data_active_object(C)); +} +} |