GPv3: Tint tool
This implements the tint tool from GPv2. Pull Request: https://projects.blender.org/blender/blender/pulls/119323
This commit is contained in:
parent
5ce54fbd25
commit
127f879be9
@ -1441,6 +1441,9 @@ def brush_basic_grease_pencil_paint_settings(layout, context, brush, *, compact=
|
||||
if gp_settings.eraser_mode == "HARD":
|
||||
layout.prop(gp_settings, "use_keep_caps_eraser")
|
||||
layout.prop(gp_settings, "use_active_layer_only")
|
||||
elif grease_pencil_tool == 'TINT':
|
||||
layout.prop(gp_settings, "vertex_mode", text="Mode")
|
||||
layout.popover("VIEW3D_PT_tools_brush_settings_advanced", text="Brush")
|
||||
|
||||
|
||||
def brush_basic_gpencil_sculpt_settings(layout, _context, brush, *, compact=False):
|
||||
|
@ -1831,6 +1831,15 @@ class _defs_paint_grease_pencil:
|
||||
data_block='ERASE',
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def tint():
|
||||
return dict(
|
||||
idname="builtin_brush.Tint",
|
||||
label="Tint",
|
||||
icon="brush.gpencil_draw.tint",
|
||||
data_block='TINT',
|
||||
)
|
||||
|
||||
|
||||
class _defs_image_generic:
|
||||
|
||||
@ -3197,6 +3206,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
|
||||
None,
|
||||
_defs_paint_grease_pencil.draw,
|
||||
_defs_paint_grease_pencil.erase,
|
||||
_defs_paint_grease_pencil.tint,
|
||||
],
|
||||
'PAINT_GPENCIL': [
|
||||
_defs_view3d_generic.cursor,
|
||||
|
@ -609,6 +609,9 @@ class _draw_tool_settings_context_mode:
|
||||
)
|
||||
brush_basic__draw_color_selector(context, layout, brush, brush.gpencil_settings, None)
|
||||
|
||||
if grease_pencil_tool == 'TINT':
|
||||
UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
|
||||
|
||||
from bl_ui.properties_paint_common import (
|
||||
brush_basic__draw_color_selector,
|
||||
brush_basic_grease_pencil_paint_settings,
|
||||
|
@ -42,6 +42,7 @@ set(SRC
|
||||
curves_sculpt_snake_hook.cc
|
||||
grease_pencil_draw_ops.cc
|
||||
grease_pencil_erase.cc
|
||||
grease_pencil_tint.cc
|
||||
grease_pencil_paint.cc
|
||||
paint_canvas.cc
|
||||
paint_cursor.cc
|
||||
|
@ -52,6 +52,9 @@ static bool start_brush_operation(bContext &C,
|
||||
case GPAINT_TOOL_ERASE:
|
||||
operation = greasepencil::new_erase_operation().release();
|
||||
break;
|
||||
case GPAINT_TOOL_TINT:
|
||||
operation = greasepencil::new_tint_operation().release();
|
||||
break;
|
||||
}
|
||||
|
||||
if (operation) {
|
||||
|
@ -27,6 +27,7 @@ namespace greasepencil {
|
||||
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_erase_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation();
|
||||
|
||||
} // namespace greasepencil
|
||||
|
||||
|
270
source/blender/editors/sculpt_paint/grease_pencil_tint.cc
Normal file
270
source/blender/editors/sculpt_paint/grease_pencil_tint.cc
Normal file
@ -0,0 +1,270 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_brush.hh"
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
#include "BKE_material.h"
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_length_parameterize.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_geom.h"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_grease_pencil.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "grease_pencil_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint::greasepencil {
|
||||
|
||||
static constexpr float POINT_OVERRIDE_THRESHOLD_PX = 3.0f;
|
||||
static constexpr float POINT_RESAMPLE_MIN_DISTANCE_PX = 10.0f;
|
||||
|
||||
using ed::greasepencil::MutableDrawingInfo;
|
||||
|
||||
class TintOperation : public GreasePencilStrokeOperation {
|
||||
public:
|
||||
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
|
||||
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
|
||||
void on_stroke_done(const bContext &C) override;
|
||||
|
||||
private:
|
||||
float radius_;
|
||||
float strength_;
|
||||
bool active_layer_only_;
|
||||
ColorGeometry4f color_;
|
||||
Vector<MutableDrawingInfo> drawings_;
|
||||
Array<Array<float2>> screen_positions_per_drawing_;
|
||||
|
||||
void execute_tint(const bContext &C, const InputSample &extension_sample);
|
||||
};
|
||||
|
||||
void TintOperation::on_stroke_begin(const bContext &C, const InputSample & /*start_sample*/)
|
||||
{
|
||||
using namespace blender::bke::greasepencil;
|
||||
Scene *scene = CTX_data_scene(&C);
|
||||
Paint *paint = BKE_paint_get_active_from_context(&C);
|
||||
Brush *brush = BKE_paint_brush(paint);
|
||||
|
||||
BKE_curvemapping_init(brush->gpencil_settings->curve_sensitivity);
|
||||
BKE_curvemapping_init(brush->gpencil_settings->curve_strength);
|
||||
|
||||
if (brush->gpencil_settings == nullptr) {
|
||||
BKE_brush_init_gpencil_settings(brush);
|
||||
}
|
||||
BLI_assert(brush->gpencil_settings != nullptr);
|
||||
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
radius_ = BKE_brush_size_get(scene, brush);
|
||||
strength_ = BKE_brush_alpha_get(scene, brush);
|
||||
active_layer_only_ = ((brush->gpencil_settings->flag & GP_BRUSH_ACTIVE_LAYER_ONLY) != 0);
|
||||
|
||||
float4 color_linear;
|
||||
color_linear[3] = 1.0f;
|
||||
srgb_to_linearrgb_v3_v3(color_linear, brush->rgb);
|
||||
|
||||
color_ = ColorGeometry4f(color_linear);
|
||||
|
||||
Object *obact = CTX_data_active_object(&C);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
|
||||
|
||||
if (active_layer_only_) {
|
||||
/* Tint only on the drawings of the active layer. */
|
||||
const Layer *active_layer = grease_pencil.get_active_layer();
|
||||
if (!active_layer) {
|
||||
return;
|
||||
}
|
||||
drawings_ = ed::greasepencil::retrieve_editable_drawings_from_layer(
|
||||
*scene, grease_pencil, *active_layer);
|
||||
}
|
||||
else {
|
||||
/* Tint on all editable drawings. */
|
||||
drawings_ = ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
|
||||
}
|
||||
|
||||
if (drawings_.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ARegion *region = CTX_wm_region(&C);
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
|
||||
Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact);
|
||||
|
||||
screen_positions_per_drawing_.reinitialize(drawings_.size());
|
||||
|
||||
threading::parallel_for_each(drawings_.index_range(), [&](const int drawing_index) {
|
||||
MutableDrawingInfo drawing_info = drawings_[drawing_index];
|
||||
bke::CurvesGeometry &strokes = drawing_info.drawing.strokes_for_write();
|
||||
const Layer &layer = *grease_pencil.layers()[drawing_info.layer_index];
|
||||
|
||||
screen_positions_per_drawing_[drawing_index].reinitialize(strokes.points_num());
|
||||
|
||||
bke::crazyspace::GeometryDeformation deformation =
|
||||
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
|
||||
ob_eval, *obact, drawing_info.layer_index, drawing_info.frame_number);
|
||||
|
||||
for (const int point : strokes.points_range()) {
|
||||
ED_view3d_project_float_global(
|
||||
region,
|
||||
math::transform_point(layer.to_world_space(*ob_eval), deformation.positions[point]),
|
||||
screen_positions_per_drawing_[drawing_index][point],
|
||||
V3D_PROJ_TEST_NOP);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TintOperation::execute_tint(const bContext &C, const InputSample &extension_sample)
|
||||
{
|
||||
if (drawings_.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace blender::bke::greasepencil;
|
||||
Scene *scene = CTX_data_scene(&C);
|
||||
Object *obact = CTX_data_active_object(&C);
|
||||
|
||||
Paint *paint = &scene->toolsettings->gp_paint->paint;
|
||||
Brush *brush = BKE_paint_brush(paint);
|
||||
|
||||
/* Get the tool's data. */
|
||||
const float2 mouse_position = extension_sample.mouse_position;
|
||||
float radius = radius_;
|
||||
float strength = strength_;
|
||||
if (BKE_brush_use_size_pressure(brush)) {
|
||||
radius *= BKE_curvemapping_evaluateF(
|
||||
brush->gpencil_settings->curve_sensitivity, 0, extension_sample.pressure);
|
||||
}
|
||||
if (BKE_brush_use_alpha_pressure(brush)) {
|
||||
strength *= BKE_curvemapping_evaluateF(
|
||||
brush->gpencil_settings->curve_strength, 0, extension_sample.pressure);
|
||||
}
|
||||
/* Attenuate factor to get a smoother tinting. */
|
||||
float fill_strength = strength / 100.0f;
|
||||
|
||||
strength = math::clamp(strength, 0.0f, 1.0f);
|
||||
fill_strength = math::clamp(fill_strength, 0.0f, 1.0f);
|
||||
|
||||
const bool tint_strokes = ((brush->gpencil_settings->vertex_mode == GPPAINT_MODE_STROKE) ||
|
||||
(brush->gpencil_settings->vertex_mode == GPPAINT_MODE_BOTH));
|
||||
const bool tint_fills = ((brush->gpencil_settings->vertex_mode == GPPAINT_MODE_FILL) ||
|
||||
(brush->gpencil_settings->vertex_mode == GPPAINT_MODE_BOTH));
|
||||
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
|
||||
|
||||
std::atomic<bool> changed = false;
|
||||
const auto execute_tint_on_drawing = [&](Drawing &drawing, const int drawing_index) {
|
||||
bke::CurvesGeometry &strokes = drawing.strokes_for_write();
|
||||
|
||||
MutableSpan<ColorGeometry4f> vertex_colors = drawing.vertex_colors_for_write();
|
||||
bke::MutableAttributeAccessor stroke_attributes = strokes.attributes_for_write();
|
||||
bke::SpanAttributeWriter<ColorGeometry4f> fill_colors =
|
||||
stroke_attributes.lookup_or_add_for_write_span<ColorGeometry4f>(
|
||||
"fill_color",
|
||||
bke::AttrDomain::Curve,
|
||||
bke::AttributeInitVArray(VArray<ColorGeometry4f>::ForSingle(
|
||||
ColorGeometry4f(float4(0.0f)), strokes.curves_num())));
|
||||
OffsetIndices<int> points_by_curve = strokes.points_by_curve();
|
||||
|
||||
const Span<float2> screen_space_positions =
|
||||
screen_positions_per_drawing_[drawing_index].as_span();
|
||||
|
||||
auto point_inside_stroke = [&](const Span<float2> points, const float2 mouse) {
|
||||
std::optional<Bounds<float2>> bbox = bounds::min_max(points);
|
||||
if (!bbox.has_value()) {
|
||||
return false;
|
||||
}
|
||||
Bounds<float2> &box = bbox.value();
|
||||
if (mouse.x < box.min.x || mouse.x > box.max.x || mouse.y < box.min.y || mouse.y > box.max.y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return isect_point_poly_v2(
|
||||
mouse, reinterpret_cast<const float(*)[2]>(points.data()), points.size());
|
||||
};
|
||||
|
||||
threading::parallel_for(strokes.curves_range(), 128, [&](const IndexRange range) {
|
||||
for (const int curve : range) {
|
||||
bool stroke_touched = false;
|
||||
for (const int curve_point : points_by_curve[curve].index_range()) {
|
||||
if (tint_strokes) {
|
||||
const int point = curve_point + points_by_curve[curve].first();
|
||||
const float distance = math::distance(screen_space_positions[point], mouse_position);
|
||||
const float influence = strength * BKE_brush_curve_strength(brush, distance, radius);
|
||||
if (influence > 0.0f) {
|
||||
stroke_touched = true;
|
||||
/* Manually do an alpha-over mix, not using `ColorGeometry4f::premultiply_alpha`
|
||||
* since the vertex color in GPv3 is stored as straight alpha (which is technically
|
||||
* `ColorPaint4f`). */
|
||||
float4 premultiplied;
|
||||
straight_to_premul_v4_v4(premultiplied, vertex_colors[point]);
|
||||
float4 rgba = float4(
|
||||
math::interpolate(float3(premultiplied), float3(color_), influence),
|
||||
vertex_colors[point][3]);
|
||||
rgba[3] = rgba[3] * (1.0f - influence) + influence;
|
||||
premul_to_straight_v4_v4(vertex_colors[point], rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tint_fills && !fill_colors.span.is_empty()) {
|
||||
/* Will tint fill color when either the brush being inside the fill region or touching
|
||||
* the stroke. */
|
||||
const bool fill_effective = stroke_touched ||
|
||||
point_inside_stroke(screen_space_positions.slice(
|
||||
points_by_curve[curve].first(),
|
||||
points_by_curve[curve].size()),
|
||||
mouse_position);
|
||||
if (fill_effective) {
|
||||
float4 premultiplied;
|
||||
straight_to_premul_v4_v4(premultiplied, fill_colors.span[curve]);
|
||||
float4 rgba = float4(
|
||||
math::interpolate(float3(premultiplied), float3(color_), fill_strength),
|
||||
fill_colors.span[curve][3]);
|
||||
rgba[3] = rgba[3] * (1.0f - fill_strength) + fill_strength;
|
||||
premul_to_straight_v4_v4(fill_colors.span[curve], rgba);
|
||||
stroke_touched = true;
|
||||
}
|
||||
}
|
||||
if (stroke_touched) {
|
||||
changed.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
fill_colors.finish();
|
||||
};
|
||||
|
||||
threading::parallel_for_each(drawings_.index_range(), [&](const int drawing_index) {
|
||||
const MutableDrawingInfo &info = drawings_[drawing_index];
|
||||
execute_tint_on_drawing(info.drawing, drawing_index);
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
|
||||
}
|
||||
}
|
||||
|
||||
void TintOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
|
||||
{
|
||||
execute_tint(C, extension_sample);
|
||||
}
|
||||
|
||||
void TintOperation::on_stroke_done(const bContext & /*C*/) {}
|
||||
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation()
|
||||
{
|
||||
return std::make_unique<TintOperation>();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
Loading…
Reference in New Issue
Block a user