Blender V5.0
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_brush.hh"
6#include "BKE_colortools.hh"
7#include "BKE_context.hh"
8#include "BKE_curves.hh"
10#include "BKE_material.hh"
11#include "BKE_paint.hh"
12
13#include "BLI_bounds.hh"
14#include "BLI_math_color.h"
15#include "BLI_math_geom.h"
16
17#include "DNA_brush_types.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 TintOperation(bool temp_eraser = false) : temp_eraser_(temp_eraser) {};
37 void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
38 void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
39 void on_stroke_done(const bContext &C) override;
40
41 private:
42 float radius_;
43 float strength_;
44 bool temp_eraser_;
45 bool active_layer_only_;
46 ColorGeometry4f color_;
48 Array<Array<float2>> screen_positions_per_drawing_;
49
50 void execute_tint(const bContext &C, const InputSample &extension_sample);
51
52 void tint_strokes(const bke::CurvesGeometry &strokes,
53 const OffsetIndices<int> points_by_curve,
54 const Brush *brush,
55 const Span<float2> screen_space_positions,
56 const float2 mouse_position,
57 const float radius,
58 const float strength,
59 MutableSpan<ColorGeometry4f> vertex_colors,
60 MutableSpan<bool> touched_strokes);
61 void tint_fills(const bke::CurvesGeometry &strokes,
62 const OffsetIndices<int> points_by_curve,
63 const Span<float2> screen_space_positions,
64 const float2 mouse_position,
65 const float fill_strength,
67 MutableSpan<bool> touched_strokes);
68};
69
70void TintOperation::on_stroke_begin(const bContext &C, const InputSample & /*start_sample*/)
71{
72 using namespace blender::bke::greasepencil;
73 Scene *scene = CTX_data_scene(&C);
76
77 BKE_curvemapping_init(brush->gpencil_settings->curve_sensitivity);
78 BKE_curvemapping_init(brush->gpencil_settings->curve_strength);
79
80 if (brush->gpencil_settings == nullptr) {
82 }
83 BLI_assert(brush->gpencil_settings != nullptr);
84
85 BKE_curvemapping_init(brush->curve_distance_falloff);
86
87 radius_ = brush->size / 2.0f;
88 strength_ = brush->alpha;
89 active_layer_only_ = ((brush->gpencil_settings->flag & GP_BRUSH_ACTIVE_LAYER_ONLY) != 0);
90
91 float4 color_linear;
92 color_linear[3] = 1.0f;
94
95 color_ = ColorGeometry4f(color_linear);
96
98 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
99
100 if (active_layer_only_) {
101 /* Tint only on the drawings of the active layer. */
102 const Layer *active_layer = grease_pencil.get_active_layer();
103 if (!active_layer) {
104 return;
105 }
107 *scene, grease_pencil, *active_layer);
108 }
109 else {
110 /* Tint on all editable drawings. */
111 drawings_ = ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
112 }
113
114 if (drawings_.is_empty()) {
115 return;
116 }
117
118 ARegion *region = CTX_wm_region(&C);
120 Object *ob_eval = DEG_get_evaluated(depsgraph, obact);
121
122 screen_positions_per_drawing_.reinitialize(drawings_.size());
123
124 threading::parallel_for_each(drawings_, [&](const MutableDrawingInfo &drawing_info) {
125 const int drawing_index = (&drawing_info - drawings_.data());
126
127 bke::CurvesGeometry &strokes = drawing_info.drawing.strokes_for_write();
128 const Layer &layer = grease_pencil.layer(drawing_info.layer_index);
129
130 screen_positions_per_drawing_[drawing_index].reinitialize(strokes.points_num());
131
134 ob_eval, *obact, drawing_info.drawing);
135
136 for (const int point : strokes.points_range()) {
138 region,
139 math::transform_point(layer.to_world_space(*ob_eval), deformation.positions[point]),
140 screen_positions_per_drawing_[drawing_index][point],
142 }
143 });
144}
145
146void TintOperation::tint_strokes(const bke::CurvesGeometry &strokes,
147 const OffsetIndices<int> points_by_curve,
148 const Brush *brush,
149 const Span<float2> screen_space_positions,
150 const float2 mouse_position,
151 const float radius,
152 const float strength,
153 MutableSpan<ColorGeometry4f> vertex_colors,
154 MutableSpan<bool> touched_strokes)
155{
156 threading::parallel_for(strokes.curves_range(), 512, [&](const IndexRange curves) {
157 for (const int curve : curves) {
158 std::atomic<bool> changed = false;
159 threading::parallel_for(points_by_curve[curve], 4096, [&](const IndexRange points) {
160 for (const int point : points) {
161 const float distance = math::distance(screen_space_positions[point], mouse_position);
162 const float influence = strength * BKE_brush_curve_strength(brush, distance, radius);
163 if (influence <= 0.0f) {
164 continue;
165 }
166
167 if (temp_eraser_) {
168 float &alpha = vertex_colors[point][3];
169 alpha -= influence;
170 alpha = math::max(alpha, 0.0f);
171 }
172 else {
173 /* Manually do an alpha-over mix, not using `ColorGeometry4f::premultiply_alpha`
174 * since the vertex color is stored as straight alpha (which is technically
175 * `ColorPaint4f`). */
176 float4 premultiplied;
177 straight_to_premul_v4_v4(premultiplied, vertex_colors[point]);
178 float4 rgba = float4(
179 math::interpolate(float3(premultiplied), float3(color_), influence),
180 vertex_colors[point][3]);
181 rgba[3] = rgba[3] * (1.0f - influence) + influence;
182 premul_to_straight_v4_v4(vertex_colors[point], rgba);
183 }
184
185 changed.store(true, std::memory_order_relaxed);
186 }
187 });
188
189 touched_strokes[curve] = changed;
190 }
191 });
192}
193
194void TintOperation::tint_fills(const bke::CurvesGeometry &strokes,
195 const OffsetIndices<int> points_by_curve,
196 const Span<float2> screen_space_positions,
197 const float2 mouse_position,
198 const float fill_strength,
200 MutableSpan<bool> touched_strokes)
201{
202 auto point_inside_stroke = [](const Span<float2> points, const float2 mouse) {
203 std::optional<Bounds<float2>> bbox = bounds::min_max(points);
204 if (!bbox.has_value()) {
205 return false;
206 }
207 Bounds<float2> &box = bbox.value();
208 if (mouse.x < box.min.x || mouse.x > box.max.x || mouse.y < box.min.y || mouse.y > box.max.y) {
209 return false;
210 }
211 return isect_point_poly_v2(
212 mouse, reinterpret_cast<const float (*)[2]>(points.data()), points.size());
213 };
214
215 threading::parallel_for(strokes.curves_range(), 512, [&](const IndexRange curves) {
216 for (const int curve : curves) {
217 const IndexRange points = points_by_curve[curve];
218 const bool stroke_touched = touched_strokes[curve];
219 const bool fill_effective = stroke_touched ||
220 point_inside_stroke(screen_space_positions.slice(points),
221 mouse_position);
222 if (!fill_effective) {
223 continue;
224 }
225
226 if (temp_eraser_) {
227 float &alpha = fill_colors[curve][3];
228 alpha -= fill_strength;
229 alpha = math::max(alpha, 0.0f);
230 }
231 else {
232 float4 premultiplied;
233 straight_to_premul_v4_v4(premultiplied, fill_colors[curve]);
234 float4 rgba = float4(
235 math::interpolate(float3(premultiplied), float3(color_), fill_strength),
236 fill_colors[curve][3]);
237 rgba[3] = rgba[3] * (1.0f - fill_strength) + fill_strength;
238 premul_to_straight_v4_v4(fill_colors[curve], rgba);
239 }
240
241 touched_strokes[curve] = true;
242 }
243 });
244}
245
246void TintOperation::execute_tint(const bContext &C, const InputSample &extension_sample)
247{
248 if (drawings_.is_empty()) {
249 return;
250 }
251
252 using namespace blender::bke::greasepencil;
253 Scene *scene = CTX_data_scene(&C);
255
257 Brush *brush = BKE_paint_brush(paint);
258
259 /* Get the brush's data. */
260 const float2 mouse_position = extension_sample.mouse_position;
261 float radius = radius_;
262 float strength = strength_;
263 if (BKE_brush_use_size_pressure(brush)) {
265 brush->gpencil_settings->curve_sensitivity, 0, extension_sample.pressure);
266 }
267 if (BKE_brush_use_alpha_pressure(brush)) {
268 strength *= BKE_curvemapping_evaluateF(
269 brush->gpencil_settings->curve_strength, 0, extension_sample.pressure);
270 }
271 /* Attenuate factor to get a smoother tinting. */
272 float fill_strength = strength / 100.0f;
273
274 strength = math::clamp(strength, 0.0f, 1.0f);
275 fill_strength = math::clamp(fill_strength, 0.0f, 1.0f);
276
277 const bool tint_strokes = ELEM(
279 const bool tint_fills = ELEM(
281
282 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
283
284 std::atomic<bool> changed = false;
285 const auto execute_tint_on_drawing = [&](Drawing &drawing, const int drawing_index) {
286 bke::CurvesGeometry &strokes = drawing.strokes_for_write();
287
288 MutableSpan<ColorGeometry4f> vertex_colors = drawing.vertex_colors_for_write();
289 MutableSpan<ColorGeometry4f> fill_colors = drawing.fill_colors_for_write();
290 OffsetIndices<int> points_by_curve = strokes.points_by_curve();
291
292 const Span<float2> screen_space_positions =
293 screen_positions_per_drawing_[drawing_index].as_span();
294
295 Array<bool> touched_strokes(strokes.curves_num(), false);
296 if (tint_strokes) {
297 this->tint_strokes(strokes,
298 points_by_curve,
299 brush,
300 screen_space_positions,
301 mouse_position,
302 radius,
303 strength,
304 vertex_colors,
305 touched_strokes.as_mutable_span());
306 }
307
308 if (tint_fills && !fill_colors.is_empty()) {
309 this->tint_fills(strokes,
310 points_by_curve,
311 screen_space_positions,
312 mouse_position,
313 fill_strength,
314 fill_colors,
315 touched_strokes.as_mutable_span());
316 }
317
318 for (const bool touched : touched_strokes) {
319 if (touched) {
320 changed.store(true, std::memory_order_relaxed);
321 break;
322 }
323 }
324 };
325
326 threading::parallel_for_each(drawings_, [&](const MutableDrawingInfo &info) {
327 const int drawing_index = (&info - drawings_.data());
328 execute_tint_on_drawing(info.drawing, drawing_index);
329 });
330
331 if (changed) {
332 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
333 WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
334 }
335}
336
337void TintOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
338{
339 execute_tint(C, extension_sample);
340}
341
343
344std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation(bool temp_eraser)
345{
346 return std::make_unique<TintOperation>(temp_eraser);
347}
348
349} // namespace blender::ed::sculpt_paint::greasepencil
bool BKE_brush_use_alpha_pressure(const Brush *brush)
Definition brush.cc:1290
const float * BKE_brush_color_get(const Paint *paint, const Brush *brush)
Definition brush.cc:1161
bool BKE_brush_use_size_pressure(const Brush *brush)
Definition brush.cc:1285
void BKE_brush_init_gpencil_settings(Brush *brush)
Definition brush.cc:648
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:476
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:645
#define BLI_assert(a)
Definition BLI_assert.h:46
bool isect_point_poly_v2(const float pt[2], const float verts[][2], unsigned int nr)
MINLINE void copy_v3_v3(float r[3], const float a[3])
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
@ GP_BRUSH_ACTIVE_LAYER_ONLY
@ GPPAINT_MODE_STROKE
@ GPPAINT_MODE_FILL
@ GPPAINT_MODE_BOTH
struct Brush Brush
struct GreasePencil GreasePencil
struct Object Object
struct Scene Scene
struct Paint Paint
@ V3D_PROJ_TEST_NOP
Definition ED_view3d.hh:279
eV3DProjStatus ED_view3d_project_float_global(const ARegion *region, const float co[3], float r_co[2], eV3DProjTest flag)
#define C
Definition RandGen.cpp:29
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DATA
Definition WM_types.hh:509
BPy_StructRNA * depsgraph
bke::CurvesGeometry & strokes_for_write()
MutableSpan< ColorGeometry4f > fill_colors_for_write()
MutableSpan< ColorGeometry4f > vertex_colors_for_write()
constexpr bool is_empty() const
Definition BLI_span.hh:509
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr int64_t size() const
Definition BLI_span.hh:252
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
IndexRange points_range() const
bke::CurvesGeometry & strokes_for_write()
float4x4 to_world_space(const Object &object) const
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, const bke::greasepencil::Drawing &drawing_orig)
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:55
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(bool temp_eraser=false)
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:56
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:93
VecBase< float, 4 > float4
VecBase< float, 2 > float2
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
struct CurveMapping * curve_sensitivity
struct CurveMapping * curve_strength
struct BrushGpencilSettings * gpencil_settings
struct ToolSettings * toolsettings
void WM_event_add_notifier(const bContext *C, uint type, void *reference)