Blender V5.0
node_composite_scale.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_listbase.h"
11#include "BLI_math_base.hh"
12#include "BLI_math_matrix.hh"
14#include "BLI_math_vector.hh"
16#include "BLI_string.h"
17
18#include "DNA_node_types.h"
19
20#include "RNA_enum_types.hh"
21
22#include "GPU_shader.hh"
23#include "GPU_texture.hh"
24
25#include "COM_domain.hh"
26#include "COM_node_operation.hh"
27#include "COM_utilities.hh"
28
30
32
33static const EnumPropertyItem type_items[] = {
34 {CMP_NODE_SCALE_RELATIVE, "RELATIVE", 0, N_("Relative"), ""},
35 {CMP_NODE_SCALE_ABSOLUTE, "ABSOLUTE", 0, N_("Absolute"), ""},
36 {CMP_NODE_SCALE_RENDER_PERCENT, "SCENE_SIZE", 0, N_("Scene Size"), ""},
37 {CMP_NODE_SCALE_RENDER_SIZE, "RENDER_SIZE", 0, N_("Render Size"), ""},
38 {0, nullptr, 0, nullptr, nullptr},
39};
40
41/* Matches bgpic_camera_frame_items[]. */
43 {CMP_NODE_SCALE_RENDER_SIZE_STRETCH, "STRETCH", 0, N_("Stretch"), ""},
44 {CMP_NODE_SCALE_RENDER_SIZE_FIT, "FIT", 0, N_("Fit"), ""},
45 {CMP_NODE_SCALE_RENDER_SIZE_CROP, "CROP", 0, N_("Crop"), ""},
46 {0, nullptr, 0, nullptr, nullptr},
47};
48
50{
51 b.use_custom_socket_order();
52 b.allow_any_socket_order();
53
54 b.add_input<decl::Color>("Image")
55 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
56 .hide_value()
58 .structure_type(StructureType::Dynamic);
59 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
60
61 b.add_input<decl::Menu>("Type").default_value(CMP_NODE_SCALE_RELATIVE).static_items(type_items);
62 b.add_input<decl::Float>("X")
63 .default_value(1.0f)
64 .min(0.0001f)
65 .max(CMP_SCALE_MAX)
66 .structure_type(StructureType::Dynamic)
67 .usage_by_menu("Type", {CMP_NODE_SCALE_RELATIVE, CMP_NODE_SCALE_ABSOLUTE});
68 b.add_input<decl::Float>("Y")
69 .default_value(1.0f)
70 .min(0.0001f)
71 .max(CMP_SCALE_MAX)
72 .structure_type(StructureType::Dynamic)
73 .usage_by_menu("Type", {CMP_NODE_SCALE_RELATIVE, CMP_NODE_SCALE_ABSOLUTE});
74 b.add_input<decl::Menu>("Frame Type")
76 .static_items(frame_type_items)
77 .usage_by_menu("Type", CMP_NODE_SCALE_RENDER_SIZE)
79 .description("How the image fits in the camera frame");
80
81 PanelDeclarationBuilder &sampling_panel = b.add_panel("Sampling").default_closed(true);
82 sampling_panel.add_input<decl::Menu>("Interpolation")
86 .description("Interpolation method");
87 sampling_panel.add_input<decl::Menu>("Extension X")
88 .default_value(CMP_NODE_EXTENSION_MODE_CLIP)
91 .description("The extension mode applied to the X axis");
92 sampling_panel.add_input<decl::Menu>("Extension Y")
93 .default_value(CMP_NODE_EXTENSION_MODE_CLIP)
96 .description("The extension mode applied to the Y axis");
97}
98
99static void node_composit_init_scale(bNodeTree * /*ntree*/, bNode *node)
100{
101 /* Unused, kept for forward compatibility. */
103 node->storage = data;
104}
105
106using namespace blender::compositor;
107
109 public:
111
112 void execute() override
113 {
114 if (is_variable_size()) {
116 }
117 else {
119 }
120 }
121
123 {
124 const float2 scale = this->get_scale();
125 const float3x3 transformation = math::from_scale<float3x3>(scale);
126
127 const Result &input = this->get_input("Image");
128 Result &output = this->get_result("Image");
129 output.share_data(input);
130 output.transform(transformation);
131
132 output.get_realization_options().interpolation = this->get_interpolation();
135 }
136
138 {
139 if (this->context().use_gpu()) {
141 }
142 else {
144 }
145 }
146
148 {
149 gpu::Shader *shader = this->context().get_shader(this->get_shader_name());
150 GPU_shader_bind(shader);
151
152 Result &input = get_input("Image");
153 /* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
154 * cases, as the logic used by the bicubic realization shader expects textures to use bilinear
155 * interpolation. */
156 const Interpolation interpolation = this->get_interpolation();
157 const ExtensionMode extension_mode_x = this->get_extension_mode_x();
158 const ExtensionMode extension_mode_y = this->get_extension_mode_y();
159
160 /* For now the EWA sampling falls back to bicubic interpolation. */
161 const bool use_bilinear = ELEM(interpolation, Interpolation::Bilinear, Interpolation::Bicubic);
162 GPU_texture_filter_mode(input, use_bilinear);
165 input.bind_as_texture(shader, "input_tx");
166
167 Result &x_scale = get_input("X");
168 x_scale.bind_as_texture(shader, "x_scale_tx");
169
170 Result &y_scale = get_input("Y");
171 y_scale.bind_as_texture(shader, "y_scale_tx");
172
173 Result &output = get_result("Image");
174 const Domain domain = compute_domain();
175 output.allocate_texture(domain);
176 output.bind_as_image(shader, "output_img");
177
179
180 input.unbind_as_texture();
181 x_scale.unbind_as_texture();
182 y_scale.unbind_as_texture();
183 output.unbind_as_image();
185 }
186
188 {
189 const Result &input = this->get_input("Image");
190 const Result &x_scale = this->get_input("X");
191 const Result &y_scale = this->get_input("Y");
192
193 Result &output = this->get_result("Image");
194 const Interpolation interpolation = this->get_interpolation();
195 const ExtensionMode extension_mode_x = this->get_extension_mode_x();
196 const ExtensionMode extension_mode_y = this->get_extension_mode_y();
197 const Domain domain = compute_domain();
198 const int2 size = domain.size;
199 output.allocate_texture(domain);
200
201 parallel_for(size, [&](const int2 texel) {
202 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
203 float2 center = float2(0.5f);
204
205 float2 scale = float2(x_scale.load_pixel<float, true>(texel),
206 y_scale.load_pixel<float, true>(texel));
207 float2 scaled_coordinates = center +
208 (coordinates - center) / math::max(scale, float2(0.0001f));
209
210 output.store_pixel(
211 texel,
212 input.sample(scaled_coordinates, interpolation, extension_mode_x, extension_mode_y));
213 });
214 }
215
216 const char *get_shader_name() const
217 {
219 return "compositor_scale_variable_bicubic";
220 }
221 return "compositor_scale_variable";
222 }
223
225 {
226 const Result &input = this->get_input("Interpolation");
227 const MenuValue default_menu_value = MenuValue(CMP_NODE_INTERPOLATION_BILINEAR);
228 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
229 const CMPNodeInterpolation interpolation = static_cast<CMPNodeInterpolation>(menu_value.value);
230 switch (interpolation) {
238 }
239
241 }
242
244 {
245 const Result &input = this->get_input("Extension X");
246 const MenuValue default_menu_value = MenuValue(CMP_NODE_EXTENSION_MODE_CLIP);
247 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
248 const CMPExtensionMode extension_x = static_cast<CMPExtensionMode>(menu_value.value);
249 switch (extension_x) {
251 return ExtensionMode::Clip;
256 }
257
258 return ExtensionMode::Clip;
259 }
260
262 {
263 const Result &input = this->get_input("Extension Y");
264 const MenuValue default_menu_value = MenuValue(CMP_NODE_EXTENSION_MODE_CLIP);
265 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
266 const CMPExtensionMode extension_y = static_cast<CMPExtensionMode>(menu_value.value);
267 switch (extension_y) {
269 return ExtensionMode::Clip;
274 }
275
276 return ExtensionMode::Clip;
277 }
278
280 {
281 switch (get_type()) {
283 return get_scale_relative();
285 return get_scale_absolute();
289 return get_scale_render_size();
290 }
291
292 return float2(1.0f);
293 }
294
295 /* Scale by the input factors. */
297 {
298 return float2(get_input("X").get_single_value_default(1.0f),
299 get_input("Y").get_single_value_default(1.0f));
300 }
301
302 /* Scale such that the new size matches the input absolute size. */
304 {
305 const float2 input_size = float2(get_input("Image").domain().size);
306 const float2 absolute_size = float2(get_input("X").get_single_value_default(1.0f),
307 get_input("Y").get_single_value_default(1.0f));
308 return absolute_size / input_size;
309 }
310
311 /* Scale by the render resolution percentage. */
313 {
314 return float2(context().get_render_percentage());
315 }
316
318 {
319 if (!context().is_valid_compositing_region()) {
320 return float2(1.0f);
321 }
322
323 switch (get_frame_type()) {
330 }
331
332 return float2(1.0f);
333 }
334
335 /* Scale such that the new size matches the render size. Since the input is freely scaled, it is
336 * potentially stretched, hence the name. */
338 {
339 const float2 input_size = float2(get_input("Image").domain().size);
340 const float2 render_size = float2(context().get_compositing_region_size());
341 return render_size / input_size;
342 }
343
344 /* Scale such that the dimension with the smaller scaling factor matches that of the render size
345 * while maintaining the input's aspect ratio. Since the other dimension is guaranteed not to
346 * exceed the render size region due to its larger scaling factor, the image is said to be fit
347 * inside that region, hence the name. */
349 {
350 const float2 input_size = float2(get_input("Image").domain().size);
351 const float2 render_size = float2(context().get_compositing_region_size());
352 const float2 scale = render_size / input_size;
353 return float2(math::min(scale.x, scale.y));
354 }
355
356 /* Scale such that the dimension with the larger scaling factor matches that of the render size
357 * while maintaining the input's aspect ratio. Since the other dimension is guaranteed to exceed
358 * the render size region due to its lower scaling factor, the image will be cropped inside that
359 * region, hence the name. */
361 {
362 const float2 input_size = float2(get_input("Image").domain().size);
363 const float2 render_size = float2(context().get_compositing_region_size());
364 const float2 scale = render_size / input_size;
365 return float2(math::max(scale.x, scale.y));
366 }
367
369 {
370 /* Only relative scaling can be variable. */
372 return false;
373 }
374
375 return !get_input("X").is_single_value() || !get_input("Y").is_single_value();
376 }
377
379 {
380 const Result &input = this->get_input("Type");
381 const MenuValue default_menu_value = MenuValue(CMP_NODE_SCALE_RELATIVE);
382 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
383 return static_cast<CMPNodeScaleMethod>(menu_value.value);
384 }
385
387 {
388 const Result &input = this->get_input("Frame Type");
389 const MenuValue default_menu_value = MenuValue(CMP_NODE_SCALE_RENDER_SIZE_STRETCH);
390 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
391 return static_cast<CMPNodeScaleRenderSizeMethod>(menu_value.value);
392 }
393};
394
396{
397 return new ScaleOperation(context, node);
398}
399
400} // namespace blender::nodes::node_composite_scale_cc
401
403{
404 namespace file_ns = blender::nodes::node_composite_scale_cc;
405
406 static blender::bke::bNodeType ntype;
407
408 cmp_node_type_base(&ntype, "CompositorNodeScale", CMP_NODE_SCALE);
409 ntype.ui_name = "Scale";
410 ntype.ui_description = "Change the size of the image";
411 ntype.enum_name_legacy = "SCALE";
413 ntype.declare = file_ns::cmp_node_scale_declare;
414 ntype.initfunc = file_ns::node_composit_init_scale;
417 ntype.get_compositor_operation = file_ns::get_compositor_operation;
418
420}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:455
#define CMP_NODE_SCALE
#define ELEM(...)
CMPNodeInterpolation
@ CMP_NODE_INTERPOLATION_NEAREST
@ CMP_NODE_INTERPOLATION_BILINEAR
@ CMP_NODE_INTERPOLATION_BICUBIC
@ CMP_NODE_INTERPOLATION_ANISOTROPIC
CMPExtensionMode
@ CMP_NODE_EXTENSION_MODE_EXTEND
@ CMP_NODE_EXTENSION_MODE_CLIP
@ CMP_NODE_EXTENSION_MODE_REPEAT
CMPNodeScaleRenderSizeMethod
@ CMP_NODE_SCALE_RENDER_SIZE_STRETCH
@ CMP_NODE_SCALE_RENDER_SIZE_FIT
@ CMP_NODE_SCALE_RENDER_SIZE_CROP
CMPNodeScaleMethod
@ CMP_NODE_SCALE_RENDER_SIZE
@ CMP_NODE_SCALE_RELATIVE
@ CMP_NODE_SCALE_ABSOLUTE
@ CMP_NODE_SCALE_RENDER_PERCENT
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_texture_extend_mode_y(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_extend_mode_x(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
BMesh const char void * data
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
RealizationOptions & get_realization_options()
Definition result.cc:630
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
T load_pixel(const int2 &texel) const
bool is_single_value() const
Definition result.cc:758
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
const CompositorInputRealizationMode & compositor_realization_mode() const
#define input
#define output
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
GPUSamplerExtendMode map_extension_mode_to_extend_mode(const ExtensionMode &mode)
Definition domain.cc:70
void parallel_for(const int2 range, const Function &function)
T min(const T &a, const T &b)
MatT from_scale(const VecBase< typename MatT::base_type, ScaleDim > &scale)
T max(const T &a, const T &b)
static const EnumPropertyItem type_items[]
static const EnumPropertyItem frame_type_items[]
static void cmp_node_scale_declare(NodeDeclarationBuilder &b)
static void node_composit_init_scale(bNodeTree *, bNode *node)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
static void register_node_type_cmp_scale()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
#define CMP_SCALE_MAX
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
const EnumPropertyItem rna_enum_node_compositor_extension_items[]
const EnumPropertyItem rna_enum_node_compositor_interpolation_items[]
void * storage
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
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
#define N_(msgid)