Blender V5.0
pbvh_pixels_copy.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array.hh"
6#include "BLI_listbase.h"
7#include "BLI_math_geom.h"
8#include "BLI_math_vector.hh"
9#include "BLI_task.hh"
10#include "BLI_vector.hh"
11
12#include "IMB_imbuf.hh"
13#include "IMB_imbuf_types.hh"
14
15#include "BKE_image_wrappers.hh"
16#include "BKE_paint_bvh.hh"
18
19#include "pbvh_pixels_copy.hh"
20#include "pbvh_uv_islands.hh"
21
23
24const int THREADING_GRAIN_SIZE = 128;
25
27enum class CoordSpace {
32
40};
41
42template<CoordSpace Space> struct Vertex {
44};
45
46template<CoordSpace Space> struct Edge {
49};
50
52static rcti get_bounds(const Edge<CoordSpace::Tile> &tile_edge)
53{
56 BLI_rcti_do_minmax_v(&bounds, int2(tile_edge.vertex_1.coordinate));
57 BLI_rcti_do_minmax_v(&bounds, int2(tile_edge.vertex_2.coordinate));
58 return bounds;
59}
60
62static void add_margin(rcti &bounds, int margin)
63{
64 bounds.xmin -= margin;
65 bounds.xmax += margin;
66 bounds.ymin -= margin;
67 bounds.ymax += margin;
68}
69
71static void clamp(rcti &bounds, int2 resolution)
72{
73 rcti clamping_bounds;
74 BLI_rcti_init(&clamping_bounds, 0, resolution.x - 1, 0, resolution.y - 1);
75 BLI_rcti_isect(&bounds, &clamping_bounds, &bounds);
76}
77
79 const image::ImageTileWrapper image_tile,
80 const int2 tile_resolution)
81{
82 return Vertex<CoordSpace::Tile>{(uv_vertex.coordinate - float2(image_tile.get_tile_offset())) *
83 float2(tile_resolution)};
84}
85
87 const image::ImageTileWrapper image_tile,
88 const int2 tile_resolution)
89{
91 convert_coord_space(uv_edge.vertex_1, image_tile, tile_resolution),
92 convert_coord_space(uv_edge.vertex_2, image_tile, tile_resolution),
93 };
94}
95
96class NonManifoldTileEdges : public Vector<Edge<CoordSpace::Tile>> {};
97
98class NonManifoldUVEdges : public Vector<Edge<CoordSpace::UV>> {
99 public:
101 {
102 int num_non_manifold_edges = count_non_manifold_edges(mesh_data);
103 reserve(num_non_manifold_edges);
104 for (const int primitive_id : mesh_data.corner_tris.index_range()) {
105 for (const int edge_id : mesh_data.primitive_to_edge_map[primitive_id]) {
106 if (is_manifold(mesh_data, edge_id)) {
107 continue;
108 }
109 const int3 &tri = mesh_data.corner_tris[primitive_id];
110 const int2 mesh_edge = mesh_data.edges[edge_id];
112
113 edge.vertex_1.coordinate = find_uv(mesh_data, tri, mesh_edge[0]);
114 edge.vertex_2.coordinate = find_uv(mesh_data, tri, mesh_edge[1]);
115 append(edge);
116 }
117 }
118 BLI_assert_msg(size() == num_non_manifold_edges,
119 "Incorrect number of non manifold edges added. ");
120 }
121
123 const int2 tile_resolution) const
124 {
126 for (const Edge<CoordSpace::UV> &uv_edge : *this) {
128 uv_edge, image_tile, tile_resolution);
129 result.append(tile_edge);
130 }
131 return result;
132 }
133
134 private:
135 static int64_t count_non_manifold_edges(const uv_islands::MeshData &mesh_data)
136 {
137 int64_t result = 0;
138 for (const int primitive_id : mesh_data.corner_tris.index_range()) {
139 for (const int edge_id : mesh_data.primitive_to_edge_map[primitive_id]) {
140 if (is_manifold(mesh_data, edge_id)) {
141 continue;
142 }
143 result += 1;
144 }
145 }
146 return result;
147 }
148
149 static bool is_manifold(const uv_islands::MeshData &mesh_data, const int edge_id)
150 {
151 return mesh_data.edge_to_primitive_map[edge_id].size() == 2;
152 }
153
154 static float2 find_uv(const uv_islands::MeshData &mesh_data, const int3 &tri, int vertex_i)
155 {
156 for (int i = 0; i < 3; i++) {
157 const int loop_i = tri[i];
158 const int vert = mesh_data.corner_verts[loop_i];
159 if (vert == vertex_i) {
160 return mesh_data.uv_map[loop_i];
161 }
162 }
164 return float2(0.0f);
165 }
166};
167
168class PixelNodesTileData : public Vector<std::reference_wrapper<UDIMTilePixels>> {
169 public:
171 {
172 reserve(count_nodes(pbvh, image_tile));
173
174 std::visit(
175 [&](auto &nodes) {
176 for (blender::bke::pbvh::Node &node : nodes) {
177 if (should_add_node(node, image_tile)) {
178 NodeData &node_data = *static_cast<NodeData *>(node.pixels_);
179 UDIMTilePixels &tile_pixels = *node_data.find_tile_data(image_tile);
180 append(tile_pixels);
181 }
182 }
183 },
184 pbvh.nodes_);
185 }
186
187 private:
188 static bool should_add_node(blender::bke::pbvh::Node &node,
189 const image::ImageTileWrapper &image_tile)
190 {
191 if ((node.flag_ & Node::Leaf) == 0) {
192 return false;
193 }
194 if (node.pixels_ == nullptr) {
195 return false;
196 }
197 NodeData &node_data = *static_cast<NodeData *>(node.pixels_);
198 if (node_data.find_tile_data(image_tile) == nullptr) {
199 return false;
200 }
201 return true;
202 }
203
204 static int64_t count_nodes(blender::bke::pbvh::Tree &pbvh,
205 const image::ImageTileWrapper &image_tile)
206 {
207 int64_t result = 0;
208 std::visit(
209 [&](auto &nodes) {
210 for (blender::bke::pbvh::Node &node : nodes) {
211 if (should_add_node(node, image_tile)) {
212 result++;
213 }
214 }
215 },
216 pbvh.nodes_);
217 return result;
218 }
219};
220
225
226struct Rows {
235
236 struct Pixel {
238 float distance;
247
248 Pixel() = default;
249
250 void init(int2 coordinate)
251 {
252 copy_command.destination = coordinate;
253 copy_command.source_1 = coordinate;
254 copy_command.source_2 = coordinate;
255 copy_command.mix_factor = 0.0f;
257 distance = std::numeric_limits<float>::max();
258 edge_index = -1;
259 }
260 };
261
265
266 struct RowView {
267 int row_number = 0;
270 RowView() = delete;
273 pixels(
275 {
276 }
277 };
278
283
285 {
286 int64_t index = 0;
287 for (int y : IndexRange(resolution.y)) {
288 for (int64_t x : IndexRange(resolution.x)) {
289 int2 position(x, y);
290 pixels[index++].init(position);
291 }
292 }
293 }
294
300 {
301 for (const UDIMTilePixels &tile_pixels : nodes_tile_pixels) {
303 tile_pixels.pixel_rows, [&](const PackedPixelRow &encoded_pixels) {
304 for (int x = encoded_pixels.start_image_coordinate.x;
305 x < encoded_pixels.start_image_coordinate.x + encoded_pixels.num_pixels;
306 x++)
307 {
308 int64_t index = encoded_pixels.start_image_coordinate.y * resolution.x + x;
309 pixels[index].type = PixelType::Brush;
310 pixels[index].distance = 0.0f;
311 }
312 });
313 }
314 }
315
326 int2 find_second_source(int2 destination, int2 first_source)
327 {
328 rcti search_bounds;
329 BLI_rcti_init(&search_bounds,
330 max_ii(first_source.x - 1, 0),
331 min_ii(first_source.x + 1, resolution.x - 1),
332 max_ii(first_source.y - 1, 0),
333 min_ii(first_source.y + 1, resolution.y - 1));
334 /* Initialize to the first source, so when no other source could be found it will use the
335 * first_source. */
336 int2 found_source = first_source;
337 float found_distance = std::numeric_limits<float>::max();
338 for (int sy : IndexRange(search_bounds.ymin, BLI_rcti_size_y(&search_bounds) + 1)) {
339 for (int sx : IndexRange(search_bounds.xmin, BLI_rcti_size_x(&search_bounds) + 1)) {
340 int2 source(sx, sy);
341 /* Skip first source as it should be the closest and already selected. */
342 if (source == first_source) {
343 continue;
344 }
345 int pixel_index = sy * resolution.y + sx;
346 if (pixels[pixel_index].type != PixelType::Brush) {
347 continue;
348 }
349
350 float new_distance = math::distance(float2(destination), float2(source));
351 if (new_distance < found_distance) {
352 found_distance = new_distance;
353 found_source = source;
354 }
355 }
356 }
357 return found_source;
358 }
359
360 float determine_mix_factor(const int2 destination,
361 const int2 source_1,
362 const int2 source_2,
363 const Edge<CoordSpace::Tile> &edge)
364 {
365 /* Use stable result when both sources are the same. */
366 if (source_1 == source_2) {
367 return 0.0f;
368 }
369
370 float2 clamped_to_edge;
371 float destination_lambda = closest_to_line_v2(
372 clamped_to_edge, float2(destination), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
373 float source_1_lambda = closest_to_line_v2(
374 clamped_to_edge, float2(source_1), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
375 float source_2_lambda = closest_to_line_v2(
376 clamped_to_edge, float2(source_2), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
377
378 return clamp_f(
379 (destination_lambda - source_1_lambda) / (source_2_lambda - source_1_lambda), 0.0f, 1.0f);
380 }
381
382 void find_copy_source(Pixel &pixel, const NonManifoldTileEdges &tile_edges)
383 {
385
386 rcti bounds;
391 pixel.copy_command.destination.y);
394
395 float found_distance = std::numeric_limits<float>::max();
396 int2 found_source(0);
397
398 for (int sy : IndexRange(bounds.ymin, BLI_rcti_size_y(&bounds))) {
399 int pixel_index = sy * resolution.x;
400 for (int sx : IndexRange(bounds.xmin, BLI_rcti_size_x(&bounds))) {
401 Pixel &source = pixels[pixel_index + sx];
402 if (source.type != PixelType::Brush) {
403 continue;
404 }
405 float new_distance = math::distance(float2(sx, sy),
407 if (new_distance < found_distance) {
408 found_source = int2(sx, sy);
409 found_distance = new_distance;
410 }
411 }
412 }
413
414 if (found_distance == std::numeric_limits<float>::max()) {
415 return;
416 }
418 pixel.distance = found_distance;
419 pixel.copy_command.source_1 = found_source;
424 tile_edges[pixel.edge_index]);
425 }
426
427 void find_copy_source(Vector<std::reference_wrapper<Pixel>> &selected_pixels,
428 const NonManifoldTileEdges &tile_edges)
429 {
431 IndexRange(selected_pixels.size()), THREADING_GRAIN_SIZE, [&](IndexRange range) {
432 for (int selected_pixel_index : range) {
433 Pixel &current_pixel = selected_pixels[selected_pixel_index];
434 find_copy_source(current_pixel, tile_edges);
435 }
436 });
437 }
438
440 const NonManifoldTileEdges &tile_edges)
441 {
443 selected_pixels.reserve(10000);
444
445 for (int tile_edge_index : tile_edges.index_range()) {
446 const Edge<CoordSpace::Tile> &tile_edge = tile_edges[tile_edge_index];
447 rcti edge_bounds = get_bounds(tile_edge);
448 add_margin(edge_bounds, margin);
449 clamp(edge_bounds, resolution);
450
451 for (const int64_t sy : IndexRange(edge_bounds.ymin, BLI_rcti_size_y(&edge_bounds))) {
452 for (const int64_t sx : IndexRange(edge_bounds.xmin, BLI_rcti_size_x(&edge_bounds))) {
453 const int64_t index = sy * resolution.x + sx;
454 Pixel &pixel = pixels[index];
455 if (pixel.type == PixelType::Brush) {
456 continue;
457 }
459 "PixelType::CopyFromClosestEdge isn't allowed to be set as it is set "
460 "when finding the pixels to copy.");
461
462 const float2 point(sx, sy);
463 float2 closest_edge_point;
464 closest_to_line_segment_v2(closest_edge_point,
465 point,
466 tile_edge.vertex_1.coordinate,
467 tile_edge.vertex_2.coordinate);
468 float distance_to_edge = math::distance(closest_edge_point, point);
469 if (distance_to_edge < margin && distance_to_edge < pixel.distance) {
471 selected_pixels.append(std::reference_wrapper<Pixel>(pixel));
472 }
474 pixel.distance = distance_to_edge;
475 pixel.edge_index = tile_edge_index;
476 }
477 }
478 }
479 }
480 return selected_pixels;
481 }
482
483 void pack_into(const Span<std::reference_wrapper<Pixel>> selected_pixels,
484 CopyPixelTile &copy_tile) const
485 {
486 std::optional<std::reference_wrapper<CopyPixelGroup>> last_group = std::nullopt;
487 std::optional<CopyPixelCommand> last_command = std::nullopt;
488
489 for (const Pixel &elem : selected_pixels) {
490 if (elem.type == PixelType::CopyFromClosestEdge) {
491 if (!last_command.has_value() || !last_command->can_be_extended(elem.copy_command)) {
492 CopyPixelGroup new_group = {elem.copy_command.destination - int2(1, 0),
493 elem.copy_command.source_1,
494 copy_tile.command_deltas.size(),
495 0};
496 copy_tile.groups.append(new_group);
497 last_group = copy_tile.groups.last();
498 last_command = CopyPixelCommand(*last_group);
499 }
500
501 DeltaCopyPixelCommand delta_command = last_command->encode_delta(elem.copy_command);
502 copy_tile.command_deltas.append(delta_command);
503 last_group->get().num_deltas++;
504 last_command = elem.copy_command;
505 }
506 }
507 }
508};
509
511 Image &image,
512 ImageUser &image_user,
513 const uv_islands::MeshData &mesh_data)
514{
515 PBVHData &pbvh_data = data_get(pbvh);
516 pbvh_data.tiles_copy_pixels.clear();
517 const NonManifoldUVEdges non_manifold_edges(mesh_data);
518 if (non_manifold_edges.is_empty()) {
519 /* Early exit: No non manifold edges detected. */
520 return;
521 }
522
523 ImageUser tile_user = image_user;
524 LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) {
526 tile_user.tile = image_tile.get_tile_number();
527
528 ImBuf *tile_buffer = BKE_image_acquire_ibuf(&image, &tile_user, nullptr);
529 if (tile_buffer == nullptr) {
530 continue;
531 }
532 const PixelNodesTileData nodes_tile_pixels(pbvh, image_tile);
533
534 int2 tile_resolution(tile_buffer->x, tile_buffer->y);
535 BKE_image_release_ibuf(&image, tile_buffer, nullptr);
536
537 NonManifoldTileEdges tile_edges = non_manifold_edges.extract_tile_edges(image_tile,
538 tile_resolution);
539 CopyPixelTile copy_tile(image_tile.get_tile_number());
540
541 Rows rows(tile_resolution, image.seam_margin);
542 rows.init_pixels();
543 rows.mark_pixels_effected_by_brush(nodes_tile_pixels);
544
547 rows.find_copy_source(selected_pixels, tile_edges);
548 rows.pack_into(selected_pixels, copy_tile);
549
550 copy_tile.print_compression_rate();
551 pbvh_data.tiles_copy_pixels.tiles.append(copy_tile);
552 }
553}
554
556 Image &image,
557 ImageUser &image_user,
558 image::TileNumber tile_number)
559{
560 PBVHData &pbvh_data = data_get(pbvh);
561 std::optional<std::reference_wrapper<CopyPixelTile>> pixel_tile =
562 pbvh_data.tiles_copy_pixels.find_tile(tile_number);
563 if (!pixel_tile.has_value()) {
564 /* No pixels need to be copied. */
565 return;
566 }
567
568 ImageUser tile_user = image_user;
569 tile_user.tile = tile_number;
570 ImBuf *tile_buffer = BKE_image_acquire_ibuf(&image, &tile_user, nullptr);
571 if (tile_buffer == nullptr) {
572 /* No tile buffer found to copy. */
573 return;
574 }
575
576 CopyPixelTile &tile = pixel_tile->get();
577 threading::parallel_for(tile.groups.index_range(), THREADING_GRAIN_SIZE, [&](IndexRange range) {
578 tile.copy_pixels(*tile_buffer, range);
579 });
580
581 BKE_image_release_ibuf(&image, tile_buffer, nullptr);
582}
583
584} // namespace blender::bke::pbvh::pixels
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
A BVH for high poly meshes.
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define LISTBASE_FOREACH(type, var, list)
MINLINE int min_ii(int a, int b)
MINLINE float clamp_f(float value, float min, float max)
MINLINE int max_ii(int a, int b)
float closest_to_line_segment_v2(float r_close[2], const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:365
float closest_to_line_v2(float r_close[2], const float p[2], const float l1[2], const float l2[2])
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:198
void BLI_rcti_init_minmax(struct rcti *rect)
Definition rct.cc:474
void BLI_rcti_init(struct rcti *rect, int xmin, int xmax, int ymin, int ymax)
Definition rct.cc:414
bool BLI_rcti_isect(const struct rcti *src1, const struct rcti *src2, struct rcti *dest)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:194
void BLI_rcti_do_minmax_v(struct rcti *rect, const int xy[2])
Definition rct.cc:486
long long int int64_t
void append(const Edge< CoordSpace::UV > &value)
bool is_empty() const
IndexRange index_range() const
void reserve(const int64_t min_capacity)
void append(const T &value)
void reserve(const int64_t min_capacity)
pixels::NodeData * pixels_
std::variant< Vector< MeshNode >, Vector< GridsNode >, Vector< BMeshNode > > nodes_
NonManifoldTileEdges extract_tile_edges(const image::ImageTileWrapper image_tile, const int2 tile_resolution) const
NonManifoldUVEdges(const uv_islands::MeshData &mesh_data)
PixelNodesTileData(blender::bke::pbvh::Tree &pbvh, const image::ImageTileWrapper &image_tile)
constexpr T clamp(T, U, U) RET
VecBase< int, 2 > int2
const ccl_global KernelWorkTile * tile
static void clamp(rcti &bounds, int2 resolution)
static Vertex< CoordSpace::Tile > convert_coord_space(const Vertex< CoordSpace::UV > &uv_vertex, const image::ImageTileWrapper image_tile, const int2 tile_resolution)
static void add_margin(rcti &bounds, int margin)
void copy_update(blender::bke::pbvh::Tree &pbvh, Image &image, ImageUser &image_user, const uv_islands::MeshData &mesh_data)
void copy_pixels(blender::bke::pbvh::Tree &pbvh, Image &image, ImageUser &image_user, image::TileNumber tile_number)
static rcti get_bounds(const Edge< CoordSpace::Tile > &tile_edge)
PBVHData & data_get(blender::bke::pbvh::Tree &pbvh)
T distance(const T &a, const T &b)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:56
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< int32_t, 3 > int3
Vector< DeltaCopyPixelCommand > command_deltas
std::optional< std::reference_wrapper< CopyPixelTile > > find_tile(image::TileNumber tile_number)
UDIMTilePixels * find_tile_data(const image::ImageTileWrapper &image_tile)
RowView(Rows &rows, int64_t row_number)
void pack_into(const Span< std::reference_wrapper< Pixel > > selected_pixels, CopyPixelTile &copy_tile) const
Vector< std::reference_wrapper< Pixel > > filter_pixels_for_closer_examination(const NonManifoldTileEdges &tile_edges)
void mark_pixels_effected_by_brush(const PixelNodesTileData &nodes_tile_pixels)
Rows(int2 resolution, int margin)
void find_copy_source(Pixel &pixel, const NonManifoldTileEdges &tile_edges)
void find_copy_source(Vector< std::reference_wrapper< Pixel > > &selected_pixels, const NonManifoldTileEdges &tile_edges)
float determine_mix_factor(const int2 destination, const int2 source_1, const int2 source_2, const Edge< CoordSpace::Tile > &edge)
int2 find_second_source(int2 destination, int2 first_source)
int ymin
int xmin
i
Definition text_draw.cc:230