Blender V4.5
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_math_vector.h"
17#include "BLI_path_utils.hh"
18#include "BLI_string.h"
19#include "BLI_string_utf8.h"
20#include "BLI_string_utils.hh"
21#include "BLI_task.hh"
22#include "BLI_utildefines.h"
23
24#include "BLT_translation.hh"
25
26#include "MEM_guardedalloc.h"
27
28#include "DNA_node_types.h"
29#include "DNA_scene_types.h"
30
31#include "BKE_context.hh"
32#include "BKE_cryptomatte.hh"
33#include "BKE_image.hh"
34#include "BKE_image_format.hh"
35#include "BKE_main.hh"
37#include "BKE_scene.hh"
38
39#include "RNA_access.hh"
40
41#include "UI_interface.hh"
42#include "UI_resources.hh"
43
44#include "WM_api.hh"
45
46#include "IMB_imbuf.hh"
47
48#include "GPU_state.hh"
49#include "GPU_texture.hh"
50
51#include "COM_node_operation.hh"
52#include "COM_utilities.hh"
53
55
57
59
60/* **************** OUTPUT FILE ******************** */
61
62/* find unique path */
64 bNodeSocket *sock,
65 const blender::StringRef name)
66{
67 LISTBASE_FOREACH (bNodeSocket *, sock_iter, lb) {
68 if (sock_iter != sock) {
69 NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock_iter->storage;
70 if (sockdata->path == name) {
71 return true;
72 }
73 }
74 }
75 return false;
76}
78 bNodeSocket *sock,
79 const char defname[],
80 char delim)
81{
82 /* See if we are given an empty string */
83 if (ELEM(nullptr, sock, defname)) {
84 return;
85 }
88 [&](const blender::StringRef check_name) {
89 return unique_path_unique_check(list, sock, check_name);
90 },
91 defname,
92 delim,
93 sockdata->path,
94 sizeof(sockdata->path));
95}
96
97/* find unique EXR layer */
99 bNodeSocket *sock,
100 const blender::StringRef name)
101{
102 LISTBASE_FOREACH (bNodeSocket *, sock_iter, lb) {
103 if (sock_iter != sock) {
104 NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock_iter->storage;
105 if (sockdata->layer == name) {
106 return true;
107 }
108 }
109 }
110 return false;
111}
113 bNodeSocket *sock,
114 const char defname[],
115 char delim)
116{
117 /* See if we are given an empty string */
118 if (ELEM(nullptr, sock, defname)) {
119 return;
120 }
123 [&](const blender::StringRef check_name) {
124 return unique_layer_unique_check(list, sock, check_name);
125 },
126 defname,
127 delim,
128 sockdata->layer,
129 sizeof(sockdata->layer));
130}
131
133 bNode *node,
134 const char *name,
135 const ImageFormatData *im_format)
136{
139 *ntree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "", name);
140
141 /* create format data for the input socket */
143 sock->storage = sockdata;
144
145 STRNCPY_UTF8(sockdata->path, name);
146 ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
147 STRNCPY_UTF8(sockdata->layer, name);
148 ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
149
150 if (im_format) {
151 BKE_image_format_copy(&sockdata->format, im_format);
153 if (BKE_imtype_is_movie(sockdata->format.imtype)) {
155 }
156 }
157 else {
158 BKE_image_format_init(&sockdata->format, false);
159 }
161
162 /* use node data format by default */
163 sockdata->use_node_format = true;
164 sockdata->save_as_render = true;
165
166 nimf->active_input = BLI_findindex(&node->inputs, sock);
167
168 return sock;
169}
170
172{
174 bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->inputs, nimf->active_input);
175 int totinputs = BLI_listbase_count(&node->inputs);
176
177 if (!sock) {
178 return 0;
179 }
180
181 if (nimf->active_input == totinputs - 1) {
182 --nimf->active_input;
183 }
184
185 /* free format data */
186 MEM_freeN(reinterpret_cast<NodeImageMultiFileSocket *>(sock->storage));
187
188 blender::bke::node_remove_socket(*ntree, *node, *sock);
189 return 1;
190}
191
192void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name)
193{
195 STRNCPY_UTF8(sockdata->path, name);
196 ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
197}
198
199void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name)
200{
202 STRNCPY_UTF8(sockdata->layer, name);
203 ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
204}
205
207
209
210/* XXX uses initfunc_api callback, regular initfunc does not support context yet */
212{
213 Scene *scene = CTX_data_scene(C);
214 bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
215 bNode *node = (bNode *)ptr->data;
217 nimf->save_as_render = true;
218 ImageFormatData *format = nullptr;
219 node->storage = nimf;
220
221 if (scene) {
222 RenderData *rd = &scene->r;
223
224 STRNCPY(nimf->base_path, rd->pic);
227 if (BKE_imtype_is_movie(nimf->format.imtype)) {
229 }
230
231 format = &nimf->format;
232 }
233 else {
234 BKE_image_format_init(&nimf->format, false);
235 }
237
238 /* add one socket by default */
239 ntreeCompositOutputFileAddSocket(ntree, node, DATA_("Image"), format);
240}
241
242static void free_output_file(bNode *node)
243{
244 /* free storage data in sockets */
245 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
246 NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
247 BKE_image_format_free(&sockdata->format);
248 MEM_freeN(sockdata);
249 }
250
253 MEM_freeN(nimf);
254}
255
256static void copy_output_file(bNodeTree * /*dst_ntree*/, bNode *dest_node, const bNode *src_node)
257{
258 bNodeSocket *src_sock, *dest_sock;
259
260 dest_node->storage = MEM_dupallocN(src_node->storage);
261 NodeImageMultiFile *dest_nimf = (NodeImageMultiFile *)dest_node->storage;
262 NodeImageMultiFile *src_nimf = (NodeImageMultiFile *)src_node->storage;
263 BKE_image_format_copy(&dest_nimf->format, &src_nimf->format);
264
265 /* duplicate storage data in sockets */
266 for (src_sock = (bNodeSocket *)src_node->inputs.first,
267 dest_sock = (bNodeSocket *)dest_node->inputs.first;
268 src_sock && dest_sock;
269 src_sock = src_sock->next, dest_sock = (bNodeSocket *)dest_sock->next)
270 {
271 dest_sock->storage = MEM_dupallocN(src_sock->storage);
272 NodeImageMultiFileSocket *dest_sockdata = (NodeImageMultiFileSocket *)dest_sock->storage;
273 NodeImageMultiFileSocket *src_sockdata = (NodeImageMultiFileSocket *)src_sock->storage;
274 BKE_image_format_copy(&dest_sockdata->format, &src_sockdata->format);
275 }
276}
277
278static void update_output_file(bNodeTree *ntree, bNode *node)
279{
280 /* XXX fix for #36706: remove invalid sockets added with bpy API.
281 * This is not ideal, but prevents crashes from missing storage.
282 * FileOutput node needs a redesign to support this properly.
283 */
285 if (sock->storage == nullptr) {
286 blender::bke::node_remove_socket(*ntree, *node, *sock);
287 }
288 }
290 blender::bke::node_remove_socket(*ntree, *node, *sock);
291 }
292
293 cmp_node_update_default(ntree, node);
294
295 /* automatically update the socket type based on linked input */
296 ntree->ensure_topology_cache();
297 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
298 if (sock->is_logically_linked()) {
299 const bNodeSocket *from_socket = sock->logically_linked_sockets()[0];
300 if (sock->type != from_socket->type) {
301 blender::bke::node_modify_socket_type_static(ntree, node, sock, from_socket->type, 0);
303 }
304 }
305 }
306}
307
309{
310 PointerRNA imfptr = RNA_pointer_get(ptr, "format");
311 const bool multilayer = RNA_enum_get(&imfptr, "file_format") == R_IMF_IMTYPE_MULTILAYER;
312
313 if (multilayer) {
314 layout->label(IFACE_("Path:"), ICON_NONE);
315 }
316 else {
317 layout->label(IFACE_("Base Path:"), ICON_NONE);
318 }
319 layout->prop(ptr, "base_path", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
320}
321
323{
324 Scene *scene = CTX_data_scene(C);
325 PointerRNA imfptr = RNA_pointer_get(ptr, "format");
326 PointerRNA active_input_ptr, op_ptr;
327 uiLayout *row, *col;
328 const bool multilayer = RNA_enum_get(&imfptr, "file_format") == R_IMF_IMTYPE_MULTILAYER;
329 const bool is_multiview = (scene->r.scemode & R_MULTIVIEW) != 0;
330
332
333 {
334 uiLayout *column = &layout->column(true);
335 uiLayoutSetPropSep(column, true);
336 uiLayoutSetPropDecorate(column, false);
337 column->prop(ptr, "save_as_render", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
338 }
339 const bool save_as_render = RNA_boolean_get(ptr, "save_as_render");
340 uiTemplateImageSettings(layout, &imfptr, save_as_render);
341
342 if (!save_as_render) {
343 uiLayout *col = &layout->column(true);
344 uiLayoutSetPropSep(col, true);
346
347 PointerRNA linear_settings_ptr = RNA_pointer_get(&imfptr, "linear_colorspace_settings");
348 col->prop(&linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
349 }
350
351 /* disable stereo output for multilayer, too much work for something that no one will use */
352 /* if someone asks for that we can implement it */
353 if (is_multiview) {
354 uiTemplateImageFormatViews(layout, &imfptr, nullptr);
355 }
356
357 layout->separator();
358
359 layout->op("NODE_OT_output_file_add_socket", IFACE_("Add Input"), ICON_ADD);
360
361 row = &layout->row(false);
362 col = &row->column(true);
363
364 const int active_index = RNA_int_get(ptr, "active_input_index");
365 /* using different collection properties if multilayer format is enabled */
366 if (multilayer) {
368 C,
369 "UI_UL_list",
370 "file_output_node",
371 ptr,
372 "layer_slots",
373 ptr,
374 "active_input_index",
375 nullptr,
376 0,
377 0,
378 0,
379 0,
382 ptr, RNA_struct_find_property(ptr, "layer_slots"), active_index, &active_input_ptr);
383 }
384 else {
386 C,
387 "UI_UL_list",
388 "file_output_node",
389 ptr,
390 "file_slots",
391 ptr,
392 "active_input_index",
393 nullptr,
394 0,
395 0,
396 0,
397 0,
400 ptr, RNA_struct_find_property(ptr, "file_slots"), active_index, &active_input_ptr);
401 }
402 /* XXX collection lookup does not return the ID part of the pointer,
403 * setting this manually here */
404 active_input_ptr.owner_id = ptr->owner_id;
405
406 col = &row->column(true);
407 wmOperatorType *ot = WM_operatortype_find("NODE_OT_output_file_move_active_socket", false);
408 op_ptr = col->op(ot, "", ICON_TRIA_UP, WM_OP_INVOKE_DEFAULT, UI_ITEM_NONE);
409 RNA_enum_set(&op_ptr, "direction", 1);
410 op_ptr = col->op(ot, "", ICON_TRIA_DOWN, WM_OP_INVOKE_DEFAULT, UI_ITEM_NONE);
411 RNA_enum_set(&op_ptr, "direction", 2);
412
413 if (active_input_ptr.data) {
414 if (multilayer) {
415 col = &layout->column(true);
416
417 col->label(IFACE_("Layer:"), ICON_NONE);
418 row = &col->row(false);
419 row->prop(&active_input_ptr, "name", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
420 row->op("NODE_OT_output_file_remove_active_socket",
421 "",
422 ICON_X,
425 }
426 else {
427 col = &layout->column(true);
428
429 col->label(IFACE_("File Subpath:"), ICON_NONE);
430 row = &col->row(false);
431 row->prop(&active_input_ptr, "path", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
432 row->op("NODE_OT_output_file_remove_active_socket",
433 "",
434 ICON_X,
437
438 /* format details for individual files */
439 imfptr = RNA_pointer_get(&active_input_ptr, "format");
440
441 col = &layout->column(true);
442 col->label(IFACE_("Format:"), ICON_NONE);
443 col->prop(&active_input_ptr,
444 "use_node_format",
446 std::nullopt,
447 ICON_NONE);
448
449 const bool use_node_format = RNA_boolean_get(&active_input_ptr, "use_node_format");
450
451 if (!use_node_format) {
452 {
453 uiLayout *column = &layout->column(true);
454 uiLayoutSetPropSep(column, true);
455 uiLayoutSetPropDecorate(column, false);
456 column->prop(&active_input_ptr,
457 "save_as_render",
459 std::nullopt,
460 ICON_NONE);
461 }
462
463 const bool use_color_management = RNA_boolean_get(&active_input_ptr, "save_as_render");
464
465 col = &layout->column(false);
466 uiTemplateImageSettings(col, &imfptr, use_color_management);
467
468 if (!use_color_management) {
469 uiLayout *col = &layout->column(true);
470 uiLayoutSetPropSep(col, true);
472
473 PointerRNA linear_settings_ptr = RNA_pointer_get(&imfptr, "linear_colorspace_settings");
474 col->prop(&linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
475 }
476
477 if (is_multiview) {
478 col = &layout->column(false);
479 uiTemplateImageFormatViews(col, &imfptr, nullptr);
480 }
481 }
482 }
483 }
484}
485
486using namespace blender::compositor;
487
489 public:
491 {
492 for (const bNodeSocket *input : node->input_sockets()) {
493 if (!is_socket_available(input)) {
494 continue;
495 }
496
497 InputDescriptor &descriptor = this->get_input_descriptor(input->identifier);
498 /* Inputs for multi-layer files need to be the same size, while they can be different for
499 * individual file outputs. */
500 descriptor.realization_mode = this->is_multi_layer() ?
503 descriptor.skip_type_conversion = true;
504 }
505 }
506
507 void execute() override
508 {
509 if (is_multi_layer()) {
511 }
512 else {
514 }
515 }
516
517 /* --------------------
518 * Single Layer Images.
519 */
520
522 {
523 for (const bNodeSocket *input : this->node()->input_sockets()) {
525 continue;
526 }
527
528 const Result &result = get_input(input->identifier);
529 /* We only write images, not single values. */
530 if (result.is_single_value()) {
531 continue;
532 }
533
534 char base_path[FILE_MAX];
535 const auto &socket = *static_cast<NodeImageMultiFileSocket *>(input->storage);
536
537 if (!get_single_layer_image_base_path(socket.path, base_path)) {
538 /* TODO: propagate this error to the render pipeline and UI. */
539 BKE_report(nullptr,
540 RPT_ERROR,
541 "Invalid path template in File Output node. Skipping writing file.");
542 continue;
543 }
544
545 /* The image saving code expects EXR images to have a different structure than standard
546 * images. In particular, in EXR images, the buffers need to be stored in passes that are, in
547 * turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to
548 * be stored in views. An exception to this is stereo images, which needs to have the same
549 * structure as non-EXR images. */
550 const auto &format = socket.use_node_format ? node_storage(bnode()).format : socket.format;
551 const bool save_as_render = socket.use_node_format ? node_storage(bnode()).save_as_render :
552 socket.save_as_render;
553 const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR;
554 const int views_count = BKE_scene_multiview_num_views_get(&context().get_render_data());
555 if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) {
556 execute_single_layer_multi_view_exr(result, format, base_path, socket.layer);
557 continue;
558 }
559
560 char image_path[FILE_MAX];
561 get_single_layer_image_path(base_path, format, image_path);
562
563 const int2 size = result.domain().size;
564 FileOutput &file_output = context().render_context()->get_file_output(
565 image_path, format, size, save_as_render);
566
567 add_view_for_result(file_output, result, context().get_view_name().data());
568
569 add_meta_data_for_result(file_output, result, socket.layer);
570 }
571 }
572
573 /* -----------------------------------
574 * Single Layer Multi-View EXR Images.
575 */
576
578 const ImageFormatData &format,
579 const char *base_path,
580 const char *layer_name)
581 {
582 const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL;
583
584 /* The EXR stores all views in the same file, so we supply an empty view to make sure the file
585 * name does not contain a view suffix. */
586 char image_path[FILE_MAX];
587 const char *path_view = has_views ? "" : context().get_view_name().data();
588
589 if (!get_multi_layer_exr_image_path(base_path, path_view, false, image_path)) {
591 return;
592 }
593
594 const int2 size = result.domain().size;
595 FileOutput &file_output = context().render_context()->get_file_output(
596 image_path, format, size, true);
597
598 /* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we
599 * add a default unnamed view. */
600 const char *view_name = has_views ? context().get_view_name().data() : "";
601 file_output.add_view(view_name);
602 add_pass_for_result(file_output, result, "", view_name);
603
604 add_meta_data_for_result(file_output, result, layer_name);
605 }
606
607 /* -----------------------
608 * Multi-Layer EXR Images.
609 */
610
612 {
613 const bool store_views_in_single_file = is_multi_view_exr();
614 const char *view = context().get_view_name().data();
615
616 /* If we are saving all views in a single multi-layer file, we supply an empty view to make
617 * sure the file name does not contain a view suffix. */
618 char image_path[FILE_MAX];
619 const char *write_view = store_views_in_single_file ? "" : view;
620 if (!get_multi_layer_exr_image_path(get_base_path(), write_view, true, image_path)) {
621 /* TODO: propagate this error to the render pipeline and UI. */
623 nullptr, RPT_ERROR, "Invalid path template in File Output node. Skipping writing file.");
624 return;
625 }
626
627 const int2 size = compute_domain().size;
628 const ImageFormatData format = node_storage(bnode()).format;
629 FileOutput &file_output = context().render_context()->get_file_output(
630 image_path, format, size, true);
631
632 /* If we are saving views in separate files, we needn't store the view in the channel names, so
633 * we add an unnamed view. */
634 const char *pass_view = store_views_in_single_file ? view : "";
635 file_output.add_view(pass_view);
636
637 for (const bNodeSocket *input : this->node()->input_sockets()) {
639 continue;
640 }
641
642 const Result &input_result = get_input(input->identifier);
643 const char *pass_name = (static_cast<NodeImageMultiFileSocket *>(input->storage))->layer;
644 add_pass_for_result(file_output, input_result, pass_name, pass_view);
645
646 add_meta_data_for_result(file_output, input_result, pass_name);
647 }
648 }
649
650 /* Read the data stored in the given result and add a pass of the given name, view, and read
651 * buffer. The pass channel identifiers follows the EXR conventions. */
653 const Result &result,
654 const char *pass_name,
655 const char *view_name)
656 {
657 /* For single values, we fill a buffer that covers the domain of the operation with the value
658 * of the result. */
659 const int2 size = result.is_single_value() ? this->compute_domain().size :
660 result.domain().size;
661
662 /* The image buffer in the file output will take ownership of this buffer and freeing it will
663 * be its responsibility. */
664 float *buffer = nullptr;
665 if (result.is_single_value()) {
666 buffer = this->inflate_result(result, size);
667 }
668 else {
669 if (context().use_gpu()) {
671 buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
672 }
673 else {
674 /* Copy the result into a new buffer. */
675 buffer = static_cast<float *>(MEM_dupallocN(result.cpu_data().data()));
676 }
677 }
678
679 switch (result.type()) {
681 /* Use lowercase rgba for Cryptomatte layers because the EXR internal compression rules
682 * specify that all uppercase RGBA channels will be compressed, and Cryptomatte should not
683 * be compressed. */
684 if (result.meta_data.is_cryptomatte_layer()) {
685 file_output.add_pass(pass_name, view_name, "rgba", buffer);
686 }
687 else {
688 file_output.add_pass(pass_name, view_name, "RGBA", buffer);
689 }
690 break;
692 /* Float3 results might be stored in 4-component textures due to hardware limitations, so
693 * we need to convert the buffer to a 3-component buffer on the host. */
694 if (!result.is_single_value() && this->context().use_gpu() &&
696 {
697 file_output.add_pass(pass_name, view_name, "XYZ", float4_to_float3_image(size, buffer));
698 }
699 else {
700 file_output.add_pass(pass_name, view_name, "XYZ", buffer);
701 }
702 break;
704 file_output.add_pass(pass_name, view_name, "XYZW", buffer);
705 break;
707 file_output.add_pass(pass_name, view_name, "V", buffer);
708 break;
710 file_output.add_pass(pass_name, view_name, "XY", buffer);
711 break;
712 case ResultType::Int2:
713 file_output.add_pass(pass_name, view_name, "XY", buffer);
714 break;
715 case ResultType::Int:
716 file_output.add_pass(pass_name, view_name, "V", buffer);
717 break;
718 case ResultType::Bool:
719 file_output.add_pass(pass_name, view_name, "V", buffer);
720 break;
721 }
722 }
723
724 /* Allocates and fills an image buffer of the specified size with the value of the given single
725 * value result. */
726 float *inflate_result(const Result &result, const int2 size)
727 {
728 BLI_assert(result.is_single_value());
729
730 const int64_t length = int64_t(size.x) * size.y;
731 const int64_t buffer_size = length * result.channels_count();
732 float *buffer = MEM_malloc_arrayN<float>(buffer_size, "File Output Inflated Buffer.");
733
734 switch (result.type()) {
739 case ResultType::Color: {
740 const GPointer single_value = result.single_value();
741 single_value.type()->fill_assign_n(single_value.get(), buffer, length);
742 return buffer;
743 }
744 case ResultType::Int: {
745 const float value = float(result.get_single_value<int32_t>());
746 CPPType::get<float>().fill_assign_n(&value, buffer, length);
747 return buffer;
748 }
749 case ResultType::Int2: {
750 const float2 value = float2(result.get_single_value<int2>());
751 CPPType::get<float2>().fill_assign_n(&value, buffer, length);
752 return buffer;
753 }
754 case ResultType::Bool: {
755 const float value = float(result.get_single_value<bool>());
756 CPPType::get<float>().fill_assign_n(&value, buffer, length);
757 return buffer;
758 }
759 }
760
762 return nullptr;
763 }
764
765 /* Read the data stored the given result and add a view of the given name and read buffer. */
766 void add_view_for_result(FileOutput &file_output, const Result &result, const char *view_name)
767 {
768 /* The image buffer in the file output will take ownership of this buffer and freeing it will
769 * be its responsibility. */
770 float *buffer = nullptr;
771 if (context().use_gpu()) {
773 buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
774 }
775 else {
776 /* Copy the result into a new buffer. */
777 buffer = static_cast<float *>(MEM_dupallocN(result.cpu_data().data()));
778 }
779
780 const int2 size = result.domain().size;
781 switch (result.type()) {
783 file_output.add_view(view_name, 4, buffer);
784 break;
786 file_output.add_view(view_name, 4, buffer);
787 break;
789 /* Float3 results might be stored in 4-component textures due to hardware limitations, so
790 * we need to convert the buffer to a 3-component buffer on the host. */
791 if (!result.is_single_value() && this->context().use_gpu() &&
793 {
794 file_output.add_view(view_name, 3, float4_to_float3_image(size, buffer));
795 }
796 else {
797 file_output.add_view(view_name, 3, buffer);
798 }
799 break;
801 file_output.add_view(view_name, 1, buffer);
802 break;
804 case ResultType::Int2:
805 case ResultType::Int:
806 case ResultType::Bool:
807 /* Not supported. */
809 break;
810 }
811 }
812
813 /* Given a float4 image, return a newly allocated float3 image that ignores the last channel. The
814 * input image is freed. */
815 float *float4_to_float3_image(int2 size, float *float4_image)
816 {
817 float *float3_image = MEM_malloc_arrayN<float>(3 * size_t(size.x) * size_t(size.y),
818 "File Output Vector Buffer.");
819
820 parallel_for(size, [&](const int2 texel) {
821 for (int i = 0; i < 3; i++) {
822 const int64_t pixel_index = int64_t(texel.y) * size.x + texel.x;
823 float3_image[pixel_index * 3 + i] = float4_image[pixel_index * 4 + i];
824 }
825 });
826
827 MEM_freeN(float4_image);
828 return float3_image;
829 }
830
831 /* Add Cryptomatte meta data to the file if they exist for the given result of the given layer
832 * name. We do not write any other meta data for now. */
833 void add_meta_data_for_result(FileOutput &file_output, const Result &result, const char *name)
834 {
836
837 if (result.meta_data.is_cryptomatte_layer()) {
838 file_output.add_meta_data(
839 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "name"),
840 cryptomatte_layer_name);
841 }
842
843 if (!result.meta_data.cryptomatte.manifest.empty()) {
844 file_output.add_meta_data(
845 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "manifest"),
846 result.meta_data.cryptomatte.manifest);
847 }
848
849 if (!result.meta_data.cryptomatte.hash.empty()) {
850 file_output.add_meta_data(
851 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "hash"),
852 result.meta_data.cryptomatte.hash);
853 }
854
855 if (!result.meta_data.cryptomatte.conversion.empty()) {
856 file_output.add_meta_data(
857 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "conversion"),
858 result.meta_data.cryptomatte.conversion);
859 }
860 }
861
877 bool get_single_layer_image_base_path(const char *base_name, char *r_base_path)
878 {
879 const path_templates::VariableMap template_variables =
881
882 /* Do template expansion on the node's base path. */
883 char node_base_path[FILE_MAX] = "";
884 STRNCPY(node_base_path, get_base_path());
885 {
887 node_base_path, FILE_MAX, template_variables);
888 if (!errors.is_empty()) {
889 r_base_path[0] = '\0';
890 return false;
891 }
892 }
893
894 if (base_name[0]) {
895 /* Do template expansion on the socket's sub path ("base name"). */
896 char sub_path[FILE_MAX] = "";
897 STRNCPY(sub_path, base_name);
898 {
900 sub_path, FILE_MAX, template_variables);
901 if (!errors.is_empty()) {
902 r_base_path[0] = '\0';
903 return false;
904 }
905 }
906
907 /* Combine the base path and sub path. */
908 BLI_path_join(r_base_path, FILE_MAX, node_base_path, sub_path);
909 }
910 else {
911 /* Just use the base path, as a directory. */
912 BLI_strncpy(r_base_path, node_base_path, FILE_MAX);
913 BLI_path_slash_ensure(r_base_path, FILE_MAX);
914 }
915
916 return true;
917 }
918
919 /* Get the path of the image to be saved based on the given format. */
920 void get_single_layer_image_path(const char *base_path,
921 const ImageFormatData &format,
922 char *r_image_path)
923 {
924 BKE_image_path_from_imformat(r_image_path,
925 base_path,
927 /* No variables, because path templating is
928 * already done by
929 * `get_single_layer_image_base_path()` before
930 * this is called. */
931 nullptr,
932 context().get_frame_number(),
933 &format,
935 true,
936 nullptr);
937 }
938
954 bool get_multi_layer_exr_image_path(const char *base_path,
955 const char *view,
956 const bool apply_template,
957 char *r_image_path)
958 {
959 const RenderData &render_data = context().get_render_data();
960 const char *suffix = BKE_scene_multiview_view_suffix_get(&render_data, view);
961 const char *relbase = BKE_main_blendfile_path_from_global();
962 const path_templates::VariableMap template_variables =
965 r_image_path,
966 base_path,
967 relbase,
968 apply_template ? &template_variables : nullptr,
969 context().get_frame_number(),
972 true,
973 suffix);
974
975 if (!errors.is_empty()) {
976 r_image_path[0] = '\0';
977 }
978
979 return errors.is_empty();
980 }
981
983 {
984 return node_storage(bnode()).format.imtype == R_IMF_IMTYPE_MULTILAYER;
985 }
986
987 const char *get_base_path()
988 {
989 return node_storage(bnode()).base_path;
990 }
991
992 /* Add the file format extensions to the rendered file name. */
994 {
995 return context().get_render_data().scemode & R_EXTENSION;
996 }
997
998 /* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */
1000 {
1001 if (!is_multi_view_scene()) {
1002 return false;
1003 }
1004
1005 return node_storage(bnode()).format.views_format == R_IMF_VIEWS_MULTIVIEW;
1006 }
1007
1009 {
1010 return context().get_render_data().scemode & R_MULTIVIEW;
1011 }
1012};
1013
1015{
1016 return new FileOutputOperation(context, node);
1017}
1018
1019} // namespace blender::nodes::node_composite_file_output_cc
1020
1022{
1024
1025 static blender::bke::bNodeType ntype;
1026
1027 cmp_node_type_base(&ntype, "CompositorNodeOutputFile", CMP_NODE_OUTPUT_FILE);
1028 ntype.ui_name = "File Output";
1029 ntype.ui_description = "Write image file to disk";
1030 ntype.enum_name_legacy = "OUTPUT_FILE";
1031 ntype.nclass = NODE_CLASS_OUTPUT;
1032 ntype.draw_buttons = file_ns::node_composit_buts_file_output;
1033 ntype.draw_buttons_ex = file_ns::node_composit_buts_file_output_ex;
1034 ntype.initfunc_api = file_ns::init_output_file;
1035 ntype.flag |= NODE_PREVIEW;
1037 ntype, "NodeImageMultiFile", file_ns::free_output_file, file_ns::copy_output_file);
1038 ntype.updatefunc = file_ns::update_output_file;
1039 ntype.get_compositor_operation = file_ns::get_compositor_operation;
1040
1042}
Scene * CTX_data_scene(const bContext *C)
void BKE_image_format_free(ImageFormatData *imf)
void BKE_image_format_init(ImageFormatData *imf, const bool render)
blender::Vector< blender::bke::path_templates::Error > BKE_image_path_from_imtype(char *filepath, const char *base, const char *relbase, const blender::bke::path_templates::VariableMap *template_variables, int frame, char imtype, bool use_ext, bool use_frames, const char *suffix)
void BKE_image_format_update_color_space_for_type(ImageFormatData *format)
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)
bool BKE_imtype_is_movie(char imtype)
void BKE_image_format_copy(ImageFormatData *imf_dst, const ImageFormatData *imf_src)
const char * BKE_main_blendfile_path_from_global()
Definition main.cc:882
#define NODE_CLASS_OUTPUT
Definition BKE_node.hh:434
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define CMP_NODE_OUTPUT_FILE
void BKE_ntree_update_tag_socket_property(bNodeTree *ntree, bNodeSocket *socket)
blender::bke::path_templates::VariableMap BKE_build_template_variables_for_render_path(const RenderData *render_data)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
int BKE_scene_multiview_num_views_get(const RenderData *rd)
Definition scene.cc:2952
const char * BKE_scene_multiview_view_suffix_get(const RenderData *rd, const char *viewname)
Definition scene.cc:3149
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
#define LISTBASE_FOREACH(type, var, list)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
#define FILE_MAX
#define BLI_path_join(...)
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STRNCPY_UTF8(dst, src)
size_t void BLI_uniquename_cb(blender::FunctionRef< bool(blender::StringRefNull)> unique_check, const char *defname, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(2
#define ELEM(...)
#define IFACE_(msgid)
#define DATA_(msgid)
@ SOCK_IN
@ NODE_PREVIEW
@ SOCK_RGBA
@ R_IMF_IMTYPE_OPENEXR
@ R_IMF_IMTYPE_MULTILAYER
@ R_IMF_COLOR_MANAGEMENT_FOLLOW_SCENE
@ R_IMF_VIEWS_MULTIVIEW
@ R_IMF_VIEWS_STEREO_3D
@ R_IMF_VIEWS_INDIVIDUAL
@ R_MULTIVIEW
@ R_EXTENSION
static AppView * view
void GPU_memory_barrier(eGPUBarrier barrier)
Definition gpu_state.cc:385
@ GPU_BARRIER_TEXTURE_UPDATE
Definition GPU_state.hh:39
void * GPU_texture_read(GPUTexture *texture, eGPUDataFormat data_format, int mip_level)
@ GPU_DATA_FLOAT
size_t GPU_texture_component_len(eGPUTextureFormat format)
eGPUTextureFormat GPU_texture_format(const GPUTexture *texture)
Read Guarded memory(de)allocation.
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_NONE
Definition RNA_types.hh:221
#define C
Definition RandGen.cpp:29
void uiTemplateImageSettings(uiLayout *layout, PointerRNA *imfptr, bool color_management)
void uiTemplateList(uiLayout *layout, const bContext *C, const char *listtype_name, const char *list_id, PointerRNA *dataptr, blender::StringRefNull propname, PointerRNA *active_dataptr, const char *active_propname, const char *item_dyntip_propname, int rows, int maxrows, int layout_type, int columns, enum uiTemplateListFlags flags)
void uiTemplateImageFormatViews(uiLayout *layout, PointerRNA *imfptr, PointerRNA *ptr)
@ UI_TEMPLATE_LIST_FLAG_NONE
@ UI_ITEM_R_SPLIT_EMPTY_NAME
@ UI_ITEM_R_ICON_ONLY
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:238
@ WM_OP_EXEC_DEFAULT
Definition WM_types.hh:245
BMesh const char void * data
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static const CPPType & get()
void fill_assign_n(const void *value, void *dst, int64_t n) const
const CPPType * type() const
const void * get() const
bool is_empty() 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)
virtual Domain compute_domain()
Definition operation.cc:56
void execute_single_layer_multi_view_exr(const Result &result, const ImageFormatData &format, const char *base_path, const char *layer_name)
bool get_multi_layer_exr_image_path(const char *base_path, const char *view, const bool apply_template, char *r_image_path)
void get_single_layer_image_path(const char *base_path, const ImageFormatData &format, char *r_image_path)
void add_pass_for_result(FileOutput &file_output, const Result &result, const char *pass_name, const char *view_name)
bool get_single_layer_image_base_path(const char *base_name, char *r_base_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)
uint col
#define input
#define this
float length(VecOp< float, D >) RET
format
descriptor
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
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_modify_socket_type_static(bNodeTree *ntree, bNode *node, bNodeSocket *sock, int type, int subtype)
Definition node.cc:3123
void node_remove_socket(bNodeTree &ntree, bNode &node, bNodeSocket &sock)
Definition node.cc:3575
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
bNodeSocket * node_add_static_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out, int type, int subtype, StringRefNull identifier, StringRefNull name)
Definition node.cc:3529
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:5603
void parallel_for(const int2 range, const Function &function)
bool is_socket_available(const bNodeSocket *socket)
Definition utilities.cc:25
static void copy_output_file(bNodeTree *, bNode *dest_node, const bNode *src_node)
static void init_output_file(const bContext *C, PointerRNA *ptr)
static void update_output_file(bNodeTree *ntree, bNode *node)
static void node_composit_buts_file_output(uiLayout *layout, bContext *, PointerRNA *ptr)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void node_composit_buts_file_output_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static bool unique_path_unique_check(ListBase *lb, bNodeSocket *sock, const blender::StringRef name)
void ntreeCompositOutputFileUniqueLayer(ListBase *list, bNodeSocket *sock, const char defname[], char delim)
void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name)
static bool unique_layer_unique_check(ListBase *lb, bNodeSocket *sock, const blender::StringRef name)
int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node)
static void register_node_type_cmp_output_file()
void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name)
bNodeSocket * ntreeCompositOutputFileAddSocket(bNodeTree *ntree, bNode *node, const char *name, const ImageFormatData *im_format)
void ntreeCompositOutputFileUniquePath(ListBase *list, bNodeSocket *sock, const char defname[], char delim)
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void cmp_node_update_default(bNodeTree *, bNode *node)
blender::Vector< Error > BKE_path_apply_template(char *path, int path_max_length, const VariableMap &template_variables)
PointerRNA RNA_pointer_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
int RNA_int_get(PointerRNA *ptr, const char *name)
bool RNA_property_collection_lookup_int(PointerRNA *ptr, PropertyRNA *prop, int key, PointerRNA *r_ptr)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
void RNA_enum_set(PointerRNA *ptr, const char *name, int value)
int RNA_enum_get(PointerRNA *ptr, const char *name)
void * first
ImageFormatData format
ID * owner_id
Definition RNA_types.hh:51
void * data
Definition RNA_types.hh:53
struct ImageFormatData im_format
char pic[1024]
struct RenderData r
struct bNodeSocket * next
ListBase inputs
void * storage
ListBase outputs
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:336
void(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:249
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
void(* updatefunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:269
void(* initfunc_api)(const bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:290
PointerRNA op(wmOperatorType *ot, std::optional< blender::StringRef > name, int icon, wmOperatorCallContext context, eUI_Item_Flag flag)
void label(blender::StringRef name, int icon)
uiLayout & column(bool align)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
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
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
PointerRNA * ptr
Definition wm_files.cc:4227
wmOperatorType * ot
Definition wm_files.cc:4226
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)