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