Blender V5.0
grease_pencil_fill.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_bounds.hh"
6#include "BLI_color.hh"
7#include "BLI_index_mask.hh"
8#include "BLI_math_base.hh"
9#include "BLI_math_matrix.hh"
10#include "BLI_math_vector.hh"
11#include "BLI_offset_indices.hh"
12#include "BLI_stack.hh"
13#include "BLI_task.hh"
14
15#include "BKE_attribute.hh"
16#include "BKE_camera.h"
17#include "BKE_context.hh"
18#include "BKE_crazyspace.hh"
19#include "BKE_curves.hh"
20#include "BKE_grease_pencil.hh"
21#include "BKE_image.hh"
22#include "BKE_lib_id.hh"
23#include "BKE_material.hh"
24#include "BKE_paint.hh"
25
26#include "DNA_brush_types.h"
27#include "DNA_curves_types.h"
29#include "DNA_material_types.h"
30#include "DNA_object_types.h"
31#include "DNA_scene_types.h"
32#include "DNA_view3d_types.h"
33
35
36#include "ED_grease_pencil.hh"
37#include "ED_view3d.hh"
38
39#include "IMB_imbuf.hh"
40#include "IMB_imbuf_types.hh"
41
42#include "GPU_state.hh"
43
45
46#include <list>
47#include <optional>
48
50
51/* -------------------------------------------------------------------- */
54
56const ColorGeometry4f draw_seed_color = {0, 1, 0, 1};
57
59 Border = (1 << 0),
60 Stroke = (1 << 1),
61 Fill = (1 << 2),
62 Seed = (1 << 3),
63 Debug = (1 << 7),
64};
66
67
68
69/* -------------------------------------------------------------------- */
72
73/* Utility class for access to pixel buffer data. */
75 private:
76 Image *ima_ = nullptr;
77 ImBuf *ibuf_ = nullptr;
78 void *lock_ = nullptr;
80 int2 size_ = int2(0);
81
82 public:
83 bool has_buffer() const
84 {
85 return ibuf_ != nullptr;
86 }
87
89 {
90 BLI_assert(!this->has_buffer());
91 }
92
93 void acquire(Image &ima)
94 {
95 BLI_assert(!this->has_buffer());
96 ima_ = &ima;
97 ibuf_ = BKE_image_acquire_ibuf(&ima, nullptr, &lock_);
98 size_ = {ibuf_->x, ibuf_->y};
100 reinterpret_cast<ColorGeometry4b *>(ibuf_->byte_buffer.data), ibuf_->x * ibuf_->y);
101 }
102
103 void release()
104 {
105 BLI_assert(this->has_buffer());
106 BKE_image_release_ibuf(ima_, ibuf_, lock_);
107 lock_ = nullptr;
108 ima_ = nullptr;
109 ibuf_ = nullptr;
110 data_ = {};
111 size_ = int2(0);
112 }
113
114 int2 size() const
115 {
116 return this->size_;
117 }
118
119 int width() const
120 {
121 return this->size_.x;
122 }
123
124 int height() const
125 {
126 return this->size_.y;
127 }
128
129 bool is_valid_coord(const int2 &c) const
130 {
131 return c.x >= 0 && c.x < this->size_.x && c.y >= 0 && c.y < this->size_.y;
132 }
133
134 int2 coord_from_index(const int index) const
135 {
136 const div_t d = div(index, this->size_.x);
137 return int2{d.rem, d.quot};
138 }
139
140 int index_from_coord(const int2 &c) const
141 {
142 return c.x + c.y * this->size_.x;
143 }
144
146 {
147 return this->data_;
148 }
149
151 {
152 return this->data_;
153 }
154
156 {
157 return this->data_[index_from_coord(c)];
158 }
159
160 const ColorGeometry4b &pixel_from_coord(const int2 &c) const
161 {
162 return this->data_[index_from_coord(c)];
163 }
164};
165
166static bool get_flag(const ColorGeometry4b &color, const ColorFlag flag)
167{
168 return (color.r & flag) != 0;
169}
170
171static void set_flag(ColorGeometry4b &color, const ColorFlag flag, bool value)
172{
173 color.r = value ? (color.r | flag) : (color.r & (~flag));
174}
175
176/* Set a border to create image limits. */
177/* TODO this shouldn't be necessary if drawing could accurately save flag values. */
179{
180 for (ColorGeometry4b &color : buffer.pixels()) {
181 const bool is_stroke = color.r > 0.0f;
182 const bool is_seed = color.g > 0.0f;
183 color.r = (is_stroke ? ColorFlag::Stroke : 0) | (is_seed ? ColorFlag::Seed : 0);
184 color.g = 0;
185 color.b = 0;
186 color.a = 0;
187 }
188}
189
190/* Set a border to create image limits. */
192{
193 constexpr const ColorGeometry4b output_stroke_color = {255, 0, 0, 255};
194 constexpr const ColorGeometry4b output_seed_color = {127, 127, 0, 255};
195 constexpr const ColorGeometry4b output_border_color = {0, 0, 255, 255};
196 constexpr const ColorGeometry4b output_fill_color = {127, 255, 0, 255};
197 // constexpr const ColorGeometry4b output_extend_color = {25, 255, 0, 255};
198 // constexpr const ColorGeometry4b output_helper_color = {255, 0, 127, 255};
199 constexpr const ColorGeometry4b output_debug_color = {255, 127, 0, 255};
200
201 auto add_colors = [](const ColorGeometry4b &a, const ColorGeometry4b &b) -> ColorGeometry4b {
202 return ColorGeometry4b(std::min(int(a.r) + int(b.r), 255),
203 std::min(int(a.g) + int(b.g), 255),
204 std::min(int(a.b) + int(b.b), 255),
205 std::min(int(a.a) + int(b.a), 255));
206 };
207
208 for (ColorGeometry4b &color : buffer.pixels()) {
209 ColorGeometry4b output_color = ColorGeometry4b(0, 0, 0, 0);
210 if (color.r & ColorFlag::Debug) {
211 output_color = add_colors(output_color, output_debug_color);
212 }
213 if (color.r & ColorFlag::Fill) {
214 output_color = add_colors(output_color, output_fill_color);
215 }
216 if (color.r & ColorFlag::Stroke) {
217 output_color = add_colors(output_color, output_stroke_color);
218 }
219 if (color.r & ColorFlag::Border) {
220 output_color = add_colors(output_color, output_border_color);
221 }
222 if (color.r & ColorFlag::Seed) {
223 output_color = add_colors(output_color, output_seed_color);
224 }
225 color = std::move(output_color);
226 }
227}
228
229/* Set a border to create image limits. */
231{
232 int row_start = 0;
233 /* Fill first row */
234 for (const int i : IndexRange(buffer.width())) {
235 set_flag(buffer.pixels()[row_start + i], ColorFlag::Border, true);
236 }
237 row_start += buffer.width();
238 /* Fill first and last pixel of middle rows. */
239 for ([[maybe_unused]] const int i : IndexRange(buffer.height()).drop_front(1).drop_back(1)) {
240 set_flag(buffer.pixels()[row_start], ColorFlag::Border, true);
241 set_flag(buffer.pixels()[row_start + buffer.width() - 1], ColorFlag::Border, true);
242 row_start += buffer.width();
243 }
244 /* Fill last row */
245 for (const int i : IndexRange(buffer.width())) {
246 set_flag(buffer.pixels()[row_start + i], ColorFlag::Border, true);
247 }
248}
249
254
256 /* Cancel when hitting the border, fill failed. */
258 /* Allow border contact, continue with other pixels. */
260};
261
262template<FillBorderMode border_mode>
263FillResult flood_fill(ImageBufferAccessor &buffer, const int leak_filter_width = 0)
264{
265 const MutableSpan<ColorGeometry4b> pixels = buffer.pixels();
266 const int width = buffer.width();
267 const int height = buffer.height();
268
269 blender::Stack<int> active_pixels;
270 /* Initialize the stack with filled pixels (dot at mouse position). */
271 for (const int i : pixels.index_range()) {
272 if (get_flag(pixels[i], ColorFlag::Seed)) {
273 active_pixels.push(i);
274 }
275 }
276
277 enum FilterDirection {
278 Horizontal = 1,
279 Vertical = 2,
280 };
281
282 bool border_contact = false;
283 while (!active_pixels.is_empty()) {
284 const int index = active_pixels.pop();
285 const int2 coord = buffer.coord_from_index(index);
286 ColorGeometry4b pixel_value = buffer.pixels()[index];
287
288 if constexpr (border_mode == FillBorderMode::Cancel) {
289 if (get_flag(pixel_value, ColorFlag::Border)) {
290 border_contact = true;
291 break;
292 }
293 }
294 else if constexpr (border_mode == FillBorderMode::Ignore) {
295 if (get_flag(pixel_value, ColorFlag::Border)) {
296 border_contact = true;
297 }
298 }
299
300 if (get_flag(pixel_value, ColorFlag::Fill)) {
301 /* Pixel already filled. */
302 continue;
303 }
304
305 if (get_flag(pixel_value, ColorFlag::Stroke)) {
306 /* Boundary pixel, ignore. */
307 continue;
308 }
309
310 /* Mark as filled. */
311 set_flag(pixels[index], ColorFlag::Fill, true);
312
313 /* Directional box filtering for gap detection. */
314 const IndexRange filter_x_neg = IndexRange(1, std::min(coord.x, leak_filter_width));
315 const IndexRange filter_x_pos = IndexRange(1,
316 std::min(width - 1 - coord.x, leak_filter_width));
317 const IndexRange filter_y_neg = IndexRange(1, std::min(coord.y, leak_filter_width));
318 const IndexRange filter_y_pos = IndexRange(1,
319 std::min(height - 1 - coord.y, leak_filter_width));
320 bool is_boundary_horizontal = false;
321 bool is_boundary_vertical = false;
322 for (const int filter_i : filter_y_neg) {
323 is_boundary_horizontal |= get_flag(buffer.pixel_from_coord(coord - int2(0, filter_i)),
325 }
326 for (const int filter_i : filter_y_pos) {
327 is_boundary_horizontal |= get_flag(buffer.pixel_from_coord(coord + int2(0, filter_i)),
329 }
330 for (const int filter_i : filter_x_neg) {
331 is_boundary_vertical |= get_flag(buffer.pixel_from_coord(coord - int2(filter_i, 0)),
333 }
334 for (const int filter_i : filter_x_pos) {
335 is_boundary_vertical |= get_flag(buffer.pixel_from_coord(coord + int2(filter_i, 0)),
337 }
338
339 /* Activate neighbors */
340 if (coord.x > 0 && !is_boundary_horizontal) {
341 active_pixels.push(buffer.index_from_coord(coord - int2{1, 0}));
342 }
343 if (coord.x < width - 1 && !is_boundary_horizontal) {
344 active_pixels.push(buffer.index_from_coord(coord + int2{1, 0}));
345 }
346 if (coord.y > 0 && !is_boundary_vertical) {
347 active_pixels.push(buffer.index_from_coord(coord - int2{0, 1}));
348 }
349 if (coord.y < height - 1 && !is_boundary_vertical) {
350 active_pixels.push(buffer.index_from_coord(coord + int2{0, 1}));
351 }
352 }
353
354 return border_contact ? FillResult::BorderContact : FillResult::Success;
355}
356
357/* Turn unfilled areas into filled and vice versa. */
359{
360 for (ColorGeometry4b &color : buffer.pixels()) {
361 const bool is_filled = get_flag(color, ColorFlag::Fill);
362 set_flag(color, ColorFlag::Fill, !is_filled);
363 }
364}
365
366constexpr const int num_directions = 8;
368 {-1, -1},
369 {0, -1},
370 {1, -1},
371 {1, 0},
372 {1, 1},
373 {0, 1},
374 {-1, 1},
375 {-1, 0},
376};
377
378static void dilate(ImageBufferAccessor &buffer, int iterations = 1)
379{
380 const MutableSpan<ColorGeometry4b> pixels = buffer.pixels();
381
382 blender::Stack<int> active_pixels;
383 for ([[maybe_unused]] const int iter : IndexRange(iterations)) {
384 for (const int i : pixels.index_range()) {
385 /* Ignore already filled pixels */
386 if (get_flag(pixels[i], ColorFlag::Fill)) {
387 continue;
388 }
389 const int2 coord = buffer.coord_from_index(i);
390
391 /* Add to stack if any neighbor is filled. */
392 for (const int2 offset : offset_by_direction) {
393 if (buffer.is_valid_coord(coord + offset) &&
394 get_flag(buffer.pixel_from_coord(coord + offset), ColorFlag::Fill))
395 {
396 active_pixels.push(i);
397 }
398 }
399 }
400
401 while (!active_pixels.is_empty()) {
402 const int index = active_pixels.pop();
403 set_flag(buffer.pixels()[index], ColorFlag::Fill, true);
404 }
405 }
406}
407
408static void erode(ImageBufferAccessor &buffer, int iterations = 1)
409{
410 const MutableSpan<ColorGeometry4b> pixels = buffer.pixels();
411
412 blender::Stack<int> active_pixels;
413 for ([[maybe_unused]] const int iter : IndexRange(iterations)) {
414 for (const int i : pixels.index_range()) {
415 /* Ignore empty pixels */
416 if (!get_flag(pixels[i], ColorFlag::Fill)) {
417 continue;
418 }
419 const int2 coord = buffer.coord_from_index(i);
420
421 /* Add to stack if any neighbor is empty. */
422 for (const int2 offset : offset_by_direction) {
423 if (buffer.is_valid_coord(coord + offset) &&
424 !get_flag(buffer.pixel_from_coord(coord + offset), ColorFlag::Fill))
425 {
426 active_pixels.push(i);
427 }
428 }
429 }
430
431 while (!active_pixels.is_empty()) {
432 const int index = active_pixels.pop();
433 set_flag(buffer.pixels()[index], ColorFlag::Fill, false);
434 }
435 }
436}
437
438/* Wrap to valid direction, must be less than 3 * num_directions. */
439static int wrap_dir_3n(const int dir)
440{
441 return dir - num_directions * (int(dir >= num_directions) + int(dir >= 2 * num_directions));
442}
443
445 /* Pixel indices making up boundary curves. */
447 /* Offset index for each curve. */
449};
450
451/* Get the outline points of a shape using Moore Neighborhood algorithm
452 *
453 * This is a Blender customized version of the general algorithm described
454 * in https://en.wikipedia.org/wiki/Moore_neighborhood
455 */
456static FillBoundary build_fill_boundary(const ImageBufferAccessor &buffer, bool include_holes)
457{
458 using BoundarySection = std::list<int>;
459 using BoundaryStartMap = Map<int, BoundarySection>;
460
461 const Span<ColorGeometry4b> pixels = buffer.pixels();
462 const int width = buffer.width();
463 const int height = buffer.height();
464
465 /* Find possible starting points for boundary sections.
466 * Direction 3 == (1, 0) is the starting direction. */
467 constexpr const uint8_t start_direction = 3;
468 auto find_start_coordinates = [&]() -> BoundaryStartMap {
469 BoundaryStartMap starts;
470 for (const int y : IndexRange(height)) {
471 /* Check for empty pixels next to filled pixels. */
472 for (const int x : IndexRange(width).drop_back(1)) {
473 const int index_left = buffer.index_from_coord({x, y});
474 const int index_right = buffer.index_from_coord({x + 1, y});
475 const bool filled_left = get_flag(pixels[index_left], ColorFlag::Fill);
476 const bool filled_right = get_flag(pixels[index_right], ColorFlag::Fill);
477 const bool border_right = get_flag(pixels[index_right], ColorFlag::Border);
478 if (!filled_left && filled_right && !border_right) {
479 /* Empty index list indicates uninitialized section. */
480 starts.add(index_right, {});
481 /* First filled pixel on the line is in the outer boundary.
482 * Pixels further to the right are part of holes and can be disregarded. */
483 if (!include_holes) {
484 break;
485 }
486 }
487 }
488 }
489 return starts;
490 };
491
492 struct NeighborIterator {
493 int index;
494 int direction;
495 };
496
497 /* Find the next filled pixel in clockwise direction from the current. */
498 auto find_next_neighbor = [&](NeighborIterator &iter) -> bool {
499 const int2 iter_coord = buffer.coord_from_index(iter.index);
500 for (const int i : IndexRange(num_directions)) {
501 /* Invert direction (add 4) and start at next direction (add 1..n).
502 * This can not be greater than 3*num_directions-1, wrap accordingly. */
503 const int neighbor_dir = wrap_dir_3n(iter.direction + 5 + i);
504 const int2 neighbor_coord = iter_coord + offset_by_direction[neighbor_dir];
505 if (!buffer.is_valid_coord(neighbor_coord)) {
506 continue;
507 }
508 const int neighbor_index = buffer.index_from_coord(neighbor_coord);
509 /* Border pixels are not valid. */
510 if (get_flag(pixels[neighbor_index], ColorFlag::Border)) {
511 continue;
512 }
513 if (get_flag(pixels[neighbor_index], ColorFlag::Fill)) {
514 iter.index = neighbor_index;
515 iter.direction = neighbor_dir;
516 return true;
517 }
518 }
519 return false;
520 };
521
522 BoundaryStartMap boundary_starts = find_start_coordinates();
523
524 /* Find directions and connectivity for all boundary pixels. */
525 for (const int start_index : boundary_starts.keys()) {
526 /* Boundary map entries may get removed, only handle active starts. */
527 if (!boundary_starts.contains(start_index)) {
528 continue;
529 }
530 BoundarySection &section = boundary_starts.lookup(start_index);
531 section.push_back(start_index);
532 NeighborIterator iter = {start_index, start_direction};
533 while (find_next_neighbor(iter)) {
534 /* Loop closed when arriving at start again. */
535 if (iter.index == start_index) {
536 break;
537 }
538
539 /* Join existing sections. */
540 if (boundary_starts.contains(iter.index)) {
541 BoundarySection &next_section = boundary_starts.lookup(iter.index);
542 if (next_section.empty()) {
543 /* Empty sections are only start indices, remove and continue. */
544 boundary_starts.remove(iter.index);
545 }
546 else {
547 /* Merge existing points into the current section. */
548 section.splice(section.end(), next_section);
549 boundary_starts.remove(iter.index);
550 break;
551 }
552 }
553
554 section.push_back(iter.index);
555 }
556 /* Discard un-closed boundaries. */
557 if (iter.index != start_index) {
558 boundary_starts.remove(start_index);
559 }
560 }
561
562 /* Construct final strokes by tracing the boundary. */
563 FillBoundary final_boundary;
564 for (const BoundarySection &section : boundary_starts.values()) {
565 final_boundary.offset_indices.append(final_boundary.pixels.size());
566 for (const int index : section) {
567 final_boundary.pixels.append(index);
568 }
569 }
570 final_boundary.offset_indices.append(final_boundary.pixels.size());
571
572 return final_boundary;
573}
574
575/* Create curves geometry from boundary positions. */
577 const ViewContext &view_context,
578 const Brush &brush,
579 const FillBoundary &boundary,
580 const ImageBufferAccessor &buffer,
581 const ed::greasepencil::DrawingPlacement &placement,
582 const float3x3 &image_to_region,
583 const int material_index,
584 const float hardness)
585{
586 /* Curve cannot have 0 points. */
587 if (boundary.offset_indices.is_empty() || boundary.pixels.is_empty()) {
588 return {};
589 }
590
591 bke::CurvesGeometry curves(boundary.pixels.size(), boundary.offset_indices.size() - 1);
592
593 curves.offsets_for_write().copy_from(boundary.offset_indices);
594 MutableSpan<float3> positions = curves.positions_for_write();
595 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
596 /* Attributes that are defined explicitly and should not be set to default values. */
597 Set<std::string> skip_curve_attributes = {
598 "curve_type", "material_index", "cyclic", "hardness", "fill_opacity"};
599 Set<std::string> skip_point_attributes = {"position", "radius", "opacity"};
600
601 curves.curve_types_for_write().fill(CURVE_TYPE_POLY);
602 curves.update_curve_types();
603
604 /* Note: We can assume that the writers here will be valid since we created new curves. */
606 "material_index", bke::AttrDomain::Curve);
608 "cyclic", bke::AttrDomain::Curve);
610 "hardness",
613 bke::SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
614 "fill_opacity",
618 "radius",
622 "opacity",
625
626 cyclic.span.fill(true);
627 materials.span.fill(material_index);
628 hardnesses.span.fill(hardness);
629 /* TODO: `fill_opacities` are currently always 1.0f for the new strokes. Maybe this should be a
630 * parameter. */
631
632 cyclic.finish();
633 materials.finish();
634 hardnesses.finish();
635 fill_opacities.finish();
636
637 for (const int point_i : curves.points_range()) {
638 const int pixel_index = boundary.pixels[point_i];
639 const int2 pixel_coord = buffer.coord_from_index(pixel_index);
640 const float2 region_coord =
641 math::transform_point(image_to_region, float3(pixel_coord, 1.0f)).xy();
642 const float3 position = placement.project_with_shift(region_coord);
643 positions[point_i] = position;
644
645 /* Calculate radius and opacity for the outline as if it was a user stroke with full pressure.
646 */
647 constexpr const float pressure = 1.0f;
648 radii.span[point_i] = ed::greasepencil::radius_from_input_sample(view_context.rv3d,
649 view_context.region,
650 &brush,
651 pressure,
652 position,
653 placement.to_world_space(),
654 brush.gpencil_settings);
655 opacities.span[point_i] = ed::greasepencil::opacity_from_input_sample(
656 pressure, &brush, brush.gpencil_settings);
657 }
658
660 scene.toolsettings->gp_paint, &brush);
661 if (use_vertex_color) {
662 ColorGeometry4f vertex_color;
663 copy_v3_v3(vertex_color, brush.color);
664 vertex_color.a = brush.gpencil_settings->vertex_factor;
665
667 skip_curve_attributes.add("fill_color");
669 attributes.lookup_or_add_for_write_span<ColorGeometry4f>("fill_color",
671 fill_colors.span.fill(vertex_color);
672 fill_colors.finish();
673 }
675 skip_point_attributes.add("vertex_color");
677 attributes.lookup_or_add_for_write_span<ColorGeometry4f>("vertex_color",
679 vertex_colors.span.fill(vertex_color);
680 vertex_colors.finish();
681 }
682 }
683
684 radii.finish();
685 opacities.finish();
686
687 /* Initialize the rest of the attributes with default values. */
690 bke::attribute_filter_from_skip_ref(skip_curve_attributes),
691 curves.curves_range());
694 bke::attribute_filter_from_skip_ref(skip_point_attributes),
695 curves.points_range());
696
697 return curves;
698}
699
701 const Scene &scene,
702 const ViewContext &view_context,
703 const Brush &brush,
704 const ed::greasepencil::DrawingPlacement &placement,
705 const float3x3 &image_to_region,
706 const int stroke_material_index,
707 const float stroke_hardness,
708 const bool invert,
709 const bool output_as_colors)
710{
711 constexpr const int leak_filter_width = 3;
712
713 ImageBufferAccessor buffer;
714 buffer.acquire(ima);
715 BLI_SCOPED_DEFER([&]() {
716 if (output_as_colors) {
717 /* For visual output convert bit flags back to colors. */
719 }
720 buffer.release();
721 });
722
724
725 /* Set red borders to create a external limit. */
726 mark_borders(buffer);
727
728 /* Apply boundary fill */
729 if (invert) {
730 /* When inverted accept border fill, image borders are valid boundaries. */
731 FillResult fill_result = flood_fill<FillBorderMode::Ignore>(buffer, leak_filter_width);
733 return {};
734 }
735 /* Make fills into boundaries and vice versa for finding exterior boundaries. */
736 invert_fill(buffer);
737 }
738 else {
739 /* Cancel when encountering a border, counts as failure. */
740 FillResult fill_result = flood_fill<FillBorderMode::Cancel>(buffer, leak_filter_width);
741 if (fill_result != FillResult::Success) {
742 return {};
743 }
744 }
745
746 const int dilate_pixels = brush.gpencil_settings->dilate_pixels;
747 if (dilate_pixels > 0) {
748 dilate(buffer, dilate_pixels);
749 }
750 else if (dilate_pixels < 0) {
751 erode(buffer, -dilate_pixels);
752 }
753
754 /* In regular mode create only the outline of the filled area.
755 * In inverted mode create a boundary for every filled area. */
756 const bool fill_holes = invert;
757 const FillBoundary boundary = build_fill_boundary(buffer, fill_holes);
758
759 return boundary_to_curves(scene,
760 view_context,
761 brush,
762 boundary,
763 buffer,
764 placement,
765 image_to_region,
766 stroke_material_index,
767 stroke_hardness);
768}
769
771
772constexpr const char *attr_material_index = "material_index";
773constexpr const char *attr_is_fill_guide = ".is_fill_guide";
774
776 const DrawingInfo &info,
777 const bool is_boundary_layer,
778 IndexMaskMemory &memory)
779{
780 const bke::CurvesGeometry &strokes = info.drawing.strokes();
781 const bke::AttributeAccessor attributes = strokes.attributes();
782 const VArray<int> materials = *attributes.lookup_or_default<int>(
784
785 auto is_visible_curve = [&](const int curve_i) {
786 /* Check if stroke can be drawn. */
787 const IndexRange points = strokes.points_by_curve()[curve_i];
788 if (points.size() < 2) {
789 return false;
790 }
791
792 /* Check if the material is visible. */
793 const Material *material = BKE_object_material_get(const_cast<Object *>(&object),
794 materials[curve_i] + 1);
795 const MaterialGPencilStyle *gp_style = material ? material->gp_style : nullptr;
796 const bool is_hidden_material = (gp_style->flag & GP_MATERIAL_HIDE);
797 if (gp_style == nullptr || is_hidden_material) {
798 return false;
799 }
800
801 return true;
802 };
803
804 /* On boundary layers only boundary strokes are rendered. */
805 if (is_boundary_layer) {
806 const VArray<bool> fill_guides = *attributes.lookup_or_default<bool>(
808
810 strokes.curves_range(), GrainSize(512), memory, [&](const int curve_i) {
811 if (!is_visible_curve(curve_i)) {
812 return false;
813 }
814 const bool is_boundary_stroke = fill_guides[curve_i];
815 return is_boundary_stroke;
816 });
817 }
818
820 strokes.curves_range(), GrainSize(512), memory, is_visible_curve);
821}
822
825 const VArray<float> &opacities,
826 const VArray<int> materials,
827 const ColorGeometry4f &tint_color,
828 const std::optional<float> alpha_threshold)
829{
830 if (!alpha_threshold) {
831 return VArray<ColorGeometry4f>::from_single(tint_color, curves.points_num());
832 }
833
834 Array<ColorGeometry4f> colors(curves.points_num());
835 threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) {
836 for (const int curve_i : range) {
837 const Material *material = BKE_object_material_get(const_cast<Object *>(&object),
838 materials[curve_i] + 1);
839 const float material_alpha = material && material->gp_style ?
840 material->gp_style->stroke_rgba[3] :
841 1.0f;
842 const IndexRange points = curves.points_by_curve()[curve_i];
843 for (const int point_i : points) {
844 const float alpha = (material_alpha * opacities[point_i] > *alpha_threshold ? 1.0f : 0.0f);
845 colors[point_i] = ColorGeometry4f(tint_color.r, tint_color.g, tint_color.b, alpha);
846 }
847 }
848 });
850}
851
853{
854 /* Initialize maximum bound-box size. */
855 return {float2(0), float2(region.winx, region.winy)};
856}
857
858/* Helper: Calc the maximum bounding box size of strokes to get the zoom level of the viewport.
859 * For each stroke, the 2D projected bounding box is calculated and using this data, the total
860 * object bounding box (all strokes) is calculated. */
861static std::optional<Bounds<float2>> get_boundary_bounds(const ARegion &region,
862 const RegionView3D &rv3d,
863 const Object &object,
864 const Object &object_eval,
865 const VArray<bool> &boundary_layers,
866 const Span<DrawingInfo> src_drawings)
867{
870
871 std::optional<Bounds<float2>> boundary_bounds;
872
873 BLI_assert(object.type == OB_GREASE_PENCIL);
874 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
875
876 BLI_assert(grease_pencil.has_active_layer());
877
878 for (const DrawingInfo &info : src_drawings) {
879 const Layer &layer = *grease_pencil.layers()[info.layer_index];
880 const float4x4 layer_to_world = layer.to_world_space(object);
881 const bke::crazyspace::GeometryDeformation deformation =
883 &object_eval, object, info.drawing);
884 const bool only_boundary_strokes = boundary_layers[info.layer_index];
885 const VArray<float> radii = info.drawing.radii();
886 const bke::CurvesGeometry &strokes = info.drawing.strokes();
887 const bke::AttributeAccessor attributes = strokes.attributes();
888 const VArray<int> materials = *attributes.lookup_or_default<int>(
890 const VArray<bool> is_boundary_stroke = *attributes.lookup_or_default<bool>(
892
893 IndexMaskMemory curve_mask_memory;
894 const IndexMask curve_mask = get_visible_boundary_strokes(
895 object, info, only_boundary_strokes, curve_mask_memory);
896
897 curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
898 const IndexRange points = strokes.points_by_curve()[curve_i];
899 /* Check if stroke can be drawn. */
900 if (points.size() < 2) {
901 return;
902 }
903 /* Check if the color is visible. */
904 const int material_index = materials[curve_i];
905 Material *mat = BKE_object_material_get(const_cast<Object *>(&object), material_index + 1);
906 if (mat == nullptr || (mat->gp_style->flag & GP_MATERIAL_HIDE)) {
907 return;
908 }
909
910 /* In boundary layers only boundary strokes should be rendered. */
911 if (only_boundary_strokes && !is_boundary_stroke[curve_i]) {
912 return;
913 }
914
915 for (const int point_i : points) {
916 const float3 pos_world = math::transform_point(layer_to_world,
917 deformation.positions[point_i]);
918 float2 pos_view;
920 &region, pos_world, pos_view, V3D_PROJ_TEST_NOP);
921 if (result == V3D_PROJ_RET_OK) {
922 const float pixels = radii[point_i] / ED_view3d_pixel_size(&rv3d, pos_world);
923 Bounds<float2> point_bounds = {pos_view - float2(pixels), pos_view + float2(pixels)};
924 boundary_bounds = bounds::merge(boundary_bounds, {point_bounds});
925 }
926 }
927 });
928 }
929
930 return boundary_bounds;
931}
932
933static auto fit_strokes_to_view(const ViewContext &view_context,
934 const VArray<bool> &boundary_layers,
935 const Span<DrawingInfo> src_drawings,
936 const FillToolFitMethod fit_method,
937 const float2 fill_point,
938 const bool uniform_zoom,
939 const float max_zoom_factor,
940 const float2 margin,
941 const float pixel_scale)
942{
943 BLI_assert(max_zoom_factor >= 1.0f);
944 const float min_zoom_factor = math::safe_rcp(max_zoom_factor);
945 /* These values are copied from GPv2. */
946 const int2 min_image_size = int2(128, 128);
947
948 switch (fit_method) {
950 return std::make_tuple(float2(1.0f), float2(0.0f), min_image_size, float3x3::identity());
951
953 const Object &object_eval = *DEG_get_evaluated(view_context.depsgraph, view_context.obact);
954 /* Zoom and offset based on bounds, to fit all strokes within the render. */
955 const std::optional<Bounds<float2>> boundary_bounds = get_boundary_bounds(
956 *view_context.region,
957 *view_context.rv3d,
958 *view_context.obact,
959 object_eval,
960 boundary_layers,
961 src_drawings);
962 if (!boundary_bounds) {
963 return std::make_tuple(float2(1.0f), float2(0.0f), min_image_size, float3x3::identity());
964 }
965
966 /* Include fill point for computing zoom. */
967 const Bounds<float2> fill_bounds = [&]() {
968 Bounds<float2> result = bounds::merge(*boundary_bounds, Bounds<float2>(fill_point));
969 result.pad(margin);
970 return result;
971 }();
972
973 const Bounds<float2> region_bounds = get_region_bounds(*view_context.region);
974 const int2 image_size = math::max(int2(region_bounds.size() * pixel_scale), min_image_size);
975 const float2 zoom_factors = math::clamp(
976 math::safe_divide(fill_bounds.size(), region_bounds.size()),
977 float2(min_zoom_factor),
978 float2(max_zoom_factor));
979 /* Use the most zoomed out factor for uniform scale. */
980 const float2 zoom = uniform_zoom ? float2(math::reduce_max(zoom_factors)) : zoom_factors;
981
982 /* Actual rendered bounds based on the final zoom factor. */
983 const Bounds<float2> render_bounds = {
984 fill_bounds.center() - 0.5f * region_bounds.size() * zoom.x,
985 fill_bounds.center() + 0.5f * region_bounds.size() * zoom.y};
986
987 /* Center offset for View3d matrices (strokes to pixels). */
988 const float2 offset = math::safe_divide(render_bounds.center() - region_bounds.center(),
989 region_bounds.size());
990 /* Corner offset for boundary transform (pixels to strokes). */
991 const float3x3 image_to_region = math::from_loc_scale<float3x3>(
992 render_bounds.min - region_bounds.min, zoom * math::safe_rcp(pixel_scale));
993
994 return std::make_tuple(zoom, offset, image_size, image_to_region);
995 }
996 }
997
998 return std::make_tuple(float2(1.0f), float2(0.0f), min_image_size, float3x3::identity());
999}
1000
1001static Image *render_strokes(const ViewContext &view_context,
1002 const Brush &brush,
1003 const Scene &scene,
1004 const bke::greasepencil::Layer &layer,
1005 const VArray<bool> &boundary_layers,
1006 const Span<DrawingInfo> src_drawings,
1007 const int2 &image_size,
1008 const std::optional<float> alpha_threshold,
1009 const float2 &fill_point,
1010 const ExtensionData &extensions,
1011 const ed::greasepencil::DrawingPlacement &placement,
1012 const float2 &zoom,
1013 const float2 &offset)
1014{
1016
1017 ARegion &region = *view_context.region;
1018 RegionView3D &rv3d = *view_context.rv3d;
1019 Object &object = *view_context.obact;
1020
1021 BLI_assert(object.type == OB_GREASE_PENCIL);
1022 GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
1023
1024 /* Scale stroke radius by half to hide gaps between filled areas and boundaries. */
1025 const float radius_scale = (brush.gpencil_settings->fill_draw_mode == GP_FILL_DMODE_CONTROL) ?
1026 0.0f :
1027 0.5f;
1028
1029 /* Transform mouse coordinates into layer space for rendering alongside strokes. */
1030 const float3 fill_point_layer = placement.project(fill_point);
1031
1032 /* Region size is used for DrawingPlacement projection. */
1033 image_render::RegionViewData region_view_data = image_render::region_init(region, image_size);
1034 /* Make sure the region is reset on exit. */
1035 BLI_SCOPED_DEFER([&]() { image_render::region_reset(region, region_view_data); });
1036
1037 GPUOffScreen *offscreen_buffer = image_render::image_render_begin(image_size);
1038 if (offscreen_buffer == nullptr) {
1039 return {};
1040 }
1041
1042 const bool use_xray = false;
1043
1044 const float4x4 layer_to_world = layer.to_world_space(object);
1045 const float4x4 world_to_view = float4x4(rv3d.viewmat);
1046 const float4x4 layer_to_view = world_to_view * layer_to_world;
1047
1049 GPU_depth_mask(true);
1050 image_render::compute_view_matrices(view_context, scene, image_size, zoom, offset);
1052
1053 /* Draw blue point where click with mouse. */
1054 const float mouse_dot_size = 4.0f;
1055 image_render::draw_dot(layer_to_view, fill_point_layer, mouse_dot_size, draw_seed_color);
1056
1057 for (const DrawingInfo &info : src_drawings) {
1058 const Layer &layer = *grease_pencil.layers()[info.layer_index];
1059 if (!layer.is_visible()) {
1060 continue;
1061 }
1062 const float4x4 layer_to_world = layer.to_world_space(object);
1063 const bool is_boundary_layer = boundary_layers[info.layer_index];
1064 const bke::CurvesGeometry &strokes = info.drawing.strokes();
1065 const bke::AttributeAccessor attributes = strokes.attributes();
1066 const VArray<float> opacities = info.drawing.opacities();
1067 const VArray<int> materials = *attributes.lookup_or_default<int>(
1069
1070 IndexMaskMemory curve_mask_memory;
1071 const IndexMask curve_mask = get_visible_boundary_strokes(
1072 object, info, is_boundary_layer, curve_mask_memory);
1073
1074 const VArray<ColorGeometry4f> stroke_colors = get_stroke_colors(object,
1075 info.drawing.strokes(),
1076 opacities,
1077 materials,
1079 alpha_threshold);
1080
1082 image_size,
1083 object,
1084 info.drawing,
1085 layer_to_world,
1086 curve_mask,
1087 stroke_colors,
1088 use_xray,
1089 radius_scale);
1090
1091 /* Note: extension data is already in world space, only apply world-to-view transform here. */
1092
1093 const IndexRange lines_range = extensions.lines.starts.index_range();
1094 if (!lines_range.is_empty()) {
1096 draw_boundary_color, lines_range.size());
1097 const float line_width = 1.0f;
1098
1099 image_render::draw_lines(world_to_view,
1100 lines_range,
1101 extensions.lines.starts,
1102 extensions.lines.ends,
1103 line_colors,
1104 line_width);
1105 }
1106 }
1107
1109 GPU_depth_mask(false);
1111
1112 return image_render::image_render_end(*view_context.bmain, offscreen_buffer);
1113}
1114
1116 const Brush &brush,
1117 const Scene &scene,
1118 const bke::greasepencil::Layer &layer,
1119 const VArray<bool> &boundary_layers,
1120 const Span<DrawingInfo> src_drawings,
1121 const bool invert,
1122 const std::optional<float> alpha_threshold,
1123 const float2 &fill_point,
1124 const ExtensionData &extensions,
1125 const FillToolFitMethod fit_method,
1126 const int stroke_material_index,
1127 const bool keep_images)
1128{
1129 ARegion &region = *view_context.region;
1130 View3D &view3d = *view_context.v3d;
1131 Depsgraph &depsgraph = *view_context.depsgraph;
1132 Object &object = *view_context.obact;
1133
1134 BLI_assert(object.type == OB_GREASE_PENCIL);
1135 const Object &object_eval = *DEG_get_evaluated(&depsgraph, &object);
1136
1137 /* Zoom and offset based on bounds, to fit all strokes within the render. */
1138 const bool uniform_zoom = true;
1139 const float max_zoom_factor = 5.0f;
1140 const float2 margin = float2(20);
1141 /* Pixel scale (aka. "fill_factor, aka. "Precision") to reduce image size. */
1142 const float pixel_scale = brush.gpencil_settings->fill_factor;
1143 const auto [zoom, offset, image_size, image_to_region] = fit_strokes_to_view(view_context,
1144 boundary_layers,
1145 src_drawings,
1146 fit_method,
1147 fill_point,
1148 uniform_zoom,
1149 max_zoom_factor,
1150 margin,
1151 pixel_scale);
1152
1153 ed::greasepencil::DrawingPlacement placement(scene, region, view3d, object_eval, &layer);
1154 if (placement.use_project_to_surface() || placement.use_project_to_stroke()) {
1155 placement.cache_viewport_depths(&depsgraph, &region, &view3d);
1156 }
1157
1158 Image *ima = render_strokes(view_context,
1159 brush,
1160 scene,
1161 layer,
1162 boundary_layers,
1163 src_drawings,
1164 image_size,
1165 alpha_threshold,
1166 fill_point,
1167 extensions,
1168 placement,
1169 zoom,
1170 offset);
1171 if (!ima) {
1172 return {};
1173 }
1174
1175 /* TODO should use the same hardness as the paint brush. */
1176 const float stroke_hardness = 1.0f;
1177
1178 bke::CurvesGeometry fill_curves = process_image(*ima,
1179 scene,
1180 view_context,
1181 brush,
1182 placement,
1183 image_to_region,
1184 stroke_material_index,
1185 stroke_hardness,
1186 invert,
1187 keep_images);
1188
1189 if (!keep_images) {
1190 BKE_id_free(view_context.bmain, ima);
1191 }
1192
1193 return fill_curves;
1194}
1195
1196} // namespace blender::ed::greasepencil
Camera data-block and utility functions.
Low-level operations for curves.
Low-level operations for grease pencil.
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
void BKE_id_free(Main *bmain, void *idv)
General operations, lookup, etc. for materials.
Material * BKE_object_material_get(Object *ob, short act)
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE void copy_v3_v3(float r[3], const float a[3])
#define BLI_SCOPED_DEFER(function_to_defer)
#define ENUM_OPERATORS(_type, _max)
#define ELEM(...)
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ GP_FILL_DMODE_CONTROL
@ GPPAINT_MODE_STROKE
@ GPPAINT_MODE_FILL
@ GPPAINT_MODE_BOTH
@ GP_MATERIAL_HIDE
Object is a sort of wrapper for general info.
@ OB_GREASE_PENCIL
@ V3D_PROJ_TEST_NOP
Definition ED_view3d.hh:279
float ED_view3d_pixel_size(const RegionView3D *rv3d, const float co[3])
eV3DProjStatus
Definition ED_view3d.hh:255
@ V3D_PROJ_RET_OK
Definition ED_view3d.hh:256
eV3DProjStatus ED_view3d_project_float_global(const ARegion *region, const float co[3], float r_co[2], eV3DProjTest flag)
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_depth_mask(bool depth)
Definition gpu_state.cc:110
void GPU_blend(GPUBlend blend)
Definition gpu_state.cc:42
BPy_StructRNA * depsgraph
static VArray from_single(T value, const int64_t size)
ChannelStorageType r
ChannelStorageType g
ChannelStorageType b
ChannelStorageType a
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t size() const
constexpr bool is_empty() const
constexpr IndexRange drop_front(int64_t n) const
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_stack.hh:308
void push(const T &value)
Definition BLI_stack.hh:213
static VArray from_single(T value, const int64_t size)
static VArray from_container(ContainerT container)
int64_t size() const
void append(const T &value)
bool is_empty() const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, AttrType data_type, const void *default_value=nullptr) const
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
AttributeAccessor attributes() const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
const bke::CurvesGeometry & strokes() const
float4x4 to_world_space(const Object &object) const
void cache_viewport_depths(Depsgraph *depsgraph, ARegion *region, View3D *view3d)
float3 project(float2 co, bool &clipped) const
ColorGeometry4b & pixel_from_coord(const int2 &c)
const ColorGeometry4b & pixel_from_coord(const int2 &c) const
void foreach_index(Fn &&fn) const
MatBase< 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< int, 2 > int2
CCL_NAMESPACE_BEGIN ccl_device float invert(const float color, const float factor)
Definition invert.h:11
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, const bke::greasepencil::Drawing &drawing_orig)
auto attribute_filter_from_skip_ref(const Span< StringRef > skip)
void fill_attribute_range_default(MutableAttributeAccessor dst_attributes, AttrDomain domain, const AttributeFilter &attribute_filter, IndexRange range)
Bounds< T > merge(const Bounds< T > &a, const Bounds< T > &b)
Definition BLI_bounds.hh:26
void compute_view_matrices(const ViewContext &view_context, const Scene &scene, const int2 &win_size, const float2 &zoom, const float2 &offset)
void region_reset(ARegion &region, const RegionViewData &data)
void draw_lines(const float4x4 &transform, IndexRange indices, Span< float3 > start_positions, Span< float3 > end_positions, const VArray< ColorGeometry4f > &colors, float line_width)
Image * image_render_end(Main &bmain, GPUOffScreen *buffer)
void draw_grease_pencil_strokes(const RegionView3D &rv3d, const int2 &win_size, const Object &object, const bke::greasepencil::Drawing &drawing, const float4x4 &transform, const IndexMask &strokes_mask, const VArray< ColorGeometry4f > &colors, const bool use_xray, const float radius_scale)
RegionViewData region_init(ARegion &region, const int2 &win_size)
void draw_dot(const float4x4 &transform, const float3 &position, const float point_size, const ColorGeometry4f &color)
GPUOffScreen * image_render_begin(const int2 &win_size)
static FillBoundary build_fill_boundary(const ImageBufferAccessor &buffer, bool include_holes)
static IndexMask get_visible_boundary_strokes(const Object &object, const DrawingInfo &info, const bool is_boundary_layer, IndexMaskMemory &memory)
static const int2 offset_by_direction[num_directions]
constexpr const char * attr_is_fill_guide
constexpr const char * attr_material_index
static Bounds< float2 > get_region_bounds(const ARegion &region)
static void dilate(ImageBufferAccessor &buffer, int iterations=1)
float opacity_from_input_sample(const float pressure, const Brush *brush, const BrushGpencilSettings *settings)
FillResult flood_fill(ImageBufferAccessor &buffer, const int leak_filter_width=0)
static void erode(ImageBufferAccessor &buffer, int iterations=1)
const ColorGeometry4f draw_boundary_color
static int wrap_dir_3n(const int dir)
bke::CurvesGeometry fill_strokes(const ViewContext &view_context, const Brush &brush, const Scene &scene, const bke::greasepencil::Layer &layer, const VArray< bool > &boundary_layers, Span< DrawingInfo > src_drawings, bool invert, const std::optional< float > alpha_threshold, const float2 &fill_point, const ExtensionData &extensions, FillToolFitMethod fit_method, int stroke_material_index, bool keep_images)
constexpr const int num_directions
static VArray< ColorGeometry4f > get_stroke_colors(const Object &object, const bke::CurvesGeometry &curves, const VArray< float > &opacities, const VArray< int > materials, const ColorGeometry4f &tint_color, const std::optional< float > alpha_threshold)
static void mark_borders(ImageBufferAccessor &buffer)
static std::optional< Bounds< float2 > > get_boundary_bounds(const ARegion &region, const RegionView3D &rv3d, const Object &object, const Object &object_eval, const VArray< bool > &boundary_layers, const Span< DrawingInfo > src_drawings)
static void convert_colors_to_flags(ImageBufferAccessor &buffer)
static bke::CurvesGeometry boundary_to_curves(const Scene &scene, const ViewContext &view_context, const Brush &brush, const FillBoundary &boundary, const ImageBufferAccessor &buffer, const ed::greasepencil::DrawingPlacement &placement, const float3x3 &image_to_region, const int material_index, const float hardness)
static auto fit_strokes_to_view(const ViewContext &view_context, const VArray< bool > &boundary_layers, const Span< DrawingInfo > src_drawings, const FillToolFitMethod fit_method, const float2 fill_point, const bool uniform_zoom, const float max_zoom_factor, const float2 margin, const float pixel_scale)
static bke::CurvesGeometry process_image(Image &ima, const Scene &scene, const ViewContext &view_context, const Brush &brush, const ed::greasepencil::DrawingPlacement &placement, const float3x3 &image_to_region, const int stroke_material_index, const float stroke_hardness, const bool invert, const bool output_as_colors)
static Image * render_strokes(const ViewContext &view_context, const Brush &brush, const Scene &scene, const bke::greasepencil::Layer &layer, const VArray< bool > &boundary_layers, const Span< DrawingInfo > src_drawings, const int2 &image_size, const std::optional< float > alpha_threshold, const float2 &fill_point, const ExtensionData &extensions, const ed::greasepencil::DrawingPlacement &placement, const float2 &zoom, const float2 &offset)
float radius_from_input_sample(const RegionView3D *rv3d, const ARegion *region, const Brush *brush, const float pressure, const float3 &location, const float4x4 &to_world, const BrushGpencilSettings *settings)
static void convert_flags_to_colors(ImageBufferAccessor &buffer)
static void set_flag(ColorGeometry4b &color, const ColorFlag flag, bool value)
const ColorGeometry4f draw_seed_color
static bool get_flag(const ColorGeometry4b &color, const ColorFlag flag)
static void invert_fill(ImageBufferAccessor &buffer)
bool brush_using_vertex_color(const GpPaint *gp_paint, const Brush *brush)
T clamp(const T &a, const T &min, const T &max)
T safe_rcp(const T &a)
T safe_divide(const T &a, const T &b)
T reduce_max(const VecBase< T, Size > &a)
MatT from_loc_scale(const typename MatT::loc_type &location, const VecBase< typename MatT::base_type, ScaleDim > &scale)
T max(const T &a, const T &b)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
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
MatBase< float, 4, 4 > float4x4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
VecBase< float, 3 > float3
ColorSceneLinearByteEncoded4b< eAlpha::Premultiplied > ColorGeometry4b
struct BrushGpencilSettings * gpencil_settings
float color[3]
struct MaterialGPencilStyle * gp_style
float viewmat[4][4]
struct ToolSettings * toolsettings
RegionView3D * rv3d
Definition ED_view3d.hh:80
ARegion * region
Definition ED_view3d.hh:77
Main * bmain
Definition ED_view3d.hh:67
View3D * v3d
Definition ED_view3d.hh:78
Object * obact
Definition ED_view3d.hh:75
Depsgraph * depsgraph
Definition ED_view3d.hh:72
T center() const
VecBase< T, 2 > xy() const
const bke::greasepencil::Drawing & drawing
struct blender::ed::greasepencil::ExtensionData::@020012301277233322117257155067364007210377153176 lines
i
Definition text_draw.cc:230
uint8_t flag
Definition wm_window.cc:145