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:
YimingWu 2024-03-29 12:43:15 +01:00 committed by Falk David
parent 5ce54fbd25
commit 127f879be9
7 changed files with 291 additions and 0 deletions

View File

@ -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):

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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) {

View File

@ -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

View 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