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