Blender V5.0
node_composite_file_output.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 <cstring>
10
11#include "BLI_assert.h"
12#include "BLI_cpp_type.hh"
14#include "BLI_index_range.hh"
15#include "BLI_listbase.h"
16#include "BLI_path_utils.hh"
17#include "BLI_string.h"
18
19#include "BLT_translation.hh"
20
21#include "MEM_guardedalloc.h"
22
23#include "DNA_node_types.h"
24#include "DNA_scene_types.h"
25
26#include "BLO_read_write.hh"
27
28#include "BKE_context.hh"
29#include "BKE_cryptomatte.hh"
30#include "BKE_image.hh"
31#include "BKE_image_format.hh"
32#include "BKE_main.hh"
33#include "BKE_scene.hh"
34
35#include "RNA_access.hh"
36
37#include "UI_interface.hh"
39#include "UI_resources.hh"
40
41#include "WM_api.hh"
42
43#include "IMB_imbuf.hh"
44
45#include "GPU_state.hh"
46#include "GPU_texture.hh"
47
48#include "COM_node_operation.hh"
49#include "COM_utilities.hh"
50
57
59
61
63
65
67{
68 b.use_custom_socket_order();
69 b.allow_any_socket_order();
70
71 b.add_default_layout();
72
73 const bNodeTree *node_tree = b.tree_or_null();
74 const bNode *node = b.node_or_null();
75 if (!node_tree || !node) {
76 return;
77 }
78
79 const NodeCompositorFileOutput &storage = node_storage(*node);
80
81 /* Inputs for multi-layer files need to be the same size, while they can be different for
82 * individual file outputs. */
83 const bool is_multi_layer = storage.format.imtype == R_IMF_IMTYPE_MULTILAYER;
84 const CompositorInputRealizationMode realization_mode =
87
88 for (const int i : IndexRange(storage.items_count)) {
89 const NodeCompositorFileOutputItem &item = storage.items[i];
90 const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
91 const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(item);
92 BaseSocketDeclarationBuilder *declaration = nullptr;
93 if (socket_type == SOCK_VECTOR) {
94 declaration = &b.add_input<decl::Vector>(item.name, identifier)
95 .dimensions(item.vector_socket_dimensions);
96 }
97 else {
98 declaration = &b.add_input(socket_type, item.name, identifier);
99 }
100 declaration->structure_type(StructureType::Dynamic)
101 .compositor_realization_mode(realization_mode)
102 .socket_name_ptr(&node_tree->id, FileOutputItemsAccessor::item_srna, &item, "name");
103 }
104
105 b.add_input<decl::Extend>("", "__extend__");
106}
107
108static void node_init(const bContext *C, PointerRNA *node_pointer)
109{
110 bNode *node = node_pointer->data_as<bNode>();
112 node->storage = data;
113 data->save_as_render = true;
114 data->file_name = BLI_strdup("file_name");
115
116 BKE_image_format_init(&data->format);
118 &data->format, node_pointer->owner_id, MEDIA_TYPE_MULTI_LAYER_IMAGE);
120
121 Scene *scene = CTX_data_scene(C);
122 if (scene) {
123 const RenderData *render_data = &scene->r;
124 STRNCPY(data->directory, render_data->pic);
125 }
126}
127
128static void node_free_storage(bNode *node)
129{
131 NodeCompositorFileOutput &data = node_storage(*node);
133 MEM_SAFE_FREE(data.file_name);
134 MEM_freeN(&data);
135}
136
137static void node_copy_storage(bNodeTree * /*destination_node_tree*/,
138 bNode *destination_node,
139 const bNode *source_node)
140{
141 const NodeCompositorFileOutput &source_storage = node_storage(*source_node);
143 __func__, source_storage);
144 destination_storage->file_name = BLI_strdup_null(source_storage.file_name);
145 BKE_image_format_copy(&destination_storage->format, &source_storage.format);
146 destination_node->storage = destination_storage;
147 socket_items::copy_array<FileOutputItemsAccessor>(*source_node, *destination_node);
148}
149
155
160
161/* Computes the path of the image to be saved based on the given parameters. The given file name
162 * suffix, if not empty, will be added to the file name. If the given view is not empty, its file
163 * suffix will be appended to the name. The frame number, scene, and node are provides for variable
164 * substitution in the path. If there are any errors processing the path, they will be returned. */
166 const StringRefNull file_name,
167 const StringRefNull file_name_suffix,
168 const char *view,
169 const int frame_number,
170 const ImageFormatData &format,
171 const Scene &scene,
172 const bNode &node,
173 const bool is_animation_render,
174 char *r_image_path)
175{
176 char base_path[FILE_MAX] = "";
177 STRNCPY(base_path, directory.c_str());
178 const std::string full_file_name = file_name + file_name_suffix;
179 BLI_path_append(base_path, FILE_MAX, full_file_name.c_str());
180
181 path_templates::VariableMap template_variables;
182 BKE_add_template_variables_general(template_variables, &node.owner_tree().id);
183 BKE_add_template_variables_for_render_path(template_variables, scene);
184 BKE_add_template_variables_for_node(template_variables, node);
185
186 /* Substitute #### frame variables if not doing an animation render. For animation renders, this
187 * is handled internally by the following function. */
188 if (!is_animation_render) {
189 BLI_path_frame(base_path, FILE_MAX, frame_number, 0);
190 }
191
192 return BKE_image_path_from_imformat(r_image_path,
193 base_path,
195 &template_variables,
196 frame_number,
197 &format,
198 scene.r.scemode & R_EXTENSION,
199 is_animation_render,
201}
202
203static void node_layout(uiLayout *layout, bContext * /*context*/, PointerRNA *node_pointer)
204{
205 layout->prop(node_pointer, "directory", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
206 layout->prop(node_pointer, "file_name", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
207}
208
209static void format_layout(uiLayout *layout,
210 bContext *context,
211 PointerRNA *format_pointer,
212 PointerRNA *node_or_item_pointer)
213{
214 uiLayout *column = &layout->column(true);
215 column->use_property_split_set(true);
216 column->use_property_decorate_set(false);
217 column->prop(
218 node_or_item_pointer, "save_as_render", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
219 const bool save_as_render = RNA_boolean_get(node_or_item_pointer, "save_as_render");
220 uiTemplateImageSettings(layout, context, format_pointer, save_as_render);
221
222 if (!save_as_render) {
223 uiLayout *column = &layout->column(true);
224 column->use_property_split_set(true);
225 column->use_property_decorate_set(false);
226
227 PointerRNA linear_settings_ptr = RNA_pointer_get(format_pointer, "linear_colorspace_settings");
228 column->prop(&linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
229 }
230
231 Scene *scene = CTX_data_scene(context);
232 const bool is_multiview = scene->r.scemode & R_MULTIVIEW;
233 if (is_multiview) {
234 uiTemplateImageFormatViews(layout, format_pointer, nullptr);
235 }
236}
237
238static void output_path_layout(uiLayout *layout,
239 const StringRefNull directory,
240 const StringRefNull file_name,
241 const StringRefNull file_name_suffix,
242 const char *view,
243 const ImageFormatData &format,
244 const Scene &scene,
245 const bNode &node)
246{
247
248 char image_path[FILE_MAX];
249 const Vector<path_templates::Error> path_errors = compute_image_path(directory,
250 file_name,
251 file_name_suffix,
252 view,
253 scene.r.cfra,
254 format,
255 scene,
256 node,
257 false,
258 image_path);
259
260 if (path_errors.is_empty()) {
261 layout->label(image_path, ICON_FILE_IMAGE);
262 }
263 else {
264 for (const path_templates::Error &error : path_errors) {
265 layout->label(BKE_path_template_error_to_string(error, image_path).c_str(), ICON_ERROR);
266 }
267 }
268}
269
270static void output_paths_layout(uiLayout *layout,
271 bContext *context,
272 const StringRefNull file_name_suffix,
273 const bNode &node,
274 const ImageFormatData &format)
275{
276 const NodeCompositorFileOutput &storage = node_storage(node);
277 const StringRefNull directory = storage.directory;
278 const std::string file_name = storage.file_name ? storage.file_name : "";
279 const Scene &scene = *CTX_data_scene(context);
280
281 if (bool(scene.r.scemode & R_MULTIVIEW) && format.views_format == R_IMF_VIEWS_MULTIVIEW) {
284 continue;
285 }
286
288 layout, directory, file_name, file_name_suffix, view->name, format, scene, node);
289 }
290 }
291 else {
292 output_path_layout(layout, directory, file_name, file_name_suffix, "", format, scene, node);
293 }
294}
295
296static void item_layout(uiLayout *layout,
297 bContext *context,
298 PointerRNA *node_pointer,
299 PointerRNA *item_pointer,
300 const bool is_multi_layer)
301{
302 layout->use_property_split_set(true);
303 layout->use_property_decorate_set(false);
304 layout->prop(item_pointer, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
305 if (RNA_enum_get(item_pointer, "socket_type") == SOCK_VECTOR) {
306 layout->prop(item_pointer, "vector_socket_dimensions", UI_ITEM_NONE, std::nullopt, ICON_NONE);
307 }
308
309 if (is_multi_layer) {
310 return;
311 }
312
313 layout->prop(
314 item_pointer, "override_node_format", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
315 const bool override_node_format = RNA_boolean_get(item_pointer, "override_node_format");
316
317 PointerRNA node_format_pointer = RNA_pointer_get(node_pointer, "format");
318 PointerRNA item_format_pointer = RNA_pointer_get(item_pointer, "format");
319 PointerRNA *format_pointer = override_node_format ? &item_format_pointer : &node_format_pointer;
320
321 if (override_node_format) {
322 if (uiLayout *panel = layout->panel(context, "item_format", false, IFACE_("Item Format"))) {
323 format_layout(panel, context, format_pointer, item_pointer);
324 }
325 }
326}
327
328static void node_layout_ex(uiLayout *layout, bContext *context, PointerRNA *node_pointer)
329{
330 node_layout(layout, context, node_pointer);
331
332 PointerRNA format_pointer = RNA_pointer_get(node_pointer, "format");
333 const bool is_multi_layer = RNA_enum_get(&format_pointer, "file_format") ==
335 layout->prop(&format_pointer, "media_type", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
336 if (uiLayout *panel = layout->panel(context, "node_format", false, IFACE_("Node Format"))) {
337 format_layout(panel, context, &format_pointer, node_pointer);
338 }
339
340 const char *panel_name = is_multi_layer ? IFACE_("Layers") : IFACE_("Images");
341 if (uiLayout *panel = layout->panel(context, "file_output_items", false, panel_name)) {
342 bNodeTree &tree = *reinterpret_cast<bNodeTree *>(node_pointer->owner_id);
343 bNode &node = *node_pointer->data_as<bNode>();
345 context, panel, tree, node);
347 tree, node, [&](PointerRNA *item_pointer) {
348 item_layout(panel, context, node_pointer, item_pointer, is_multi_layer);
349 });
350 }
351
352 if (uiLayout *panel = layout->panel(context, "output_paths", true, IFACE_("Output Paths"))) {
353 const bNode &node = *node_pointer->data_as<bNode>();
354 const ImageFormatData &node_format = *format_pointer.data_as<ImageFormatData>();
355
356 if (is_multi_layer) {
357 output_paths_layout(panel, context, "", node, node_format);
358 }
359 else {
360 const NodeCompositorFileOutput &storage = node_storage(node);
361 for (const int i : IndexRange(storage.items_count)) {
362 const NodeCompositorFileOutputItem &item = storage.items[i];
363 const auto &format = item.override_node_format ? item.format : storage.format;
364 output_paths_layout(panel, context, item.name, node, format);
365 }
366 }
367 }
368}
369
370static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
371{
372 const NodeCompositorFileOutput &data = node_storage(node);
373 BLO_write_string(&writer, data.file_name);
374 BKE_image_format_blend_write(&writer, const_cast<ImageFormatData *>(&data.format));
376}
377
378static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
379{
380 NodeCompositorFileOutput &data = node_storage(node);
381 BLO_read_string(&reader, &data.file_name);
382 BKE_image_format_blend_read_data(&reader, &data.format);
384}
385
386static void node_extra_info(NodeExtraInfoParams &parameters)
387{
388 SpaceNode *space_node = CTX_wm_space_node(&parameters.C);
389 if (space_node->node_tree_sub_type != SNODE_COMPOSITOR_SCENE) {
391 row.text = RPT_("Node Unsupported");
392 row.tooltip = TIP_("The File Output node is only supported for scene compositing");
393 row.icon = ICON_ERROR;
394 parameters.rows.append(std::move(row));
395 }
396}
397
398using namespace blender::compositor;
399
401 public:
403
404 void execute() override
405 {
406 if (this->is_multi_layer()) {
407 this->execute_multi_layer();
408 }
409 else {
410 this->execute_single_layer();
411 }
412 }
413
414 /* --------------------
415 * Single Layer Images.
416 */
417
419 {
420 const NodeCompositorFileOutput &storage = node_storage(this->bnode());
421 for (const int i : IndexRange(storage.items_count)) {
422 const NodeCompositorFileOutputItem &item = storage.items[i];
423 const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(item);
424 const Result &result = this->get_input(identifier);
425 /* We only write images, not single values. */
426 if (result.is_single_value()) {
427 continue;
428 }
429
430 /* The image saving code expects EXR images to have a different structure than standard
431 * images. In particular, in EXR images, the buffers need to be stored in passes that are, in
432 * turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to
433 * be stored in views. An exception to this is stereo images, which needs to have the same
434 * structure as non-EXR images. */
435 const auto &format = item.override_node_format ? item.format :
436 node_storage(this->bnode()).format;
437 const bool save_as_render = item.override_node_format ?
438 item.save_as_render :
439 node_storage(this->bnode()).save_as_render;
440 const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR;
441 const int views_count = BKE_scene_multiview_num_views_get(
442 &this->context().get_render_data());
443 if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) {
445 continue;
446 }
447
448 char image_path[FILE_MAX];
450 format, item.name, "", image_path);
451 if (!path_errors.is_empty()) {
452 continue;
453 }
454
455 const int2 size = result.domain().size;
456 FileOutput &file_output = this->context().render_context()->get_file_output(
457 image_path, format, size, save_as_render);
458
459 this->add_view_for_result(file_output, result, context().get_view_name().data());
460
461 this->add_meta_data_for_result(file_output, result, item.name);
462 }
463 }
464
465 /* -----------------------------------
466 * Single Layer Multi-View EXR Images.
467 */
468
470 const ImageFormatData &format,
471 const char *layer_name)
472 {
473 const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL;
474
475 /* The EXR stores all views in the same file, so we supply an empty view to make sure the file
476 * name does not contain a view suffix. */
477 const char *path_view = has_views ? "" : this->context().get_view_name().data();
478
479 char image_path[FILE_MAX];
481 format, layer_name, path_view, image_path);
482 if (!path_errors.is_empty()) {
483 return;
484 }
485
486 const int2 size = result.domain().size;
487 FileOutput &file_output = this->context().render_context()->get_file_output(
488 image_path, format, size, true);
489
490 /* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we
491 * add a default unnamed view. */
492 const char *view_name = has_views ? this->context().get_view_name().data() : "";
493 file_output.add_view(view_name);
494 this->add_pass_for_result(file_output, result, "", view_name);
495
496 this->add_meta_data_for_result(file_output, result, layer_name);
497 }
498
499 /* -----------------------
500 * Multi-Layer EXR Images.
501 */
502
504 {
505 /* We only write images, not single values. */
506 const int2 size = this->compute_domain().size;
507 if (size == int2(1)) {
508 return;
509 }
510
511 const ImageFormatData format = node_storage(this->bnode()).format;
512 const bool store_views_in_single_file = this->is_multi_view_exr();
513 const char *view = this->context().get_view_name().data();
514
515 /* If we are saving all views in a single multi-layer file, we supply an empty view to make
516 * sure the file name does not contain a view suffix. */
517 char image_path[FILE_MAX];
518 const char *write_view = store_views_in_single_file ? "" : view;
520 format, "", write_view, image_path);
521 if (!path_errors.is_empty()) {
522 return;
523 }
524
525 FileOutput &file_output = this->context().render_context()->get_file_output(
526 image_path, format, size, true);
527
528 /* If we are saving views in separate files, we needn't store the view in the channel names, so
529 * we add an unnamed view. */
530 const char *pass_view = store_views_in_single_file ? view : "";
531 file_output.add_view(pass_view);
532
533 const NodeCompositorFileOutput &storage = node_storage(bnode());
534 for (const int i : IndexRange(storage.items_count)) {
535 const NodeCompositorFileOutputItem &item = storage.items[i];
536 const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(item);
537 const Result &input_result = this->get_input(identifier);
538 this->add_pass_for_result(file_output, input_result, item.name, pass_view);
539
540 this->add_meta_data_for_result(file_output, input_result, item.name);
541 }
542 }
543
544 /* Read the data stored in the given result and add a pass of the given name, view, and read
545 * buffer. The pass channel identifiers follows the EXR conventions. */
547 const Result &result,
548 const char *pass_name,
549 const char *view_name)
550 {
551 /* For single values, we fill a buffer that covers the domain of the operation with the value
552 * of the result. */
553 const int2 size = result.is_single_value() ? this->compute_domain().size :
554 result.domain().size;
555
556 /* The image buffer in the file output will take ownership of this buffer and freeing it will
557 * be its responsibility. */
558 float *buffer = nullptr;
559 if (result.is_single_value()) {
560 buffer = this->inflate_result(result, size);
561 }
562 else {
563 if (this->context().use_gpu()) {
565 buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
566 }
567 else {
568 /* Copy the result into a new buffer. */
569 buffer = static_cast<float *>(MEM_dupallocN(result.cpu_data().data()));
570 }
571 }
572
573 switch (result.type()) {
575 /* Use lowercase rgba for Cryptomatte layers because the EXR internal compression rules
576 * specify that all uppercase RGBA channels will be compressed, and Cryptomatte should not
577 * be compressed. */
578 if (result.meta_data.is_cryptomatte_layer()) {
579 file_output.add_pass(pass_name, view_name, "rgba", buffer);
580 }
581 else {
582 file_output.add_pass(pass_name, view_name, "RGBA", buffer);
583 }
584 break;
586 /* Float3 results might be stored in 4-component textures due to hardware limitations, so
587 * we need to convert the buffer to a 3-component buffer on the host. */
588 if (this->context().use_gpu() && GPU_texture_component_len(GPU_texture_format(result))) {
589 file_output.add_pass(pass_name, view_name, "XYZ", float4_to_float3_image(size, buffer));
590 }
591 else {
592 file_output.add_pass(pass_name, view_name, "XYZ", buffer);
593 }
594 break;
596 file_output.add_pass(pass_name, view_name, "XYZW", buffer);
597 break;
599 file_output.add_pass(pass_name, view_name, "V", buffer);
600 break;
602 file_output.add_pass(pass_name, view_name, "XY", buffer);
603 break;
604 case ResultType::Int2:
605 case ResultType::Int:
606 case ResultType::Bool:
607 case ResultType::Menu:
609 /* Not supported. */
611 break;
612 }
613 }
614
615 /* Allocates and fills an image buffer of the specified size with the value of the given single
616 * value result. */
617 float *inflate_result(const Result &result, const int2 size)
618 {
619 BLI_assert(result.is_single_value());
620
621 const int64_t length = int64_t(size.x) * size.y;
622 const int64_t buffer_size = length * result.channels_count();
623 float *buffer = MEM_malloc_arrayN<float>(buffer_size, "File Output Inflated Buffer.");
624
625 switch (result.type()) {
630 case ResultType::Color: {
631 const GPointer single_value = result.single_value();
632 single_value.type()->fill_assign_n(single_value.get(), buffer, length);
633 return buffer;
634 }
635 case ResultType::Int:
636 case ResultType::Int2:
637 case ResultType::Bool:
638 case ResultType::Menu:
640 /* Not supported. */
642 return nullptr;
643 }
644
646 return nullptr;
647 }
648
649 /* Read the data stored the given result and add a view of the given name and read buffer. */
650 void add_view_for_result(FileOutput &file_output, const Result &result, const char *view_name)
651 {
652 /* The image buffer in the file output will take ownership of this buffer and freeing it will
653 * be its responsibility. */
654 float *buffer = nullptr;
655 if (this->context().use_gpu()) {
657 buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
658 }
659 else {
660 /* Copy the result into a new buffer. */
661 buffer = static_cast<float *>(MEM_dupallocN(result.cpu_data().data()));
662 }
663
664 const int2 size = result.domain().size;
665 switch (result.type()) {
667 file_output.add_view(view_name, 4, buffer);
668 break;
670 file_output.add_view(view_name, 4, buffer);
671 break;
673 /* Float3 results might be stored in 4-component textures due to hardware limitations, so
674 * we need to convert the buffer to a 3-component buffer on the host. */
675 if (this->context().use_gpu() && GPU_texture_component_len(GPU_texture_format(result))) {
676 file_output.add_view(view_name, 3, float4_to_float3_image(size, buffer));
677 }
678 else {
679 file_output.add_view(view_name, 3, buffer);
680 }
681 break;
683 file_output.add_view(view_name, 1, buffer);
684 break;
686 case ResultType::Int2:
687 case ResultType::Int:
688 case ResultType::Bool:
689 case ResultType::Menu:
691 /* Not supported. */
693 break;
694 }
695 }
696
697 /* Given a float4 image, return a newly allocated float3 image that ignores the last channel. The
698 * input image is freed. */
699 float *float4_to_float3_image(int2 size, float *float4_image)
700 {
701 float *float3_image = MEM_malloc_arrayN<float>(3 * size_t(size.x) * size_t(size.y),
702 "File Output Vector Buffer.");
703
704 parallel_for(size, [&](const int2 texel) {
705 for (int i = 0; i < 3; i++) {
706 const int64_t pixel_index = int64_t(texel.y) * size.x + texel.x;
707 float3_image[pixel_index * 3 + i] = float4_image[pixel_index * 4 + i];
708 }
709 });
710
711 MEM_freeN(float4_image);
712 return float3_image;
713 }
714
715 /* Add Cryptomatte meta data to the file if they exist for the given result of the given layer
716 * name. We do not write any other meta data for now. */
717 void add_meta_data_for_result(FileOutput &file_output, const Result &result, const char *name)
718 {
720
721 if (result.meta_data.is_cryptomatte_layer()) {
722 file_output.add_meta_data(
723 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "name"),
724 cryptomatte_layer_name);
725 }
726
727 if (!result.meta_data.cryptomatte.manifest.empty()) {
728 file_output.add_meta_data(
729 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "manifest"),
730 result.meta_data.cryptomatte.manifest);
731 }
732
733 if (!result.meta_data.cryptomatte.hash.empty()) {
734 file_output.add_meta_data(
735 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "hash"),
736 result.meta_data.cryptomatte.hash);
737 }
738
739 if (!result.meta_data.cryptomatte.conversion.empty()) {
740 file_output.add_meta_data(
741 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "conversion"),
742 result.meta_data.cryptomatte.conversion);
743 }
744 }
745
747 const char *file_name_suffix,
748 const char *view,
749 char *r_image_path)
750 {
752 this->get_directory(),
753 this->get_file_name(),
754 file_name_suffix,
755 view,
756 this->context().get_frame_number(),
757 format,
758 this->context().get_scene(),
759 this->bnode(),
760 this->is_animation_render(),
761 r_image_path);
762
763 if (!path_errors.is_empty()) {
765 nullptr, RPT_ERROR, "Invalid path template in File Output node. Skipping writing file.");
766 }
767
768 return path_errors;
769 }
770
772 {
773 return node_storage(this->bnode()).format.imtype == R_IMF_IMTYPE_MULTILAYER;
774 }
775
777 {
778 const char *file_name = node_storage(this->bnode()).file_name;
779 return file_name ? file_name : "";
780 }
781
783 {
784 return node_storage(this->bnode()).directory;
785 }
786
787 /* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */
789 {
790 if (!this->is_multi_view_scene()) {
791 return false;
792 }
793
794 return node_storage(this->bnode()).format.views_format == R_IMF_VIEWS_MULTIVIEW;
795 }
796
798 {
799 return this->context().get_render_data().scemode & R_MULTIVIEW;
800 }
801
803 {
805 if (!this->is_multi_layer()) {
806 return domain;
807 }
808
809 /* Reset the location of the domain in multi-layer case such that translations take effect,
810 * this will result in clipping but is more expected for the user. */
811 domain.transformation.location() = float2(0.0f);
812 return domain;
813 }
814
816 {
817 if (!this->context().render_context()) {
818 return false;
819 }
821 }
822};
823
825{
826 return new FileOutputOperation(context, node);
827}
828
829static void node_register()
830{
831 static blender::bke::bNodeType ntype;
832
833 cmp_node_type_base(&ntype, "CompositorNodeOutputFile", CMP_NODE_OUTPUT_FILE);
834 ntype.ui_name = "File Output";
835 ntype.ui_description = "Write image file to disk";
836 ntype.enum_name_legacy = "OUTPUT_FILE";
838 ntype.declare = node_declare;
843 ntype.initfunc_api = node_init;
845 ntype, "NodeCompositorFileOutput", node_free_storage, node_copy_storage);
850
852}
854
855} // namespace blender::nodes::node_composite_file_output_cc
856
857namespace blender::nodes {
858
859StructRNA *FileOutputItemsAccessor::item_srna = &RNA_NodeCompositorFileOutputItem;
860
862{
863 BLO_write_string(writer, item.name);
864 BKE_image_format_blend_write(writer, const_cast<ImageFormatData *>(&item.format));
865}
866
872
874{
875 char file_name[FILE_MAX] = "";
876 STRNCPY(file_name, name.data());
878 return file_name;
879}
880
881} // namespace blender::nodes
SpaceNode * CTX_wm_space_node(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
void BKE_image_format_free(ImageFormatData *imf)
void BKE_image_format_blend_write(BlendWriter *writer, ImageFormatData *imf)
void BKE_image_format_blend_read_data(BlendDataReader *reader, ImageFormatData *imf)
void BKE_image_format_update_color_space_for_type(ImageFormatData *format)
void BKE_image_format_media_type_set(ImageFormatData *format, ID *owner_id, const MediaType media_type)
blender::Vector< blender::bke::path_templates::Error > BKE_image_path_from_imformat(char *filepath, const char *base, const char *relbase, const blender::bke::path_templates::VariableMap *template_variables, int frame, const ImageFormatData *im_format, bool use_ext, bool use_frames, const char *suffix)
void BKE_image_format_init(ImageFormatData *imf)
void BKE_image_format_copy(ImageFormatData *imf_dst, const ImageFormatData *imf_src)
const char * BKE_main_blendfile_path_from_global()
Definition main.cc:892
#define NODE_CLASS_OUTPUT
Definition BKE_node.hh:448
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define CMP_NODE_OUTPUT_FILE
std::string BKE_path_template_error_to_string(const blender::bke::path_templates::Error &error, blender::StringRef path)
void BKE_add_template_variables_for_render_path(blender::bke::path_templates::VariableMap &variables, const Scene &scene)
void BKE_add_template_variables_general(blender::bke::path_templates::VariableMap &variables, const ID *path_owner_id)
void BKE_add_template_variables_for_node(blender::bke::path_templates::VariableMap &variables, const bNode &owning_node)
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
int BKE_scene_multiview_num_views_get(const RenderData *rd)
Definition scene.cc:2940
bool BKE_scene_multiview_is_render_view_active(const RenderData *rd, const SceneRenderView *srv)
Definition scene.cc:2988
const char * BKE_scene_multiview_view_suffix_get(const RenderData *rd, const char *viewname)
Definition scene.cc:3137
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
size_t BLI_path_append(char *__restrict dst, size_t dst_maxncpy, const char *__restrict file) ATTR_NONNULL(1
#define FILE_MAX
bool BLI_path_make_safe_filename(char *filename) ATTR_NONNULL(1)
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits) ATTR_NONNULL(1)
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char * BLI_strdup_null(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_MALLOC
Definition string.cc:46
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
void BLO_read_string(BlendDataReader *reader, char **ptr_p)
Definition readfile.cc:5828
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define RPT_(msgid)
#define TIP_(msgid)
#define IFACE_(msgid)
eNodeSocketDatatype
@ SOCK_VECTOR
@ MEDIA_TYPE_MULTI_LAYER_IMAGE
@ R_IMF_VIEWS_MULTIVIEW
@ R_IMF_VIEWS_STEREO_3D
@ R_IMF_VIEWS_INDIVIDUAL
@ R_IMF_IMTYPE_OPENEXR
@ R_IMF_IMTYPE_MULTILAYER
@ R_MULTIVIEW
@ R_EXTENSION
@ SNODE_COMPOSITOR_SCENE
static AppView * view
@ GPU_BARRIER_TEXTURE_UPDATE
Definition GPU_state.hh:39
void GPU_memory_barrier(GPUBarrier barrier)
Definition gpu_state.cc:326
size_t GPU_texture_component_len(blender::gpu::TextureFormat format)
blender::gpu::TextureFormat GPU_texture_format(const blender::gpu::Texture *texture)
@ GPU_DATA_FLOAT
void * GPU_texture_read(blender::gpu::Texture *texture, eGPUDataFormat data_format, int mip_level)
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define C
Definition RandGen.cpp:29
void uiTemplateImageSettings(uiLayout *layout, bContext *C, PointerRNA *imfptr, bool color_management, const char *panel_idname=nullptr)
void uiTemplateImageFormatViews(uiLayout *layout, PointerRNA *imfptr, PointerRNA *ptr)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
@ UI_ITEM_R_EXPAND
#define UI_ITEM_NONE
BMesh const char void * data
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
void append(const T &value)
void fill_assign_n(const void *value, void *dst, int64_t n) const
const CPPType * type() const
const void * get() const
constexpr const char * data() const
constexpr const char * c_str() const
bool is_empty() const
virtual StringRef get_view_name() const
virtual const RenderData & get_render_data() const
virtual RenderContext * render_context() const
void add_view(const char *view_name)
void add_pass(const char *pass_name, const char *view_name, const char *channels, float *buffer)
void add_meta_data(std::string key, std::string value)
NodeOperation(Context &context, DNode node)
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
FileOutput & get_file_output(std::string path, ImageFormatData format, int2 size, bool save_as_render)
const Domain & domain() const
BaseSocketDeclarationBuilder & compositor_realization_mode(CompositorInputRealizationMode value)
BaseSocketDeclarationBuilder & structure_type(StructureType structure_type)
BaseSocketDeclarationBuilder & socket_name_ptr(PointerRNA ptr, StringRef property_name)
void execute_single_layer_multi_view_exr(const Result &result, const ImageFormatData &format, const char *layer_name)
void add_pass_for_result(FileOutput &file_output, const Result &result, const char *pass_name, const char *view_name)
Vector< path_templates::Error > get_image_path(const ImageFormatData &format, const char *file_name_suffix, const char *view, char *r_image_path)
void add_meta_data_for_result(FileOutput &file_output, const Result &result, const char *name)
void add_view_for_result(FileOutput &file_output, const Result &result, const char *view_name)
KDTree_3d * tree
float length(VecOp< float, D >) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
format
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static void error(const char *str)
StringRef BKE_cryptomatte_extract_layer_name(StringRef render_pass_name)
std::string BKE_cryptomatte_meta_data_key(StringRef layer_name, StringRefNull key_name)
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 parallel_for(const int2 range, const Function &function)
static void format_layout(uiLayout *layout, bContext *context, PointerRNA *format_pointer, PointerRNA *node_or_item_pointer)
static void node_blend_write(const bNodeTree &, const bNode &node, BlendWriter &writer)
static bool node_insert_link(bke::NodeInsertLinkParams &params)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *node_pointer)
static Vector< path_templates::Error > compute_image_path(const StringRefNull directory, const StringRefNull file_name, const StringRefNull file_name_suffix, const char *view, const int frame_number, const ImageFormatData &format, const Scene &scene, const bNode &node, const bool is_animation_render, char *r_image_path)
static void node_extra_info(NodeExtraInfoParams &parameters)
static void node_declare(NodeDeclarationBuilder &b)
static void output_paths_layout(uiLayout *layout, bContext *context, const StringRefNull file_name_suffix, const bNode &node, const ImageFormatData &format)
static void item_layout(uiLayout *layout, bContext *context, PointerRNA *node_pointer, PointerRNA *item_pointer, const bool is_multi_layer)
static void node_blend_read(bNodeTree &, bNode &node, BlendDataReader &reader)
static void node_init(const bContext *C, PointerRNA *node_pointer)
static void node_copy_storage(bNodeTree *, bNode *destination_node, const bNode *source_node)
static void node_layout_ex(uiLayout *layout, bContext *context, PointerRNA *node_pointer)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void output_path_layout(uiLayout *layout, const StringRefNull directory, const StringRefNull file_name, const StringRefNull file_name_suffix, const char *view, const ImageFormatData &format, const Scene &scene, const bNode &node)
static void draw_items_list_with_operators(const bContext *C, uiLayout *layout, const bNodeTree &tree, const bNode &node)
static void draw_active_item_props(const bNodeTree &tree, const bNode &node, const FunctionRef< void(PointerRNA *item_ptr)> draw_item)
void blend_write(BlendWriter *writer, const bNode &node)
void blend_read_data(BlendDataReader *reader, bNode &node)
void copy_array(const bNode &src_node, bNode &dst_node)
bool try_add_item_via_any_extend_socket(bNodeTree &ntree, bNode &extend_node, bNode &storage_node, bNodeLink &link, const std::optional< StringRef > socket_identifier=std::nullopt, typename Accessor::ItemT **r_new_item=nullptr)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
const char * name
PointerRNA RNA_pointer_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
NodeCompositorFileOutputItem * items
T * data_as() const
Definition RNA_types.hh:124
ID * owner_id
Definition RNA_types.hh:51
char pic[1024]
struct RenderData r
char node_tree_sub_type
struct ID * id
void * storage
Defines a node type.
Definition BKE_node.hh:238
NodeBlendWriteFunction blend_write_storage_content
Definition BKE_node.hh:390
std::string ui_description
Definition BKE_node.hh:244
NodeBlendDataReadFunction blend_data_read_storage_content
Definition BKE_node.hh:391
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
void(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:261
NodeExtraInfoFunction get_extra_info
Definition BKE_node.hh:381
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
bool(* insert_link)(NodeInsertLinkParams &params)
Definition BKE_node.hh:333
NodeDeclareFunction declare
Definition BKE_node.hh:362
void(* register_operators)()
Definition BKE_node.hh:417
void(* initfunc_api)(const bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:302
static std::string validate_name(const StringRef name)
static std::string socket_identifier_for_item(const NodeCompositorFileOutputItem &item)
static void blend_write_item(BlendWriter *writer, const ItemT &item)
static void blend_read_data_item(BlendDataReader *reader, ItemT &item)
Vector< NodeExtraInfoRow > & rows
void use_property_decorate_set(bool is_sep)
PanelLayout panel(const bContext *C, blender::StringRef idname, bool default_closed)
void label(blender::StringRef name, int icon)
uiLayout & column(bool align)
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230