Blender V4.3
grease_pencil_tint.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BKE_attribute.hh"
6#include "BKE_brush.hh"
7#include "BKE_colortools.hh"
8#include "BKE_context.hh"
9#include "BKE_curves.hh"
10#include "BKE_grease_pencil.hh"
11#include "BKE_material.h"
12#include "BKE_paint.hh"
13
14#include "BLI_bounds.hh"
16#include "BLI_math_color.h"
17#include "BLI_math_geom.h"
18
20
21#include "ED_curves.hh"
22#include "ED_grease_pencil.hh"
23#include "ED_view3d.hh"
24
25#include "WM_api.hh"
26#include "WM_types.hh"
27
29
31
32using ed::greasepencil::MutableDrawingInfo;
33
35 public:
36 void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
37 void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
38 void on_stroke_done(const bContext &C) override;
39
40 private:
41 float radius_;
42 float strength_;
43 bool active_layer_only_;
44 ColorGeometry4f color_;
46 Array<Array<float2>> screen_positions_per_drawing_;
47
48 void execute_tint(const bContext &C, const InputSample &extension_sample);
49};
50
51void TintOperation::on_stroke_begin(const bContext &C, const InputSample & /*start_sample*/)
52{
53 using namespace blender::bke::greasepencil;
54 Scene *scene = CTX_data_scene(&C);
56 Brush *brush = BKE_paint_brush(paint);
57
60
61 if (brush->gpencil_settings == nullptr) {
63 }
64 BLI_assert(brush->gpencil_settings != nullptr);
65
67
68 radius_ = brush->size;
69 strength_ = brush->alpha;
70 active_layer_only_ = ((brush->gpencil_settings->flag & GP_BRUSH_ACTIVE_LAYER_ONLY) != 0);
71
72 float4 color_linear;
73 color_linear[3] = 1.0f;
74 srgb_to_linearrgb_v3_v3(color_linear, BKE_brush_color_get(scene, paint, brush));
75
76 color_ = ColorGeometry4f(color_linear);
77
78 Object *obact = CTX_data_active_object(&C);
79 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
80
81 if (active_layer_only_) {
82 /* Tint only on the drawings of the active layer. */
83 const Layer *active_layer = grease_pencil.get_active_layer();
84 if (!active_layer) {
85 return;
86 }
88 *scene, grease_pencil, *active_layer);
89 }
90 else {
91 /* Tint on all editable drawings. */
92 drawings_ = ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
93 }
94
95 if (drawings_.is_empty()) {
96 return;
97 }
98
99 ARegion *region = CTX_wm_region(&C);
100 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
101 Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact);
102
103 screen_positions_per_drawing_.reinitialize(drawings_.size());
104
105 threading::parallel_for_each(drawings_, [&](const MutableDrawingInfo &drawing_info) {
106 const int drawing_index = (&drawing_info - drawings_.data());
107
108 bke::CurvesGeometry &strokes = drawing_info.drawing.strokes_for_write();
109 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
110
111 screen_positions_per_drawing_[drawing_index].reinitialize(strokes.points_num());
112
115 ob_eval, *obact, drawing_info.layer_index, drawing_info.frame_number);
116
117 for (const int point : strokes.points_range()) {
119 region,
120 math::transform_point(layer.to_world_space(*ob_eval), deformation.positions[point]),
121 screen_positions_per_drawing_[drawing_index][point],
123 }
124 });
125}
126
127void TintOperation::execute_tint(const bContext &C, const InputSample &extension_sample)
128{
129 if (drawings_.is_empty()) {
130 return;
131 }
132
133 using namespace blender::bke::greasepencil;
134 Scene *scene = CTX_data_scene(&C);
135 Object *obact = CTX_data_active_object(&C);
136
137 Paint *paint = &scene->toolsettings->gp_paint->paint;
138 Brush *brush = BKE_paint_brush(paint);
139
140 /* Get the brush's data. */
141 const float2 mouse_position = extension_sample.mouse_position;
142 float radius = radius_;
143 float strength = strength_;
144 if (BKE_brush_use_size_pressure(brush)) {
146 brush->gpencil_settings->curve_sensitivity, 0, extension_sample.pressure);
147 }
148 if (BKE_brush_use_alpha_pressure(brush)) {
149 strength *= BKE_curvemapping_evaluateF(
150 brush->gpencil_settings->curve_strength, 0, extension_sample.pressure);
151 }
152 /* Attenuate factor to get a smoother tinting. */
153 float fill_strength = strength / 100.0f;
154
155 strength = math::clamp(strength, 0.0f, 1.0f);
156 fill_strength = math::clamp(fill_strength, 0.0f, 1.0f);
157
158 const bool tint_strokes = ELEM(
160 const bool tint_fills = ELEM(
162
163 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
164
165 std::atomic<bool> changed = false;
166 const auto execute_tint_on_drawing = [&](Drawing &drawing, const int drawing_index) {
167 bke::CurvesGeometry &strokes = drawing.strokes_for_write();
168
171 OffsetIndices<int> points_by_curve = strokes.points_by_curve();
172
173 const Span<float2> screen_space_positions =
174 screen_positions_per_drawing_[drawing_index].as_span();
175
176 auto point_inside_stroke = [&](const Span<float2> points, const float2 mouse) {
177 std::optional<Bounds<float2>> bbox = bounds::min_max(points);
178 if (!bbox.has_value()) {
179 return false;
180 }
181 Bounds<float2> &box = bbox.value();
182 if (mouse.x < box.min.x || mouse.x > box.max.x || mouse.y < box.min.y || mouse.y > box.max.y)
183 {
184 return false;
185 }
186 return isect_point_poly_v2(
187 mouse, reinterpret_cast<const float(*)[2]>(points.data()), points.size());
188 };
189
190 threading::parallel_for(strokes.curves_range(), 128, [&](const IndexRange range) {
191 for (const int curve : range) {
192 bool stroke_touched = false;
193 for (const int curve_point : points_by_curve[curve].index_range()) {
194 if (tint_strokes) {
195 const int point = curve_point + points_by_curve[curve].first();
196 const float distance = math::distance(screen_space_positions[point], mouse_position);
197 const float influence = strength * BKE_brush_curve_strength(brush, distance, radius);
198 if (influence > 0.0f) {
199 stroke_touched = true;
200 /* Manually do an alpha-over mix, not using `ColorGeometry4f::premultiply_alpha`
201 * since the vertex color in GPv3 is stored as straight alpha (which is technically
202 * `ColorPaint4f`). */
203 float4 premultiplied;
204 straight_to_premul_v4_v4(premultiplied, vertex_colors[point]);
205 float4 rgba = float4(
206 math::interpolate(float3(premultiplied), float3(color_), influence),
207 vertex_colors[point][3]);
208 rgba[3] = rgba[3] * (1.0f - influence) + influence;
209 premul_to_straight_v4_v4(vertex_colors[point], rgba);
210 }
211 }
212 }
213 if (tint_fills && !fill_colors.is_empty()) {
214 /* Will tint fill color when either the brush being inside the fill region or touching
215 * the stroke. */
216 const bool fill_effective = stroke_touched ||
217 point_inside_stroke(screen_space_positions.slice(
218 points_by_curve[curve].first(),
219 points_by_curve[curve].size()),
220 mouse_position);
221 if (fill_effective) {
222 float4 premultiplied;
223 straight_to_premul_v4_v4(premultiplied, fill_colors[curve]);
224 float4 rgba = float4(
225 math::interpolate(float3(premultiplied), float3(color_), fill_strength),
226 fill_colors[curve][3]);
227 rgba[3] = rgba[3] * (1.0f - fill_strength) + fill_strength;
228 premul_to_straight_v4_v4(fill_colors[curve], rgba);
229 stroke_touched = true;
230 }
231 }
232 if (stroke_touched) {
233 changed.store(true, std::memory_order_relaxed);
234 }
235 }
236 });
237 };
238
239 threading::parallel_for_each(drawings_, [&](const MutableDrawingInfo &info) {
240 const int drawing_index = (&info - drawings_.data());
241 execute_tint_on_drawing(info.drawing, drawing_index);
242 });
243
244 if (changed) {
245 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
246 WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
247 }
248}
249
250void TintOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
251{
252 execute_tint(C, extension_sample);
253}
254
255void TintOperation::on_stroke_done(const bContext & /*C*/) {}
256
257std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation()
258{
259 return std::make_unique<TintOperation>();
260}
261
262} // namespace blender::ed::sculpt_paint::greasepencil
bool BKE_brush_use_alpha_pressure(const Brush *brush)
Definition brush.cc:1096
bool BKE_brush_use_size_pressure(const Brush *brush)
Definition brush.cc:1091
void BKE_brush_init_gpencil_settings(Brush *brush)
Definition brush.cc:563
const float * BKE_brush_color_get(const Scene *scene, const Paint *paint, const Brush *brush)
Definition brush.cc:1029
float BKE_curvemapping_evaluateF(const CurveMapping *cumap, int cur, float value)
void BKE_curvemapping_init(CurveMapping *cumap)
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
Low-level operations for curves.
Low-level operations for grease pencil.
General operations, lookup, etc. for materials.
Paint * BKE_paint_get_active_from_context(const bContext *C)
Definition paint.cc:477
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:649
#define BLI_assert(a)
Definition BLI_assert.h:50
void srgb_to_linearrgb_v3_v3(float linear[3], const float srgb[3])
bool isect_point_poly_v2(const float pt[2], const float verts[][2], unsigned int nr)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
Object * DEG_get_evaluated_object(const Depsgraph *depsgraph, Object *object)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
@ GP_BRUSH_ACTIVE_LAYER_ONLY
@ GPPAINT_MODE_STROKE
@ GPPAINT_MODE_FILL
@ GPPAINT_MODE_BOTH
@ V3D_PROJ_TEST_NOP
Definition ED_view3d.hh:266
eV3DProjStatus ED_view3d_project_float_global(const ARegion *region, const float co[3], float r_co[2], eV3DProjTest flag)
#define NC_GEOM
Definition WM_types.hh:360
#define ND_DATA
Definition WM_types.hh:475
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
IndexRange points_range() const
bke::CurvesGeometry & strokes_for_write()
MutableSpan< ColorGeometry4f > fill_colors_for_write()
MutableSpan< ColorGeometry4f > vertex_colors_for_write()
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
const Depsgraph * depsgraph
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, int layer_index, int frame)
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:46
Vector< MutableDrawingInfo > retrieve_editable_drawings_from_layer(const Scene &scene, GreasePencil &grease_pencil, const blender::bke::greasepencil::Layer &layer)
Vector< MutableDrawingInfo > retrieve_editable_drawings(const Scene &scene, GreasePencil &grease_pencil)
std::unique_ptr< GreasePencilStrokeOperation > new_tint_operation()
T clamp(const T &a, const T &min, const T &max)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:58
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
Definition BLI_color.hh:337
struct CurveMapping * curve_sensitivity
struct CurveMapping * curve_strength
float alpha
struct CurveMapping * curve
struct BrushGpencilSettings * gpencil_settings
void WM_event_add_notifier(const bContext *C, uint type, void *reference)