diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 3a8e9738b85..57e0e71ab60 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -10,6 +10,7 @@ if(WITH_COMPOSITOR_CPU) intern nodes operations + algorithms realtime_compositor ../blenkernel ../blentranslation @@ -589,6 +590,9 @@ if(WITH_COMPOSITOR_CPU) operations/COM_MaskOperation.cc operations/COM_MaskOperation.h + + algorithms/COM_JumpFloodingAlgorithm.cc + algorithms/COM_JumpFloodingAlgorithm.h ) set(LIB diff --git a/source/blender/compositor/algorithms/COM_JumpFloodingAlgorithm.cc b/source/blender/compositor/algorithms/COM_JumpFloodingAlgorithm.cc new file mode 100644 index 00000000000..f24526c9107 --- /dev/null +++ b/source/blender/compositor/algorithms/COM_JumpFloodingAlgorithm.cc @@ -0,0 +1,106 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "BLI_array.hh" +#include "BLI_math_base.h" +#include "BLI_math_base.hh" +#include "BLI_math_vector.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" + +#include "COM_JumpFloodingAlgorithm.h" + +/* Exact copies of the functions in gpu_shader_compositor_jump_flooding_lib.glsl and + * jump_flooding.cc but adapted for CPU. See those files for more information. */ + +namespace blender::compositor { + +int2 encode_jump_flooding_value(int2 closest_seed_texel, bool is_flooded) +{ + return is_flooded ? closest_seed_texel : JUMP_FLOODING_NON_FLOODED_VALUE; +} + +int2 initialize_jump_flooding_value(int2 texel, bool is_seed) +{ + return encode_jump_flooding_value(texel, is_seed); +} + +static int2 load_jump_flooding(Span input, int2 texel, int2 size, int2 fallback) +{ + if (texel.x < 0 || texel.x >= size.x || texel.y < 0 || texel.y >= size.y) { + return fallback; + } + return input[size_t(texel.y) * size.x + texel.x]; +} + +static void jump_flooding_pass(Span input, + MutableSpan output, + int2 size, + int step_size) +{ + threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) { + for (const int64_t y : sub_y_range) { + for (const int64_t x : IndexRange(size.x)) { + int2 texel = int2(x, y); + + int2 closest_seed_texel = int2(0); + float minimum_squared_distance = std::numeric_limits::max(); + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + int2 offset = int2(i, j) * step_size; + + int2 fallback = JUMP_FLOODING_NON_FLOODED_VALUE; + int2 jump_flooding_value = load_jump_flooding(input, texel + offset, size, fallback); + + if (jump_flooding_value == JUMP_FLOODING_NON_FLOODED_VALUE) { + continue; + } + + int2 closest_seed_texel_to_neighbor = jump_flooding_value; + + float squared_distance = math::distance_squared(float2(closest_seed_texel_to_neighbor), + float2(texel)); + + if (squared_distance < minimum_squared_distance) { + minimum_squared_distance = squared_distance; + closest_seed_texel = closest_seed_texel_to_neighbor; + } + } + } + + bool flooding_happened = minimum_squared_distance != std::numeric_limits::max(); + int2 jump_flooding_value = encode_jump_flooding_value(closest_seed_texel, + flooding_happened); + + output[size_t(texel.y) * size.x + texel.x] = jump_flooding_value; + } + } + }); +} + +Array jump_flooding(Span input, int2 size) +{ + Array initial_flooded_result(size_t(size.x) * size.y); + jump_flooding_pass(input, initial_flooded_result, size, 1); + + Array *result_to_flood = &initial_flooded_result; + Array intermediate_result(size_t(size.x) * size.y); + Array *result_after_flooding = &intermediate_result; + + const int max_size = math::max(size.x, size.y); + int step_size = power_of_2_max_i(max_size) / 2; + + while (step_size != 0) { + jump_flooding_pass(*result_to_flood, *result_after_flooding, size, step_size); + std::swap(result_to_flood, result_after_flooding); + step_size /= 2; + } + + return *result_to_flood; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/algorithms/COM_JumpFloodingAlgorithm.h b/source/blender/compositor/algorithms/COM_JumpFloodingAlgorithm.h new file mode 100644 index 00000000000..2d6a26c5879 --- /dev/null +++ b/source/blender/compositor/algorithms/COM_JumpFloodingAlgorithm.h @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_array.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_span.hh" + +/* Exact copies of the functions in gpu_shader_compositor_jump_flooding_lib.glsl and + * COM_algorithm_jump_flooding.hh but adapted for CPU. See those files for more information. */ + +#define JUMP_FLOODING_NON_FLOODED_VALUE int2(-1) + +namespace blender::compositor { + +int2 encode_jump_flooding_value(int2 closest_seed_texel, bool is_flooded); + +int2 initialize_jump_flooding_value(int2 texel, bool is_seed); + +Array jump_flooding(Span input, int2 size); + +} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_DoubleEdgeMaskNode.cc b/source/blender/compositor/nodes/COM_DoubleEdgeMaskNode.cc index fec2aede4c8..fd7b9589fcb 100644 --- a/source/blender/compositor/nodes/COM_DoubleEdgeMaskNode.cc +++ b/source/blender/compositor/nodes/COM_DoubleEdgeMaskNode.cc @@ -19,8 +19,8 @@ void DoubleEdgeMaskNode::convert_to_operations(NodeConverter &converter, const bNode *bnode = this->get_bnode(); operation = new DoubleEdgeMaskOperation(); - operation->set_adjacent_only(bnode->custom1); - operation->set_keep_inside(bnode->custom2); + operation->set_include_all_inner_edges(!bool(bnode->custom1)); + operation->set_include_edges_of_image(bool(bnode->custom2)); converter.add_operation(operation); converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0)); diff --git a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc index 9bf88ac04e7..c02f4810eba 100644 --- a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc +++ b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc @@ -4,1284 +4,138 @@ #include +#include "BLI_math_vector.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" + #include "COM_DoubleEdgeMaskOperation.h" +#include "COM_JumpFloodingAlgorithm.h" + +/* Exact copies of the functions in compositor_double_edge_mask_compute_boundary.glsl and + * compositor_double_edge_mask_compute_gradient.glsl but adapted for CPU. See those files for more + * information. */ namespace blender::compositor { -/* This part has been copied from the double edge mask. */ -static void do_adjacentKeepBorders( - uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) +static float load_mask(const float *input, int2 texel, int2 size) { - int x; - uint isz = 0; /* Inner edge size. */ - uint osz = 0; /* Outer edge size. */ - uint gsz = 0; /* Gradient fill area size. */ - /* Test the four corners */ - /* Upper left corner. */ - x = t - rw + 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or to the right, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - /* Upper right corner. */ - x = t; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or to the left, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x - 1] && lomask[x - 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - /* Lower left corner. */ - x = 0; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel above, or to the right, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - /* Lower right corner. */ - x = rw - 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel above, or to the left, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x - 1] && lomask[x - 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - - /* Test the TOP row of pixels in buffer, except corners */ - for (x = t - 1; x >= (t - rw) + 2; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel to the right, or to the left, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } - - /* Test the BOTTOM row of pixels in buffer, except corners */ - for (x = rw - 2; x; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel to the right, or to the left, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } - /* Test the LEFT edge of pixels in buffer, except corners */ - for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or above, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } - - /* Test the RIGHT edge of pixels in buffer, except corners */ - for (x = t - rw; x > rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or above, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } - - rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ - rsize[1] = osz; - rsize[2] = gsz; + const int2 clamped_texel = math::clamp(texel, int2(0), size - 1); + return input[size_t(clamped_texel.y) * size.x + clamped_texel.x]; } -static void do_adjacentBleedBorders( - uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) +static float load_mask(const float *input, int2 texel, int2 size, float fallback) { - int x; - uint isz = 0; /* Inner edge size. */ - uint osz = 0; /* Outer edge size. */ - uint gsz = 0; /* Gradient fill area size. */ - /* Test the four corners */ - /* Upper left corner. */ - x = t - rw + 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or to the right, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or to the right. */ - if (!lomask[x - rw] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } + if (texel.x < 0 || texel.x >= size.x || texel.y < 0 || texel.y >= size.y) { + return fallback; } - /* Upper right corner. */ - x = t; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or to the left, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x - 1] && lomask[x - 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or to the left. */ - if (!lomask[x - rw] || !lomask[x - 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Lower left corner. */ - x = 0; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel above, or to the right, are empty in the inner mask, - * But filled in the outer mask. */ - if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty above or to the right. */ - if (!lomask[x + rw] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Lower right corner. */ - x = rw - 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel above, or to the left, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x - 1] && lomask[x - 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty above or to the left. */ - if (!lomask[x + rw] || !lomask[x - 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Test the TOP row of pixels in buffer, except corners */ - for (x = t - 1; x >= (t - rw) + 2; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel to the left, or to the right, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty to the left or to the right. */ - if (!lomask[x - 1] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - - /* Test the BOTTOM row of pixels in buffer, except corners */ - for (x = rw - 2; x; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel to the left, or to the right, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty to the left or to the right. */ - if (!lomask[x - 1] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - /* Test the LEFT edge of pixels in buffer, except corners */ - for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or above, are empty in the inner mask, - * but filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or above. */ - if (!lomask[x - rw] || !lomask[x + rw]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - - /* Test the RIGHT edge of pixels in buffer, except corners */ - for (x = t - rw; x > rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if pixel underneath, or above, are empty in the inner mask, - * But filled in the outer mask. */ - if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or above. */ - if (!lomask[x - rw] || !lomask[x + rw]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - - rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ - rsize[1] = osz; - rsize[2] = gsz; + return input[size_t(texel.y) * size.x + texel.x]; } -static void do_allKeepBorders( - uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) +void DoubleEdgeMaskOperation::compute_boundary(const float *inner_mask, + const float *outer_mask, + MutableSpan inner_boundary, + MutableSpan outer_boundary) { - int x; - uint isz = 0; /* Inner edge size. */ - uint osz = 0; /* Outer edge size. */ - uint gsz = 0; /* Gradient fill area size. */ - /* Test the four corners. */ - /* Upper left corner. */ - x = t - rw + 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if the inner mask is empty underneath or to the right. */ - if (!limask[x - rw] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - /* Upper right corner. */ - x = t; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if the inner mask is empty underneath or to the left. */ - if (!limask[x - rw] || !limask[x - 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - /* Lower left corner. */ - x = 0; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty above or to the right. */ - if (!limask[x + rw] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - /* Lower right corner. */ - x = rw - 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty above or to the left. */ - if (!limask[x + rw] || !limask[x - 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } + const int2 size = int2(this->get_width(), this->get_height()); + threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) { + for (const int64_t y : sub_y_range) { + for (const int64_t x : IndexRange(size.x)) { + int2 texel = int2(x, y); - /* Test the TOP row of pixels in buffer, except corners */ - for (x = t - 1; x >= (t - rw) + 2; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty to the left or to the right. */ - if (!limask[x - 1] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } + bool has_inner_non_masked_neighbors = false; + bool has_outer_non_masked_neighbors = false; + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + int2 offset = int2(i, j); - /* Test the BOTTOM row of pixels in buffer, except corners */ - for (x = rw - 2; x; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty to the left or to the right. */ - if (!limask[x - 1] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } - /* Test the LEFT edge of pixels in buffer, except corners */ - for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty underneath or above. */ - if (!limask[x - rw] || !limask[x + rw]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } + if (offset == int2(0)) { + continue; + } - /* Test the RIGHT edge of pixels in buffer, except corners */ - for (x = t - rw; x > rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty underneath or above. */ - if (!limask[x - rw] || !limask[x + rw]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - } + if (load_mask(inner_mask, texel + offset, size) == 0.0f) { + has_inner_non_masked_neighbors = true; + } - rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ - rsize[1] = osz; - rsize[2] = gsz; -} + float boundary_fallback = include_edges_of_image_ ? 0.0f : 1.0f; + if (load_mask(outer_mask, texel + offset, size, boundary_fallback) == 0.0f) { + has_outer_non_masked_neighbors = true; + } -static void do_allBleedBorders( - uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) -{ - int x; - uint isz = 0; /* Inner edge size. */ - uint osz = 0; /* Outer edge size. */ - uint gsz = 0; /* Gradient fill area size. */ - /* Test the four corners */ - /* Upper left corner. */ - x = t - rw + 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if the inner mask is empty underneath or to the right. */ - if (!limask[x - rw] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or to the right. */ - if (!lomask[x - rw] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Upper right corner. */ - x = t; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if the inner mask is empty underneath or to the left. */ - if (!limask[x - rw] || !limask[x - 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty above or to the left. */ - if (!lomask[x - rw] || !lomask[x - 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Lower left corner. */ - x = 0; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty above or to the right. */ - if (!limask[x + rw] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or to the right. */ - if (!lomask[x + rw] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Lower right corner. */ - x = rw - 1; - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty above or to the left. */ - if (!limask[x + rw] || !limask[x - 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or to the left. */ - if (!lomask[x + rw] || !lomask[x - 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - /* Test the TOP row of pixels in buffer, except corners */ - for (x = t - 1; x >= (t - rw) + 2; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty to the left or to the right. */ - if (!limask[x - 1] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty to the left or to the right. */ - if (!lomask[x - 1] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - - /* Test the BOTTOM row of pixels in buffer, except corners */ - for (x = rw - 2; x; x--) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty to the left or to the right. */ - if (!limask[x - 1] || !limask[x + 1]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty to the left or to the right. */ - if (!lomask[x - 1] || !lomask[x + 1]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - /* Test the LEFT edge of pixels in buffer, except corners */ - for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty underneath or above. */ - if (!limask[x - rw] || !limask[x + rw]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or above. */ - if (!lomask[x - rw] || !lomask[x + rw]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - - /* Test the RIGHT edge of pixels in buffer, except corners */ - for (x = t - rw; x > rw; x -= rw) { - /* Test if inner mask is filled. */ - if (limask[x]) { - /* Test if inner mask is empty underneath or above. */ - if (!limask[x - rw] || !limask[x + rw]) { - isz++; /* Increment inner edge size. */ - lres[x] = 4; /* Flag pixel as inner edge. */ - } - else { - res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ - } - } /* Inner mask was empty, test if outer mask is filled. */ - else if (lomask[x]) { - /* Test if outer mask is empty underneath or above. */ - if (!lomask[x - rw] || !lomask[x + rw]) { - osz++; /* Increment outer edge size. */ - lres[x] = 3; /* Flag pixel as outer edge. */ - } - else { - gsz++; /* Increment the gradient pixel count. */ - lres[x] = 2; /* Flag pixel as gradient. */ - } - } - } - - rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ - rsize[1] = osz; - rsize[2] = gsz; -} - -static void do_allEdgeDetection(uint t, - uint rw, - const uint *limask, - const uint *lomask, - uint *lres, - float *res, - uint *rsize, - uint in_isz, - uint in_osz, - uint in_gsz) -{ - int x; /* Pixel loop counter. */ - int a; /* Pixel loop counter. */ - int dx; /* Delta x. */ - int pix_prevRow; /* Pixel one row behind the one we are testing in a loop. */ - int pix_nextRow; /* Pixel one row in front of the one we are testing in a loop. */ - int pix_prevCol; /* Pixel one column behind the one we are testing in a loop. */ - int pix_nextCol; /* Pixel one column in front of the one we are testing in a loop. */ - - /* Test all rows between the FIRST and LAST rows, excluding left and right edges */ - for (x = (t - rw) + 1, dx = x - (rw - 2); dx > rw; x -= rw, dx -= rw) { - a = x - 2; - pix_prevRow = a + rw; - pix_nextRow = a - rw; - pix_prevCol = a + 1; - pix_nextCol = a - 1; - while (a > dx - 2) { - if (!limask[a]) { /* If the inner mask is empty. */ - if (lomask[a]) { /* If the outer mask is full. */ - /* - * Next we test all 4 directions around the current pixel: next/previous/up/down - * The test ensures that the outer mask is empty and that the inner mask - * is also empty. If both conditions are true for any one of the 4 adjacent pixels - * then the current pixel is counted as being a true outer edge pixel. - */ - if ((!lomask[pix_nextCol] && !limask[pix_nextCol]) || - (!lomask[pix_prevCol] && !limask[pix_prevCol]) || - (!lomask[pix_nextRow] && !limask[pix_nextRow]) || - (!lomask[pix_prevRow] && !limask[pix_prevRow])) - { - in_osz++; /* Increment the outer boundary pixel count. */ - lres[a] = 3; /* Flag pixel as part of outer edge. */ - } - else { /* It's not a boundary pixel, but it is a gradient pixel. */ - in_gsz++; /* Increment the gradient pixel count. */ - lres[a] = 2; /* Flag pixel as gradient. */ + if (has_inner_non_masked_neighbors && has_outer_non_masked_neighbors) { + break; + } } } - } - else { - if (!limask[pix_nextCol] || !limask[pix_prevCol] || !limask[pix_nextRow] || - !limask[pix_prevRow]) - { - in_isz++; /* Increment the inner boundary pixel count. */ - lres[a] = 4; /* Flag pixel as part of inner edge. */ - } - else { - res[a] = 1.0f; /* Pixel is part of inner mask, but not at an edge. */ - } - } - a--; - pix_prevRow--; - pix_nextRow--; - pix_prevCol--; - pix_nextCol--; - } - } - rsize[0] = in_isz; /* Fill in our return sizes for edges + fill. */ - rsize[1] = in_osz; - rsize[2] = in_gsz; + bool is_inner_masked = load_mask(inner_mask, texel, size) > 0.0f; + bool is_outer_masked = load_mask(outer_mask, texel, size) > 0.0f; + + bool is_inner_boundary = is_inner_masked && has_inner_non_masked_neighbors && + (is_outer_masked || include_all_inner_edges_); + bool is_outer_boundary = is_outer_masked && !is_inner_masked && + has_outer_non_masked_neighbors; + + int2 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary); + int2 outer_jump_flooding_value = initialize_jump_flooding_value(texel, is_outer_boundary); + + const size_t output_index = size_t(texel.y) * size.x + texel.x; + inner_boundary[output_index] = inner_jump_flooding_value; + outer_boundary[output_index] = outer_jump_flooding_value; + } + } + }); } -static void do_adjacentEdgeDetection(uint t, - uint rw, - const uint *limask, - const uint *lomask, - uint *lres, - float *res, - uint *rsize, - uint in_isz, - uint in_osz, - uint in_gsz) +void DoubleEdgeMaskOperation::compute_gradient(const float *inner_mask_buffer, + const float *outer_mask_buffer, + MutableSpan flooded_inner_boundary, + MutableSpan flooded_outer_boundary, + float *output_mask) { - int x; /* Pixel loop counter. */ - int a; /* Pixel loop counter. */ - int dx; /* Delta x. */ - int pix_prevRow; /* Pixel one row behind the one we are testing in a loop. */ - int pix_nextRow; /* Pixel one row in front of the one we are testing in a loop. */ - int pix_prevCol; /* Pixel one column behind the one we are testing in a loop. */ - int pix_nextCol; /* Pixel one column in front of the one we are testing in a loop. */ - /* Test all rows between the FIRST and LAST rows, excluding left and right edges */ - for (x = (t - rw) + 1, dx = x - (rw - 2); dx > rw; x -= rw, dx -= rw) { - a = x - 2; - pix_prevRow = a + rw; - pix_nextRow = a - rw; - pix_prevCol = a + 1; - pix_nextCol = a - 1; - while (a > dx - 2) { - if (!limask[a]) { /* If the inner mask is empty. */ - if (lomask[a]) { /* If the outer mask is full. */ - /* - * Next we test all 4 directions around the current pixel: next/previous/up/down - * The test ensures that the outer mask is empty and that the inner mask - * is also empty. If both conditions are true for any one of the 4 adjacent pixels - * then the current pixel is counted as being a true outer edge pixel. - */ - if ((!lomask[pix_nextCol] && !limask[pix_nextCol]) || - (!lomask[pix_prevCol] && !limask[pix_prevCol]) || - (!lomask[pix_nextRow] && !limask[pix_nextRow]) || - (!lomask[pix_prevRow] && !limask[pix_prevRow])) - { - in_osz++; /* Increment the outer boundary pixel count. */ - lres[a] = 3; /* Flag pixel as part of outer edge. */ - } - else { /* It's not a boundary pixel, but it is a gradient pixel. */ - in_gsz++; /* Increment the gradient pixel count. */ - lres[a] = 2; /* Flag pixel as gradient. */ - } - } - } - else { - if ((!limask[pix_nextCol] && lomask[pix_nextCol]) || - (!limask[pix_prevCol] && lomask[pix_prevCol]) || - (!limask[pix_nextRow] && lomask[pix_nextRow]) || - (!limask[pix_prevRow] && lomask[pix_prevRow])) - { - in_isz++; /* Increment the inner boundary pixel count. */ - lres[a] = 4; /* Flag pixel as part of inner edge. */ - } - else { - res[a] = 1.0f; /* Pixel is part of inner mask, but not at an edge. */ - } - } - a--; - pix_prevRow--; /* Advance all four "surrounding" pixel pointers. */ - pix_nextRow--; - pix_prevCol--; - pix_nextCol--; - } - } + const int2 size = int2(this->get_width(), this->get_height()); + threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) { + for (const int64_t y : sub_y_range) { + for (const int64_t x : IndexRange(size.x)) { + int2 texel = int2(x, y); + const size_t index = size_t(texel.y) * size.x + texel.x; - rsize[0] = in_isz; /* Fill in our return sizes for edges + fill. */ - rsize[1] = in_osz; - rsize[2] = in_gsz; + float inner_mask = inner_mask_buffer[index]; + if (inner_mask != 0.0f) { + output_mask[index] = 1.0f; + continue; + } + + float outer_mask = outer_mask_buffer[index]; + if (outer_mask == 0.0f) { + output_mask[index] = 0.0f; + continue; + } + + int2 inner_boundary_texel = flooded_inner_boundary[index]; + int2 outer_boundary_texel = flooded_outer_boundary[index]; + float distance_to_inner = math::distance(float2(texel), float2(inner_boundary_texel)); + float distance_to_outer = math::distance(float2(texel), float2(outer_boundary_texel)); + + float gradient = distance_to_outer / (distance_to_outer + distance_to_inner); + + output_mask[index] = gradient; + } + } + }); } -static void do_createEdgeLocationBuffer(uint t, - uint rw, - const uint *lres, - float *res, - ushort *gbuf, - uint *inner_edge_offset, - uint *outer_edge_offset, - uint isz, - uint gsz) +void DoubleEdgeMaskOperation::compute_double_edge_mask(const float *inner_mask, + const float *outer_mask, + float *output_mask) { - int x; /* Pixel loop counter. */ - int a; /* Temporary pixel index buffer loop counter. */ - uint ud; /* Unscaled edge distance. */ - uint dmin; /* Minimum edge distance. */ - - uint rsl; /* Long used for finding fast `1.0/sqrt`. */ - uint gradient_fill_offset; - - /* For looping inner edge pixel indexes, represents current position from offset. */ - uint inner_accum = 0; - /* For looping outer edge pixel indexes, represents current position from offset. */ - uint outer_accum = 0; - /* For looping gradient pixel indexes, represents current position from offset. */ - uint gradient_accum = 0; - - /* Disable clang-format to prevent line-wrapping. */ - /* clang-format off */ - /* - * Here we compute the size of buffer needed to hold (row,col) coordinates - * for each pixel previously determined to be either gradient, inner edge, - * or outer edge. - * - * Allocation is done by requesting 4 bytes "sizeof(int)" per pixel, even - * though gbuf[] is declared as `(ushort *)` (2 bytes) because we don't - * store the pixel indexes, we only store x,y location of pixel in buffer. - * - * This does make the assumption that x and y can fit in 16 unsigned bits - * so if Blender starts doing renders greater than 65536 in either direction - * this will need to allocate gbuf[] as uint *and allocate 8 bytes - * per flagged pixel. - * - * In general, the buffer on-screen: - * - * Example: 9 by 9 pixel block - * - * `.` = Pixel non-white in both outer and inner mask. - * `o` = Pixel white in outer, but not inner mask, adjacent to "." pixel. - * `g` = Pixel white in outer, but not inner mask, not adjacent to "." pixel. - * `i` = Pixel white in inner mask, adjacent to "g" or "." pixel. - * `F` = Pixel white in inner mask, only adjacent to other pixels white in the inner mask. - * - * - * ......... <----- pixel #80 - * ..oooo... - * .oggggo.. - * .oggiggo. - * .ogi_figo. - * .oggiggo. - * .oggggo.. - * ..oooo... - * pixel #00 -----> ......... - * - * gsz = 18 (18 "g" pixels above) - * isz = 4 (4 "i" pixels above) - * osz = 18 (18 "o" pixels above) - * - * - * The memory in gbuf[] after filling will look like this: - * - * gradient_fill_offset (0 pixels) inner_edge_offset (18 pixels) outer_edge_offset (22 pixels) - * / / / - * / / / - * |X Y X Y X Y X Y > <----------------> <------------------------> <----------------+ - * |0 2 4 6 8 10 12 14 > ... <68 70 72 74 > ... <80 82 84 86 88 90 > ... <152 154 156 158 | <- bytes - * +--------------------------------> <----------------> <------------------------> <----------------+ - * |g0 g0 g1 g1 g2 g2 g3 g3 > = 0; x--) { - gradient_fill_offset = x << 1; - t = gbuf[gradient_fill_offset]; /* Calculate column of pixel indexed by `gbuf[x]`. */ - fsz = gbuf[gradient_fill_offset + 1]; /* Calculate row of pixel indexed by `gbuf[x]`. */ - dmin = 0xffffffff; /* Reset min distance to edge pixel. */ - /* Loop through all outer edge buffer pixels. */ - for (a = outer_edge_offset + osz - 1; a >= outer_edge_offset; a--) { - ud = a << 1; - dy = t - gbuf[ud]; /* Set dx to gradient pixel column - outer edge pixel row. */ - dx = fsz - gbuf[ud + 1]; /* Set dy to gradient pixel row - outer edge pixel column. */ - ud = dx * dx + dy * dy; /* Compute sum of squares. */ - if (ud < dmin) { /* If our new sum of squares is less than the current minimum. */ - dmin = ud; /* Set a new minimum equal to the new lower value. */ - } - } - odist = float(dmin); /* Cast outer min to a float. */ - rsf = odist * 0.5f; - rsl = *(uint *)&odist; /* Use some peculiar properties of the way bits are stored. */ - rsl = 0x5f3759df - (rsl >> 1); /* In floats vs. uints to compute an approximate. */ - odist = *(float *)&rsl; /* Reciprocal square root. */ - odist = odist * (rsopf - (rsf * odist * - odist)); /* -- This line can be iterated for more accuracy. -- */ - dmin = 0xffffffff; /* Reset min distance to edge pixel. */ - /* Loop through all inside edge pixels. */ - for (a = inner_edge_offset + isz - 1; a >= inner_edge_offset; a--) { - ud = a << 1; - dy = t - gbuf[ud]; /* Compute delta in Y from gradient pixel to inside edge pixel. */ - dx = fsz - gbuf[ud + 1]; /* Compute delta in X from gradient pixel to inside edge pixel. */ - ud = dx * dx + dy * dy; /* Compute sum of squares. */ - /* If our new sum of squares is less than the current minimum we've found. */ - if (ud < dmin) { - dmin = ud; /* Set a new minimum equal to the new lower value. */ - } - } - - /* Cast inner min to a float. */ - idist = float(dmin); - rsf = idist * 0.5f; - rsl = *(uint *)&idist; - - /* See notes above. */ - rsl = 0x5f3759df - (rsl >> 1); - idist = *(float *)&rsl; - idist = idist * (rsopf - (rsf * idist * idist)); - - /* NOTE: once again that since we are using reciprocals of distance values our - * proportion is already the correct intensity, and does not need to be - * subtracted from 1.0 like it would have if we used real distances. */ - - /* Here we reconstruct the pixel's memory location in the CompBuf by - * `Pixel Index = Pixel Column + ( Pixel Row * Row Width )`. */ - res[gbuf[gradient_fill_offset + 1] + (gbuf[gradient_fill_offset] * rw)] = - (idist / (idist + odist)); /* Set intensity. */ - } -} - -/* End of copy. */ - -void DoubleEdgeMaskOperation::do_double_edge_mask(float *imask, float *omask, float *res) -{ - uint *lres; /* Pointer to output pixel buffer (for bit operations). */ - uint *limask; /* Pointer to inner mask (for bit operations). */ - uint *lomask; /* Pointer to outer mask (for bit operations). */ - - int rw; /* Pixel row width. */ - int t; /* Total number of pixels in buffer - 1 (used for loop starts). */ - int fsz; /* Size of the frame. */ - - uint isz = 0; /* Size (in pixels) of inside edge pixel index buffer. */ - uint osz = 0; /* Size (in pixels) of outside edge pixel index buffer. */ - uint gsz = 0; /* Size (in pixels) of gradient pixel index buffer. */ - uint rsize[3]; /* Size storage to pass to helper functions. */ - uint inner_edge_offset = 0; /* Offset into final buffer where inner edge pixel indexes start. */ - uint outer_edge_offset = 0; /* Offset into final buffer where outer edge pixel indexes start. */ - - ushort *gbuf; /* Gradient/inner/outer pixel location index buffer. */ - - if (true) { /* If both input sockets have some data coming in... */ - - rw = this->get_width(); /* Width of a row of pixels. */ - t = (rw * this->get_height()) - 1; /* Determine size of the frame. */ - - /* Clear output buffer (not all pixels will be written later). */ - memset(res, 0, sizeof(float) * (t + 1)); - - lres = (uint *)res; /* Pointer to output buffer (for bit level ops).. */ - limask = (uint *)imask; /* Pointer to input mask (for bit level ops).. */ - lomask = (uint *)omask; /* Pointer to output mask (for bit level ops).. */ - - /* - * The whole buffer is broken up into 4 parts. The four CORNERS, the FIRST and LAST rows, the - * LEFT and RIGHT edges (excluding the corner pixels), and all OTHER rows. - * This allows for quick computation of outer edge pixels where - * a screen edge pixel is marked to be gradient. - * - * The pixel type (gradient vs inner-edge vs outer-edge) tests change - * depending on the user selected "Inner Edge Mode" and the user selected - * "Buffer Edge Mode" on the node's GUI. There are 4 sets of basically the - * same algorithm: - * - * 1.) Inner Edge -> Adjacent Only - * Buffer Edge -> Keep Inside - * - * 2.) Inner Edge -> Adjacent Only - * Buffer Edge -> Bleed Out - * - * 3.) Inner Edge -> All - * Buffer Edge -> Keep Inside - * - * 4.) Inner Edge -> All - * Buffer Edge -> Bleed Out - * - * Each version has slightly different criteria for detecting an edge pixel. - */ - if (adjacent_only_) { /* If "adjacent only" inner edge mode is turned on. */ - if (keep_inside_) { /* If "keep inside" buffer edge mode is turned on. */ - do_adjacentKeepBorders(t, rw, limask, lomask, lres, res, rsize); - } - else { /* "bleed out" buffer edge mode is turned on. */ - do_adjacentBleedBorders(t, rw, limask, lomask, lres, res, rsize); - } - /* Set up inner edge, outer edge, and gradient buffer sizes after border pass. */ - isz = rsize[0]; - osz = rsize[1]; - gsz = rsize[2]; - /* Detect edges in all non-border pixels in the buffer. */ - do_adjacentEdgeDetection(t, rw, limask, lomask, lres, res, rsize, isz, osz, gsz); - } - else { /* "all" inner edge mode is turned on. */ - if (keep_inside_) { /* If "keep inside" buffer edge mode is turned on. */ - do_allKeepBorders(t, rw, limask, lomask, lres, res, rsize); - } - else { /* "bleed out" buffer edge mode is turned on. */ - do_allBleedBorders(t, rw, limask, lomask, lres, res, rsize); - } - /* Set up inner edge, outer edge, and gradient buffer sizes after border pass. */ - isz = rsize[0]; - osz = rsize[1]; - gsz = rsize[2]; - /* Detect edges in all non-border pixels in the buffer. */ - do_allEdgeDetection(t, rw, limask, lomask, lres, res, rsize, isz, osz, gsz); - } - - /* Set edge and gradient buffer sizes once again... - * the sizes in rsize[] may have been modified - * by the `do_*EdgeDetection()` function. */ - isz = rsize[0]; - osz = rsize[1]; - gsz = rsize[2]; - - /* Calculate size of pixel index buffer needed. */ - fsz = gsz + isz + osz; - /* Allocate edge/gradient pixel index buffer. */ - gbuf = (ushort *)MEM_callocN(sizeof(ushort) * fsz * 2, "DEM"); - - do_createEdgeLocationBuffer( - t, rw, lres, res, gbuf, &inner_edge_offset, &outer_edge_offset, isz, gsz); - do_fillGradientBuffer(rw, res, gbuf, isz, osz, gsz, inner_edge_offset, outer_edge_offset); - - /* Free the gradient index buffer. */ - MEM_freeN(gbuf); - } + const int2 size = int2(this->get_width(), this->get_height()); + Array inner_boundary(size_t(size.x) * size.y); + Array outer_boundary(size_t(size.x) * size.y); + compute_boundary(inner_mask, outer_mask, inner_boundary, outer_boundary); + Array flooded_inner_boundary = jump_flooding(inner_boundary, size); + Array flooded_outer_boundary = jump_flooding(outer_boundary, size); + compute_gradient( + inner_mask, outer_mask, flooded_inner_boundary, flooded_outer_boundary, output_mask); } DoubleEdgeMaskOperation::DoubleEdgeMaskOperation() @@ -1291,8 +145,8 @@ DoubleEdgeMaskOperation::DoubleEdgeMaskOperation() this->add_output_socket(DataType::Value); input_inner_mask_ = nullptr; input_outer_mask_ = nullptr; - adjacent_only_ = false; - keep_inside_ = false; + include_all_inner_edges_ = false; + include_edges_of_image_ = false; flags_.complex = true; flags_.can_be_constant = true; is_output_rendered_ = false; @@ -1335,7 +189,7 @@ void *DoubleEdgeMaskOperation::initialize_tile_data(rcti *rect) __func__); float *imask = inner_mask->get_buffer(); float *omask = outer_mask->get_buffer(); - do_double_edge_mask(imask, omask, data); + compute_double_edge_mask(imask, omask, data); cached_instance_ = data; } unlock_mutex(); @@ -1381,9 +235,8 @@ void DoubleEdgeMaskOperation::update_memory_buffer(MemoryBuffer *output, BLI_assert(output->get_width() == this->get_width()); BLI_assert(output->get_height() == this->get_height()); - /* TODO(manzanilla): Once tiled implementation is removed, use execution system to run - * multi-threaded where possible. */ - do_double_edge_mask(inner_mask->get_buffer(), outer_mask->get_buffer(), output->get_buffer()); + compute_double_edge_mask( + inner_mask->get_buffer(), outer_mask->get_buffer(), output->get_buffer()); is_output_rendered_ = true; if (inner_mask != input_inner_mask) { diff --git a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h index 32c31e7eae8..3339aa0133b 100644 --- a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h +++ b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h @@ -4,6 +4,8 @@ #pragma once +#include "BLI_span.hh" + #include "COM_MultiThreadedOperation.h" namespace blender::compositor { @@ -15,8 +17,8 @@ class DoubleEdgeMaskOperation : public NodeOperation { */ SocketReader *input_outer_mask_; SocketReader *input_inner_mask_; - bool adjacent_only_; - bool keep_inside_; + bool include_all_inner_edges_; + bool include_edges_of_image_; /* TODO(manzanilla): To be removed with tiled implementation. */ float *cached_instance_; @@ -26,7 +28,21 @@ class DoubleEdgeMaskOperation : public NodeOperation { public: DoubleEdgeMaskOperation(); - void do_double_edge_mask(float *imask, float *omask, float *res); + void compute_boundary(const float *inner_mask, + const float *outer_mask, + MutableSpan inner_boundary, + MutableSpan outer_boundary); + + void compute_gradient(const float *inner_mask_buffer, + const float *outer_mask_buffer, + MutableSpan flooded_inner_boundary, + MutableSpan flooded_outer_boundary, + float *output_mask); + + void compute_double_edge_mask(const float *inner_mask, + const float *outer_mask, + float *output_mask); + /** * The inner loop of this operation. */ @@ -48,13 +64,13 @@ class DoubleEdgeMaskOperation : public NodeOperation { ReadBufferOperation *read_operation, rcti *output) override; - void set_adjacent_only(bool adjacent_only) + void set_include_all_inner_edges(bool include_all_inner_edges) { - adjacent_only_ = adjacent_only; + include_all_inner_edges_ = include_all_inner_edges; } - void set_keep_inside(bool keep_inside) + void set_include_edges_of_image(bool include_edges_of_image) { - keep_inside_ = keep_inside; + include_edges_of_image_ = include_edges_of_image; } void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl index 1822089f096..13b58ab13ef 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl @@ -54,10 +54,11 @@ void main() /* The pixels at the boundary are those that are masked and have non masked neighbors. The inner * boundary has a specialization, if include_all_inner_edges is false, only inner boundaries that - * lie inside the outer mask will be considered a boundary. */ + * lie inside the outer mask will be considered a boundary. The outer boundary is only considered + * if it is not inside the inner mask. */ bool is_inner_boundary = is_inner_masked && has_inner_non_masked_neighbors && (is_outer_masked || include_all_inner_edges); - bool is_outer_boundary = is_outer_masked && has_outer_non_masked_neighbors; + bool is_outer_boundary = is_outer_masked && !is_inner_masked && has_outer_non_masked_neighbors; /* Encode the boundary information in the format expected by the jump flooding algorithm. */ ivec2 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary);