Fix T98975: Broken vertex paint mode operators

All of the operators in vertex paint mode didn't work properly with
the new color attribute system. They only worked on byte color type
attributes on the face corner domain.

Since there are four possible combinations of domains and types now,
it mostly ended up being simpler to convert the code to C++ and use
the geometry component API for retrieving attributes, interpolating
between domains, etc. The code changes ended up being fairly large,
but the result should be simpler now.

Differential Revision: https://developer.blender.org/D15261
This commit is contained in:
Hans Goudey 2022-06-23 11:32:53 -05:00
parent 5c6ffd07e0
commit 2eba15d3e8
Notes: blender-bot 2023-02-14 02:27:56 +01:00
Referenced by issue #99132, Vertex Color from Weight Operator: Bug and Crash
Referenced by issue #98975, Regression: Dirty Vertex Color, Invert, Levels, HSV, Brightness-Contrast ignores destination Color Attribute selection (also crash)
Referenced by issue #98661, 3.2: Potential candidates for corrective releases
5 changed files with 227 additions and 273 deletions

View File

@ -2,15 +2,10 @@
# Copyright Campbell Barton.
def get_vcolor_layer_data(me):
for lay in me.vertex_colors:
if lay.active:
return lay.data
lay = me.vertex_colors.new()
lay.active = True
return lay.data
def ensure_active_color_attribute(me):
if me.attributes.active_color:
return me.attributes.active_color
return me.color_attributes.new("Color", 'BYTE_COLOR', 'FACE_CORNER')
def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, dirt_only, normalize):
from mathutils import Vector
@ -99,17 +94,21 @@ def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean,
else:
tone_range = 1.0 / tone_range
active_col_layer = get_vcolor_layer_data(me)
if not active_col_layer:
active_color_attribute = ensure_active_color_attribute(me)
if not active_color_attribute:
return {'CANCELLED'}
point_domain = active_color_attribute.domain == 'POINT'
attribute_data = active_color_attribute.data
use_paint_mask = me.use_paint_mask
for i, p in enumerate(me.polygons):
if not use_paint_mask or p.select:
for loop_index in p.loop_indices:
loop = me.loops[loop_index]
v = loop.vertex_index
col = active_col_layer[loop_index].color
col = attribute_data[v if point_domain else loop_index].color
tone = vert_tone[v]
tone = (tone - min_tone) * tone_range

View File

@ -53,7 +53,6 @@ set(SRC
paint_utils.c
paint_vertex.cc
paint_vertex_color_ops.cc
paint_vertex_color_utils.c
paint_vertex_proj.c
paint_vertex_weight_ops.c
paint_vertex_weight_utils.c

View File

@ -134,18 +134,10 @@ void PAINT_OT_vertex_paint(struct wmOperatorType *ot);
unsigned int vpaint_get_current_color(struct Scene *scene, struct VPaint *vp, bool secondary);
/* paint_vertex_color_utils.c */
/**
* \note weight-paint has an equivalent function: #ED_wpaint_blend_tool
*/
unsigned int ED_vpaint_blend_tool(int tool, uint col, uint paintcol, int alpha_i);
/**
* Apply callback to each vertex of the active vertex color layer.
*/
bool ED_vpaint_color_transform(struct Object *ob,
VPaintTransform_Callback vpaint_tx_fn,
const void *user_data);
/* paint_vertex_weight_utils.c */

View File

@ -12,11 +12,15 @@
#include "DNA_scene_types.h"
#include "BLI_array.hh"
#include "BLI_index_mask_ops.hh"
#include "BLI_math_base.h"
#include "BLI_math_color.h"
#include "BLI_vector.hh"
#include "BKE_attribute_math.hh"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_geometry_set.hh"
#include "BKE_mesh.h"
#include "DEG_depsgraph.h"
@ -32,6 +36,10 @@
#include "paint_intern.h" /* own include */
using blender::Array;
using blender::ColorGeometry4f;
using blender::GMutableSpan;
using blender::IndexMask;
using blender::Vector;
/* -------------------------------------------------------------------- */
/** \name Internal Utility Functions
@ -62,34 +70,57 @@ static void tag_object_after_update(Object *object)
static bool vertex_paint_from_weight(Object *ob)
{
Mesh *me;
const MPoly *mp;
int vgroup_active;
using namespace blender;
Mesh *me;
if (((me = BKE_mesh_from_object(ob)) == nullptr ||
(ED_mesh_color_ensure(me, nullptr)) == false)) {
return false;
}
/* TODO: respect selection. */
/* TODO: Do we want to take weights from evaluated mesh instead? 2.7x was not doing it anyway. */
mp = me->mpoly;
vgroup_active = me->vertex_group_active_index - 1;
for (int i = 0; i < me->totpoly; i++, mp++) {
MLoopCol *lcol = &me->mloopcol[mp->loopstart];
uint j = 0;
do {
uint vidx = me->mloop[mp->loopstart + j].v;
const float weight = BKE_defvert_find_weight(&me->dvert[vidx], vgroup_active);
const uchar grayscale = weight * 255;
lcol->r = grayscale;
lcol->b = grayscale;
lcol->g = grayscale;
lcol++;
j++;
} while (j < mp->totloop);
const CustomDataLayer *active_color_layer = BKE_id_attributes_active_color_get(&me->id);
if (active_color_layer == nullptr) {
BLI_assert_unreachable();
return false;
}
const int active_vertex_group_index = me->vertex_group_active_index - 1;
const bDeformGroup *deform_group = static_cast<const bDeformGroup *>(
BLI_findlink(&me->vertex_group_names, active_vertex_group_index));
if (deform_group == nullptr) {
BLI_assert_unreachable();
return false;
}
MeshComponent component;
component.replace(me, GeometryOwnershipType::Editable);
bke::WriteAttributeLookup color_attribute = component.attribute_try_get_for_write(
active_color_layer->name);
if (!color_attribute) {
BLI_assert_unreachable();
return false;
}
/* Retrieve the vertex group with the domain and type of the existing color
* attribute, in order to let the attribute API handle both conversions. */
const GVArray vertex_group = component.attribute_get_for_read(
deform_group->name,
ATTR_DOMAIN_POINT,
bke::cpp_type_to_custom_data_type(color_attribute.varray.type()));
if (!vertex_group) {
BLI_assert_unreachable();
return false;
}
GVArray_GSpan interpolated{component.attribute_try_adapt_domain(
vertex_group, ATTR_DOMAIN_POINT, color_attribute.domain)};
color_attribute.varray.set_all(interpolated.data());
if (color_attribute.tag_modified_fn) {
color_attribute.tag_modified_fn();
}
tag_object_after_update(ob);
return true;
@ -128,99 +159,81 @@ void PAINT_OT_vertex_color_from_weight(wmOperatorType *ot)
/** \name Smooth Vertex Colors Operator
* \{ */
static void vertex_color_smooth_looptag(Mesh *me, const bool *mlooptag)
static IndexMask get_selected_indices(const Mesh &mesh,
const eAttrDomain domain,
Vector<int64_t> &indices)
{
const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const MPoly *mp;
bool has_shared = false;
using namespace blender;
Span<MVert> verts(mesh.mvert, mesh.totvert);
Span<MPoly> faces(mesh.mpoly, mesh.totpoly);
if (me->mloopcol == nullptr || me->totvert == 0 || me->totpoly == 0) {
MeshComponent component;
component.replace(&const_cast<Mesh &>(mesh), GeometryOwnershipType::ReadOnly);
if (mesh.editflag & ME_EDIT_PAINT_FACE_SEL) {
const VArray<bool> selection = component.attribute_try_adapt_domain(
VArray<bool>::ForFunc(faces.size(),
[&](const int i) { return faces[i].flag & ME_FACE_SEL; }),
ATTR_DOMAIN_FACE,
domain);
return index_mask_ops::find_indices_based_on_predicate(
IndexMask(component.attribute_domain_num(domain)), 4096, indices, [&](const int i) {
return selection[i];
});
}
if (mesh.editflag & ME_EDIT_PAINT_VERT_SEL) {
const VArray<bool> selection = component.attribute_try_adapt_domain(
VArray<bool>::ForFunc(verts.size(), [&](const int i) { return verts[i].flag & SELECT; }),
ATTR_DOMAIN_POINT,
domain);
return index_mask_ops::find_indices_based_on_predicate(
IndexMask(component.attribute_domain_num(domain)), 4096, indices, [&](const int i) {
return selection[i];
});
}
return IndexMask(component.attribute_domain_num(domain));
}
static void face_corner_color_equalize_vertices(Mesh &mesh, const IndexMask selection)
{
using namespace blender;
const CustomDataLayer *active_color_layer = BKE_id_attributes_active_color_get(&mesh.id);
if (active_color_layer == nullptr) {
BLI_assert_unreachable();
return;
}
int(*scol)[4] = static_cast<int(*)[4]>(MEM_callocN(sizeof(int) * me->totvert * 5, "scol"));
MeshComponent component;
component.replace(&mesh, GeometryOwnershipType::Editable);
int i;
for (i = 0, mp = me->mpoly; i < me->totpoly; i++, mp++) {
if ((use_face_sel == false) || (mp->flag & ME_FACE_SEL)) {
const MLoop *ml = me->mloop + mp->loopstart;
MLoopCol *lcol = me->mloopcol + mp->loopstart;
for (int j = 0; j < mp->totloop; j++, ml++, lcol++) {
scol[ml->v][0] += lcol->r;
scol[ml->v][1] += lcol->g;
scol[ml->v][2] += lcol->b;
scol[ml->v][3] += 1;
has_shared = 1;
}
}
if (component.attribute_get_meta_data(active_color_layer->name)->domain == ATTR_DOMAIN_POINT) {
return;
}
if (has_shared) {
for (i = 0; i < me->totvert; i++) {
if (scol[i][3] != 0) {
scol[i][0] = divide_round_i(scol[i][0], scol[i][3]);
scol[i][1] = divide_round_i(scol[i][1], scol[i][3]);
scol[i][2] = divide_round_i(scol[i][2], scol[i][3]);
}
}
GVArray color_attribute_point = component.attribute_try_get_for_read(active_color_layer->name,
ATTR_DOMAIN_POINT);
for (i = 0, mp = me->mpoly; i < me->totpoly; i++, mp++) {
if ((use_face_sel == false) || (mp->flag & ME_FACE_SEL)) {
const MLoop *ml = me->mloop + mp->loopstart;
MLoopCol *lcol = me->mloopcol + mp->loopstart;
for (int j = 0; j < mp->totloop; j++, ml++, lcol++) {
if (mlooptag[mp->loopstart + j]) {
lcol->r = scol[ml->v][0];
lcol->g = scol[ml->v][1];
lcol->b = scol[ml->v][2];
}
}
}
}
}
GVArray color_attribute_corner = component.attribute_try_adapt_domain(
color_attribute_point, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER);
MEM_freeN(scol);
color_attribute_corner.materialize(selection, active_color_layer->data);
}
static bool vertex_color_smooth(Object *ob)
{
Mesh *me;
const MPoly *mp;
int i, j;
if (((me = BKE_mesh_from_object(ob)) == nullptr) ||
(ED_mesh_color_ensure(me, nullptr) == false)) {
return false;
}
const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
Vector<int64_t> indices;
const IndexMask selection = get_selected_indices(*me, ATTR_DOMAIN_CORNER, indices);
Array<bool> loop_tag(me->totloop, false);
/* simply tag loops of selected faces */
mp = me->mpoly;
for (i = 0; i < me->totpoly; i++, mp++) {
const MLoop *ml = me->mloop + mp->loopstart;
if (use_face_sel && !(mp->flag & ME_FACE_SEL)) {
continue;
}
j = 0;
do {
if (!(use_vert_sel && !(me->mvert[ml->v].flag & SELECT))) {
loop_tag[mp->loopstart + j] = true;
}
ml++;
j++;
} while (j < mp->totloop);
}
/* remove stale me->mcol, will be added later */
BKE_mesh_tessface_clear(me);
vertex_color_smooth_looptag(me, loop_tag.data());
face_corner_color_equalize_vertices(*me, selection);
tag_object_after_update(ob);
@ -258,22 +271,52 @@ void PAINT_OT_vertex_color_smooth(wmOperatorType *ot)
/** \name Vertex Color Transformation Operators
* \{ */
struct VPaintTx_BrightContrastData {
/* pre-calculated */
float gain;
float offset;
};
static void vpaint_tx_brightness_contrast(const float col[3],
const void *user_data,
float r_col[3])
template<typename TransformFn>
static bool transform_active_color(Mesh &mesh, const TransformFn &transform_fn)
{
const VPaintTx_BrightContrastData *data = static_cast<const VPaintTx_BrightContrastData *>(
user_data);
using namespace blender;
for (int i = 0; i < 3; i++) {
r_col[i] = data->gain * col[i] + data->offset;
const CustomDataLayer *active_color_layer = BKE_id_attributes_active_color_get(&mesh.id);
if (active_color_layer == nullptr) {
BLI_assert_unreachable();
return false;
}
MeshComponent component;
component.replace(&mesh, GeometryOwnershipType::Editable);
bke::WriteAttributeLookup color_attribute = component.attribute_try_get_for_write(
active_color_layer->name);
if (!color_attribute) {
BLI_assert_unreachable();
return false;
}
Vector<int64_t> indices;
const IndexMask selection = get_selected_indices(mesh, color_attribute.domain, indices);
attribute_math::convert_to_static_type(color_attribute.varray.type(), [&](auto dummy) {
using T = decltype(dummy);
threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
if constexpr (std::is_same_v<T, ColorGeometry4f>) {
ColorGeometry4f color = color_attribute.varray.get<ColorGeometry4f>(i);
transform_fn(color);
color_attribute.varray.set_by_copy(i, &color);
}
else if constexpr (std::is_same_v<T, ColorGeometry4b>) {
ColorGeometry4f color = color_attribute.varray.get<ColorGeometry4b>(i).decode();
transform_fn(color);
ColorGeometry4b color_encoded = color.encode();
color_attribute.varray.set_by_copy(i, &color_encoded);
}
}
});
});
DEG_id_tag_update(&mesh.id, 0);
return true;
}
static int vertex_color_brightness_contrast_exec(bContext *C, wmOperator *op)
@ -303,15 +346,20 @@ static int vertex_color_brightness_contrast_exec(bContext *C, wmOperator *op)
}
}
VPaintTx_BrightContrastData user_data{};
user_data.gain = gain;
user_data.offset = offset;
if (ED_vpaint_color_transform(obact, vpaint_tx_brightness_contrast, &user_data)) {
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
Mesh *me;
if (((me = BKE_mesh_from_object(obact)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_CANCELLED;
transform_active_color(*me, [&](ColorGeometry4f &color) {
for (int i = 0; i < 3; i++) {
color[i] = gain * color[i] + offset;
}
});
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
}
void PAINT_OT_vertex_color_brightness_contrast(wmOperatorType *ot)
@ -337,45 +385,39 @@ void PAINT_OT_vertex_color_brightness_contrast(wmOperatorType *ot)
RNA_def_property_ui_range(prop, min, max, 1, 1);
}
struct VPaintTx_HueSatData {
float hue;
float sat;
float val;
};
static void vpaint_tx_hsv(const float col[3], const void *user_data, float r_col[3])
{
const VPaintTx_HueSatData *data = static_cast<const VPaintTx_HueSatData *>(user_data);
float hsv[3];
rgb_to_hsv_v(col, hsv);
hsv[0] += (data->hue - 0.5f);
if (hsv[0] > 1.0f) {
hsv[0] -= 1.0f;
}
else if (hsv[0] < 0.0f) {
hsv[0] += 1.0f;
}
hsv[1] *= data->sat;
hsv[2] *= data->val;
hsv_to_rgb_v(hsv, r_col);
}
static int vertex_color_hsv_exec(bContext *C, wmOperator *op)
{
Object *obact = CTX_data_active_object(C);
VPaintTx_HueSatData user_data{};
user_data.hue = RNA_float_get(op->ptr, "h");
user_data.sat = RNA_float_get(op->ptr, "s");
user_data.val = RNA_float_get(op->ptr, "v");
const float hue = RNA_float_get(op->ptr, "h");
const float sat = RNA_float_get(op->ptr, "s");
const float val = RNA_float_get(op->ptr, "v");
if (ED_vpaint_color_transform(obact, vpaint_tx_hsv, &user_data)) {
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
Mesh *me;
if (((me = BKE_mesh_from_object(obact)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_CANCELLED;
transform_active_color(*me, [&](ColorGeometry4f &color) {
float hsv[3];
rgb_to_hsv_v(color, hsv);
hsv[0] += (hue - 0.5f);
if (hsv[0] > 1.0f) {
hsv[0] -= 1.0f;
}
else if (hsv[0] < 0.0f) {
hsv[0] += 1.0f;
}
hsv[1] *= sat;
hsv[2] *= val;
hsv_to_rgb_v(hsv, color);
});
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
}
void PAINT_OT_vertex_color_hsv(wmOperatorType *ot)
@ -398,22 +440,24 @@ void PAINT_OT_vertex_color_hsv(wmOperatorType *ot)
RNA_def_float(ot->srna, "v", 1.0f, 0.0f, 2.0f, "Value", "", 0.0f, 2.0f);
}
static void vpaint_tx_invert(const float col[3], const void *UNUSED(user_data), float r_col[3])
{
for (int i = 0; i < 3; i++) {
r_col[i] = 1.0f - col[i];
}
}
static int vertex_color_invert_exec(bContext *C, wmOperator *UNUSED(op))
{
Object *obact = CTX_data_active_object(C);
if (ED_vpaint_color_transform(obact, vpaint_tx_invert, nullptr)) {
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
Mesh *me;
if (((me = BKE_mesh_from_object(obact)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_CANCELLED;
transform_active_color(*me, [&](ColorGeometry4f &color) {
for (int i = 0; i < 3; i++) {
color[i] = 1.0f - color[i];
}
});
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
}
void PAINT_OT_vertex_color_invert(wmOperatorType *ot)
@ -431,32 +475,27 @@ void PAINT_OT_vertex_color_invert(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
struct VPaintTx_LevelsData {
float gain;
float offset;
};
static void vpaint_tx_levels(const float col[3], const void *user_data, float r_col[3])
{
const VPaintTx_LevelsData *data = static_cast<const VPaintTx_LevelsData *>(user_data);
for (int i = 0; i < 3; i++) {
r_col[i] = data->gain * (col[i] + data->offset);
}
}
static int vertex_color_levels_exec(bContext *C, wmOperator *op)
{
Object *obact = CTX_data_active_object(C);
VPaintTx_LevelsData user_data{};
user_data.gain = RNA_float_get(op->ptr, "gain");
user_data.offset = RNA_float_get(op->ptr, "offset");
const float gain = RNA_float_get(op->ptr, "gain");
const float offset = RNA_float_get(op->ptr, "offset");
if (ED_vpaint_color_transform(obact, vpaint_tx_levels, &user_data)) {
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
Mesh *me;
if (((me = BKE_mesh_from_object(obact)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_CANCELLED;
transform_active_color(*me, [&](ColorGeometry4f &color) {
for (int i = 0; i < 3; i++) {
color[i] = gain * (color[i] + offset);
}
});
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact);
return OPERATOR_FINISHED;
}
void PAINT_OT_vertex_color_levels(wmOperatorType *ot)

View File

@ -1,75 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edsculpt
*
* Intended for use by `paint_vertex.c` & `paint_vertex_color_ops.c`.
*/
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "BLI_math_base.h"
#include "BLI_math_color.h"
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
#include "BKE_context.h"
#include "BKE_mesh.h"
#include "DEG_depsgraph.h"
#include "ED_mesh.h"
#include "paint_intern.h" /* own include */
#define EPS_SATURATION 0.0005f
bool ED_vpaint_color_transform(struct Object *ob,
VPaintTransform_Callback vpaint_tx_fn,
const void *user_data)
{
Mesh *me;
const MPoly *mp;
int i, j;
if (((me = BKE_mesh_from_object(ob)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) {
return false;
}
const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
mp = me->mpoly;
for (i = 0; i < me->totpoly; i++, mp++) {
MLoopCol *lcol = me->mloopcol + mp->loopstart;
if (use_face_sel && !(mp->flag & ME_FACE_SEL)) {
continue;
}
j = 0;
do {
uint vidx = me->mloop[mp->loopstart + j].v;
if (!(use_vert_sel && !(me->mvert[vidx].flag & SELECT))) {
float col_mix[3];
rgb_uchar_to_float(col_mix, &lcol->r);
vpaint_tx_fn(col_mix, user_data, col_mix);
rgb_float_to_uchar(&lcol->r, col_mix);
}
lcol++;
j++;
} while (j < mp->totloop);
}
/* remove stale me->mcol, will be added later */
BKE_mesh_tessface_clear(me);
DEG_id_tag_update(&me->id, 0);
return true;
}