Blender V5.0
node_composite_crop.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2006 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_bounds.hh"
10#include "BLI_bounds_types.hh"
12
13#include "DNA_node_types.h"
14
15#include "BKE_node.hh"
16
17#include "GPU_shader.hh"
18
19#include "COM_node_operation.hh"
20#include "COM_utilities.hh"
21
23
24/* **************** Crop ******************** */
25
27
29{
30 b.use_custom_socket_order();
31 b.allow_any_socket_order();
32 b.add_input<decl::Color>("Image")
33 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
34 .hide_value()
35 .structure_type(StructureType::Dynamic);
36 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
37
38 b.add_input<decl::Int>("X")
39 .default_value(0)
40 .min(0)
41
42 .description("The X position of the lower left corner of the crop region");
43 b.add_input<decl::Int>("Y")
44 .default_value(0)
45 .min(0)
46
47 .description("The Y position of the lower left corner of the crop region");
48 b.add_input<decl::Int>("Width")
49 .default_value(1920)
50 .min(1)
51
52 .description("The width of the crop region");
53 b.add_input<decl::Int>("Height")
54 .default_value(1080)
55 .min(1)
56
57 .description("The height of the crop region");
58 b.add_input<decl::Bool>("Alpha Crop")
59 .default_value(false)
60
62 "Sets the areas outside of the crop region to be transparent instead of actually "
63 "cropping the size of the image");
64}
65
66using namespace blender::compositor;
67
69 public:
71
72 void execute() override
73 {
74 if (this->is_identity()) {
75 const Result &input = this->get_input("Image");
76 Result &output = this->get_result("Image");
77 output.share_data(input);
78 return;
79 }
80
81 if (this->is_alpha_crop()) {
82 this->execute_alpha_crop();
83 }
84 else {
85 this->execute_image_crop();
86 }
87 }
88
89 /* Crop by replacing areas outside of the cropping bounds with zero alpha. The output have the
90 * same domain as the input image. */
92 {
93 if (this->context().use_gpu()) {
95 }
96 else {
98 }
99 }
100
102 {
103 gpu::Shader *shader = this->context().get_shader("compositor_alpha_crop");
104 GPU_shader_bind(shader);
105
107 GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min);
108 GPU_shader_uniform_2iv(shader, "upper_bound", bounds.max);
109
110 const Result &input_image = this->get_input("Image");
111 input_image.bind_as_texture(shader, "input_tx");
112
113 const Domain domain = this->compute_domain();
114
115 Result &output_image = this->get_result("Image");
116 output_image.allocate_texture(domain);
117 output_image.bind_as_image(shader, "output_img");
118
120
121 input_image.unbind_as_texture();
122 output_image.unbind_as_image();
124 }
125
127 {
129
130 const Result &input = this->get_input("Image");
131
132 const Domain domain = this->compute_domain();
133 Result &output = this->get_result("Image");
134 output.allocate_texture(domain);
135
136 parallel_for(domain.size, [&](const int2 texel) {
137 /* The lower bound is inclusive and upper bound is exclusive. */
138 bool is_inside = texel.x >= bounds.min.x && texel.y >= bounds.min.y &&
139 texel.x < bounds.max.x && texel.y < bounds.max.y;
140 /* Write the pixel color if it is inside the cropping region, otherwise, write zero. */
141 float4 color = is_inside ? input.load_pixel<float4>(texel) : float4(0.0f);
142 output.store_pixel(texel, color);
143 });
144 }
145
146 /* Crop the image into a new size that matches the cropping bounds. */
148 {
149 if (this->context().use_gpu()) {
151 }
152 else {
154 }
155 }
156
158 {
160
161 gpu::Shader *shader = this->context().get_shader("compositor_image_crop");
162 GPU_shader_bind(shader);
163
164 GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min);
165
166 const Result &input_image = this->get_input("Image");
167 input_image.bind_as_texture(shader, "input_tx");
168
169 const int2 size = bounds.size();
170
171 Result &output_image = this->get_result("Image");
172 output_image.allocate_texture(Domain(size, this->compute_domain().transformation));
173 output_image.bind_as_image(shader, "output_img");
174
176
177 input_image.unbind_as_texture();
178 output_image.unbind_as_image();
180 }
181
183 {
185
186 const Result &input = this->get_input("Image");
187
188 const int2 size = bounds.size();
189 Result &output = this->get_result("Image");
190 output.allocate_texture(Domain(size, this->compute_domain().transformation));
191
192 parallel_for(size, [&](const int2 texel) {
193 output.store_pixel(texel, input.load_pixel<float4>(texel + bounds.min));
194 });
195 }
196
197 /* Returns true if the operation does nothing and the input can be passed through. */
199 {
200 const Result &input = this->get_input("Image");
201 /* Single value inputs can't be cropped and are returned as is. */
202 if (input.is_single_value()) {
203 return true;
204 }
205
207 const int2 input_size = input.domain().size;
208 /* The cropping bounds cover the whole image, so no cropping happens. */
209 if (bounds.min == int2(0) && bounds.max == input_size) {
210 return true;
211 }
212
213 return false;
214 }
215
217 {
218 const int2 input_size = this->get_input("Image").domain().size;
219
220 const int x = math::clamp(
221 this->get_input("X").get_single_value_default(0), 0, input_size.x - 1);
222 const int y = math::clamp(
223 this->get_input("Y").get_single_value_default(0), 0, input_size.y - 1);
224 const int width = math::max(1, this->get_input("Width").get_single_value_default(100));
225 const int height = math::max(1, this->get_input("Height").get_single_value_default(100));
226
227 const Bounds<int2> input_bounds = Bounds<int2>(int2(0), input_size);
228
229 const Bounds<int2> crop_bounds = Bounds<int2>(int2(x, y), int2(x + width, y + height));
230 return *bounds::intersect(crop_bounds, input_bounds);
231 }
232
233 /* If true, the region outside of the cropping bounds will be set to a zero alpha value instead
234 * of actually cropping the size of the image. */
236 {
237 return this->get_input("Alpha Crop").get_single_value_default(false);
238 }
239};
240
242{
243 return new CropOperation(context, node);
244}
245
246} // namespace blender::nodes::node_composite_crop_cc
247
249{
250 namespace file_ns = blender::nodes::node_composite_crop_cc;
251
252 static blender::bke::bNodeType ntype;
253
254 cmp_node_type_base(&ntype, "CompositorNodeCrop", CMP_NODE_CROP);
255 ntype.ui_name = "Crop";
256 ntype.ui_description =
257 "Crops image to a smaller region, either making the cropped area transparent or resizing "
258 "the image";
259 ntype.enum_name_legacy = "CROP";
261 ntype.declare = file_ns::cmp_node_crop_declare;
262 ntype.get_compositor_operation = file_ns::get_compositor_operation;
263
265}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:455
#define CMP_NODE_CROP
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_shader_uniform_2iv(blender::gpu::Shader *sh, const char *name, const int data[2])
#define NOD_REGISTER_NODE(REGISTER_FUNC)
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
gpu::Shader * get_shader(const char *info_name, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_result(StringRef identifier)
Definition operation.cc:39
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
void share_data(const Result &source)
Definition result.cc:523
T get_single_value_default(const T &default_value) const
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
void unbind_as_texture() const
Definition result.cc:511
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
const Domain & domain() const
void unbind_as_image() const
Definition result.cc:517
void bind_as_image(gpu::Shader *shader, const char *image_name, bool read=false) const
Definition result.cc:498
bool is_single_value() const
Definition result.cc:758
#define input
#define output
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
std::optional< Bounds< T > > intersect(const Bounds< T > &a, const Bounds< T > &b)
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
void parallel_for(const int2 range, const Function &function)
T clamp(const T &a, const T &min, const T &max)
T max(const T &a, const T &b)
static void cmp_node_crop_declare(NodeDeclarationBuilder &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
static void register_node_type_cmp_crop()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362