475 lines
14 KiB
C++
475 lines
14 KiB
C++
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edsculpt
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_math_color.h"
|
|
#include "BLI_math_color_blend.h"
|
|
#include "BLI_math_vector.hh"
|
|
#include "BLI_task.h"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_layer.hh"
|
|
#include "BKE_paint.hh"
|
|
#include "BKE_pbvh_api.hh"
|
|
|
|
#include "IMB_colormanagement.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "ED_paint.hh"
|
|
#include "sculpt_intern.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
|
|
namespace blender::ed::sculpt_paint::color {
|
|
|
|
enum eSculptColorFilterTypes {
|
|
COLOR_FILTER_FILL,
|
|
COLOR_FILTER_HUE,
|
|
COLOR_FILTER_SATURATION,
|
|
COLOR_FILTER_VALUE,
|
|
COLOR_FILTER_BRIGHTNESS,
|
|
COLOR_FILTER_CONTRAST,
|
|
COLOR_FILTER_RED,
|
|
COLOR_FILTER_GREEN,
|
|
COLOR_FILTER_BLUE,
|
|
COLOR_FILTER_SMOOTH,
|
|
};
|
|
|
|
static const float fill_filter_default_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
|
|
static EnumPropertyItem prop_color_filter_types[] = {
|
|
{COLOR_FILTER_FILL, "FILL", 0, "Fill", "Fill with a specific color"},
|
|
{COLOR_FILTER_HUE, "HUE", 0, "Hue", "Change hue"},
|
|
{COLOR_FILTER_SATURATION, "SATURATION", 0, "Saturation", "Change saturation"},
|
|
{COLOR_FILTER_VALUE, "VALUE", 0, "Value", "Change value"},
|
|
|
|
{COLOR_FILTER_BRIGHTNESS, "BRIGHTNESS", 0, "Brightness", "Change brightness"},
|
|
{COLOR_FILTER_CONTRAST, "CONTRAST", 0, "Contrast", "Change contrast"},
|
|
|
|
{COLOR_FILTER_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth colors"},
|
|
|
|
{COLOR_FILTER_RED, "RED", 0, "Red", "Change red channel"},
|
|
{COLOR_FILTER_GREEN, "GREEN", 0, "Green", "Change green channel"},
|
|
{COLOR_FILTER_BLUE, "BLUE", 0, "Blue", "Change blue channel"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static void color_filter_task(Object *ob,
|
|
const int mode,
|
|
const float filter_strength,
|
|
const float *filter_fill_color,
|
|
PBVHNode *node)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
SculptOrigVertData orig_data;
|
|
SCULPT_orig_vert_data_init(&orig_data, ob, node, undo::Type::Color);
|
|
|
|
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
|
*ob, ss->filter_cache->automasking.get(), *node);
|
|
|
|
PBVHVertexIter vd;
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
|
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
|
auto_mask::node_update(automask_data, vd);
|
|
|
|
float3 orig_color;
|
|
float4 final_color;
|
|
float3 hsv_color;
|
|
int hue;
|
|
float brightness, contrast, gain, delta, offset;
|
|
float fade = vd.mask;
|
|
fade = 1.0f - fade;
|
|
fade *= filter_strength;
|
|
fade *= auto_mask::factor_get(
|
|
ss->filter_cache->automasking.get(), ss, vd.vertex, &automask_data);
|
|
if (fade == 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
copy_v3_v3(orig_color, orig_data.col);
|
|
final_color[3] = orig_data.col[3]; /* Copy alpha */
|
|
|
|
switch (mode) {
|
|
case COLOR_FILTER_FILL: {
|
|
float fill_color_rgba[4];
|
|
copy_v3_v3(fill_color_rgba, filter_fill_color);
|
|
fill_color_rgba[3] = 1.0f;
|
|
fade = clamp_f(fade, 0.0f, 1.0f);
|
|
mul_v4_fl(fill_color_rgba, fade);
|
|
blend_color_mix_float(final_color, orig_data.col, fill_color_rgba);
|
|
break;
|
|
}
|
|
case COLOR_FILTER_HUE:
|
|
rgb_to_hsv_v(orig_color, hsv_color);
|
|
hue = hsv_color[0];
|
|
hsv_color[0] = fmod((hsv_color[0] + fabs(fade)) - hue, 1);
|
|
hsv_to_rgb_v(hsv_color, final_color);
|
|
break;
|
|
case COLOR_FILTER_SATURATION:
|
|
rgb_to_hsv_v(orig_color, hsv_color);
|
|
|
|
if (hsv_color[1] > 0.001f) {
|
|
hsv_color[1] = clamp_f(hsv_color[1] + fade * hsv_color[1], 0.0f, 1.0f);
|
|
hsv_to_rgb_v(hsv_color, final_color);
|
|
}
|
|
else {
|
|
copy_v3_v3(final_color, orig_color);
|
|
}
|
|
break;
|
|
case COLOR_FILTER_VALUE:
|
|
rgb_to_hsv_v(orig_color, hsv_color);
|
|
hsv_color[2] = clamp_f(hsv_color[2] + fade, 0.0f, 1.0f);
|
|
hsv_to_rgb_v(hsv_color, final_color);
|
|
break;
|
|
case COLOR_FILTER_RED:
|
|
orig_color[0] = clamp_f(orig_color[0] + fade, 0.0f, 1.0f);
|
|
copy_v3_v3(final_color, orig_color);
|
|
break;
|
|
case COLOR_FILTER_GREEN:
|
|
orig_color[1] = clamp_f(orig_color[1] + fade, 0.0f, 1.0f);
|
|
copy_v3_v3(final_color, orig_color);
|
|
break;
|
|
case COLOR_FILTER_BLUE:
|
|
orig_color[2] = clamp_f(orig_color[2] + fade, 0.0f, 1.0f);
|
|
copy_v3_v3(final_color, orig_color);
|
|
break;
|
|
case COLOR_FILTER_BRIGHTNESS:
|
|
fade = clamp_f(fade, -1.0f, 1.0f);
|
|
brightness = fade;
|
|
contrast = 0;
|
|
delta = contrast / 2.0f;
|
|
gain = 1.0f - delta * 2.0f;
|
|
delta *= -1;
|
|
offset = gain * (brightness + delta);
|
|
for (int i = 0; i < 3; i++) {
|
|
final_color[i] = clamp_f(gain * orig_color[i] + offset, 0.0f, 1.0f);
|
|
}
|
|
break;
|
|
case COLOR_FILTER_CONTRAST:
|
|
fade = clamp_f(fade, -1.0f, 1.0f);
|
|
brightness = 0;
|
|
contrast = fade;
|
|
delta = contrast / 2.0f;
|
|
gain = 1.0f - delta * 2.0f;
|
|
if (contrast > 0) {
|
|
gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON);
|
|
offset = gain * (brightness - delta);
|
|
}
|
|
else {
|
|
delta *= -1;
|
|
offset = gain * (brightness + delta);
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
final_color[i] = clamp_f(gain * orig_color[i] + offset, 0.0f, 1.0f);
|
|
}
|
|
break;
|
|
case COLOR_FILTER_SMOOTH: {
|
|
fade = clamp_f(fade, -1.0f, 1.0f);
|
|
float smooth_color[4];
|
|
smooth::neighbor_color_average(ss, smooth_color, vd.vertex);
|
|
|
|
float col[4];
|
|
SCULPT_vertex_color_get(ss, vd.vertex, col);
|
|
|
|
if (fade < 0.0f) {
|
|
interp_v4_v4v4(smooth_color, smooth_color, col, 0.5f);
|
|
}
|
|
|
|
bool copy_alpha = col[3] == smooth_color[3];
|
|
|
|
if (fade < 0.0f) {
|
|
float delta_color[4];
|
|
|
|
/* Unsharp mask. */
|
|
copy_v4_v4(delta_color, ss->filter_cache->pre_smoothed_color[vd.index]);
|
|
sub_v4_v4(delta_color, smooth_color);
|
|
|
|
copy_v4_v4(final_color, col);
|
|
madd_v4_v4fl(final_color, delta_color, fade);
|
|
}
|
|
else {
|
|
blend_color_interpolate_float(final_color, col, smooth_color, fade);
|
|
}
|
|
|
|
final_color = math::clamp(final_color, 0.0f, 1.0f);
|
|
|
|
/* Prevent accumulated numeric error from corrupting alpha. */
|
|
if (copy_alpha) {
|
|
final_color[3] = smooth_color[3];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
SCULPT_vertex_color_set(ss, vd.vertex, final_color);
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
BKE_pbvh_node_mark_update_color(node);
|
|
}
|
|
|
|
static void sculpt_color_presmooth_init(SculptSession *ss)
|
|
{
|
|
int totvert = SCULPT_vertex_count_get(ss);
|
|
|
|
if (!ss->filter_cache->pre_smoothed_color) {
|
|
ss->filter_cache->pre_smoothed_color = static_cast<float(*)[4]>(
|
|
MEM_malloc_arrayN(totvert, sizeof(float[4]), __func__));
|
|
}
|
|
|
|
for (int i = 0; i < totvert; i++) {
|
|
SCULPT_vertex_color_get(
|
|
ss, BKE_pbvh_index_to_vertex(ss->pbvh, i), ss->filter_cache->pre_smoothed_color[i]);
|
|
}
|
|
|
|
for (int iteration = 0; iteration < 2; iteration++) {
|
|
for (int i = 0; i < totvert; i++) {
|
|
float avg[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
int total = 0;
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, BKE_pbvh_index_to_vertex(ss->pbvh, i), ni) {
|
|
float col[4] = {0};
|
|
|
|
copy_v4_v4(col, ss->filter_cache->pre_smoothed_color[ni.index]);
|
|
|
|
add_v4_v4(avg, col);
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
if (total > 0) {
|
|
mul_v4_fl(avg, 1.0f / float(total));
|
|
interp_v4_v4v4(ss->filter_cache->pre_smoothed_color[i],
|
|
ss->filter_cache->pre_smoothed_color[i],
|
|
avg,
|
|
0.5f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sculpt_color_filter_apply(bContext *C, wmOperator *op, Object *ob)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
const int mode = RNA_enum_get(op->ptr, "type");
|
|
float filter_strength = RNA_float_get(op->ptr, "strength");
|
|
float fill_color[3];
|
|
|
|
RNA_float_get_array(op->ptr, "fill_color", fill_color);
|
|
IMB_colormanagement_srgb_to_scene_linear_v3(fill_color, fill_color);
|
|
|
|
if (filter_strength < 0.0 && !ss->filter_cache->pre_smoothed_color) {
|
|
sculpt_color_presmooth_init(ss);
|
|
}
|
|
|
|
threading::parallel_for(ss->filter_cache->nodes.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
color_filter_task(ob, mode, filter_strength, fill_color, ss->filter_cache->nodes[i]);
|
|
}
|
|
});
|
|
|
|
SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
|
|
}
|
|
|
|
static void sculpt_color_filter_end(bContext *C, Object *ob)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
undo::push_end(ob);
|
|
filter::cache_free(ss);
|
|
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR);
|
|
}
|
|
|
|
static int sculpt_color_filter_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
|
|
sculpt_color_filter_end(C, ob);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
if (event->type != MOUSEMOVE) {
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
const float len = (event->prev_press_xy[0] - event->xy[0]) * 0.001f;
|
|
float filter_strength = ss->filter_cache->start_filter_strength * -len;
|
|
RNA_float_set(op->ptr, "strength", filter_strength);
|
|
|
|
sculpt_color_filter_apply(C, op, ob);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static int sculpt_color_filter_init(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
|
SculptSession *ss = ob->sculpt;
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
|
|
const Base *base = CTX_data_active_base(C);
|
|
if (!BKE_base_is_visible(v3d, base)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
int mval[2];
|
|
RNA_int_get_array(op->ptr, "start_mouse", mval);
|
|
float mval_fl[2] = {float(mval[0]), float(mval[1])};
|
|
|
|
const bool use_automasking = auto_mask::is_enabled(sd, ss, nullptr);
|
|
if (use_automasking) {
|
|
/* Increment stroke id for auto-masking system. */
|
|
SCULPT_stroke_id_next(ob);
|
|
|
|
if (v3d) {
|
|
/* Update the active face set manually as the paint cursor is not enabled when using the Mesh
|
|
* Filter Tool. */
|
|
SculptCursorGeometryInfo sgi;
|
|
SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false);
|
|
}
|
|
}
|
|
|
|
/* Disable for multires and dyntopo for now */
|
|
if (!ss->pbvh || !SCULPT_handles_colors_report(ss, op->reports)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
undo::push_begin(ob, op);
|
|
BKE_sculpt_color_layer_create_if_needed(ob);
|
|
|
|
/* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of
|
|
* earlier steps modifying the data. */
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
BKE_sculpt_update_object_for_edit(depsgraph, ob, true);
|
|
|
|
filter::cache_init(C,
|
|
ob,
|
|
sd,
|
|
undo::Type::Color,
|
|
mval_fl,
|
|
RNA_float_get(op->ptr, "area_normal_radius"),
|
|
RNA_float_get(op->ptr, "strength"));
|
|
filter::Cache *filter_cache = ss->filter_cache;
|
|
filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
|
|
filter_cache->automasking = auto_mask::cache_init(sd, ob);
|
|
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
static int sculpt_color_filter_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
|
|
if (sculpt_color_filter_init(C, op) == OPERATOR_CANCELLED) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
sculpt_color_filter_apply(C, op, ob);
|
|
sculpt_color_filter_end(C, ob);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
if (v3d && v3d->shading.type == OB_SOLID) {
|
|
v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR;
|
|
}
|
|
|
|
RNA_int_set_array(op->ptr, "start_mouse", event->mval);
|
|
|
|
if (sculpt_color_filter_init(C, op) == OPERATOR_CANCELLED) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ED_paint_tool_update_sticky_shading_color(C, ob);
|
|
|
|
WM_event_add_modal_handler(C, op);
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static std::string sculpt_color_filter_get_name(wmOperatorType * /*ot*/, PointerRNA *ptr)
|
|
{
|
|
PropertyRNA *prop = RNA_struct_find_property(ptr, "type");
|
|
const int value = RNA_property_enum_get(ptr, prop);
|
|
const char *ui_name = nullptr;
|
|
|
|
RNA_property_enum_name_gettexted(nullptr, ptr, prop, value, &ui_name);
|
|
return ui_name;
|
|
}
|
|
|
|
static void sculpt_color_filter_ui(bContext * /*C*/, wmOperator *op)
|
|
{
|
|
uiLayout *layout = op->layout;
|
|
|
|
uiItemR(layout, op->ptr, "strength", UI_ITEM_NONE, nullptr, ICON_NONE);
|
|
|
|
if (RNA_enum_get(op->ptr, "type") == COLOR_FILTER_FILL) {
|
|
uiItemR(layout, op->ptr, "fill_color", UI_ITEM_NONE, nullptr, ICON_NONE);
|
|
}
|
|
}
|
|
|
|
void SCULPT_OT_color_filter(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Filter Color";
|
|
ot->idname = "SCULPT_OT_color_filter";
|
|
ot->description = "Applies a filter to modify the active color attribute";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = sculpt_color_filter_invoke;
|
|
ot->exec = sculpt_color_filter_exec;
|
|
ot->modal = sculpt_color_filter_modal;
|
|
ot->poll = SCULPT_mode_poll;
|
|
ot->ui = sculpt_color_filter_ui;
|
|
ot->get_name = sculpt_color_filter_get_name;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* rna */
|
|
filter::register_operator_props(ot);
|
|
|
|
RNA_def_enum(ot->srna, "type", prop_color_filter_types, COLOR_FILTER_FILL, "Filter Type", "");
|
|
|
|
PropertyRNA *prop = RNA_def_float_color(ot->srna,
|
|
"fill_color",
|
|
3,
|
|
fill_filter_default_color,
|
|
0.0f,
|
|
FLT_MAX,
|
|
"Fill Color",
|
|
"",
|
|
0.0f,
|
|
1.0f);
|
|
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MESH);
|
|
RNA_def_property_subtype(prop, PROP_COLOR_GAMMA);
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint::color
|