Blender V4.3
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
9#include <cstring>
10
11#include "BLI_assert.h"
12#include "BLI_fileops.h"
13#include "BLI_index_range.hh"
14#include "BLI_math_vector.h"
15#include "BLI_path_utils.hh"
16#include "BLI_string.h"
17#include "BLI_string_utf8.h"
18#include "BLI_string_utils.hh"
19#include "BLI_task.hh"
20#include "BLI_utildefines.h"
21
22#include "MEM_guardedalloc.h"
23
24#include "DNA_node_types.h"
25#include "DNA_scene_types.h"
26
27#include "BKE_context.hh"
28#include "BKE_cryptomatte.hh"
29#include "BKE_image.hh"
30#include "BKE_image_format.hh"
31#include "BKE_main.hh"
33#include "BKE_scene.hh"
34
35#include "RNA_access.hh"
36#include "RNA_prototypes.hh"
37
38#include "UI_interface.hh"
39#include "UI_resources.hh"
40
41#include "WM_api.hh"
42
43#include "IMB_imbuf.hh"
44#include "IMB_imbuf_types.hh"
45#include "IMB_openexr.hh"
46
47#include "GPU_state.hh"
48#include "GPU_texture.hh"
49
50#include "COM_node_operation.hh"
51
53
55
56/* **************** OUTPUT FILE ******************** */
57
58/* find unique path */
59static bool unique_path_unique_check(void *arg, const char *name)
60{
61 struct Args {
62 ListBase *lb;
63 bNodeSocket *sock;
64 };
65 Args *data = (Args *)arg;
66
67 LISTBASE_FOREACH (bNodeSocket *, sock, data->lb) {
68 if (sock != data->sock) {
69 NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
70 if (STREQ(sockdata->path, name)) {
71 return true;
72 }
73 }
74 }
75 return false;
76}
78 bNodeSocket *sock,
79 const char defname[],
80 char delim)
81{
83 struct {
84 ListBase *lb;
85 bNodeSocket *sock;
86 } data;
87 data.lb = list;
88 data.sock = sock;
89
90 /* See if we are given an empty string */
91 if (ELEM(nullptr, sock, defname)) {
92 return;
93 }
94
95 sockdata = (NodeImageMultiFileSocket *)sock->storage;
97 unique_path_unique_check, &data, defname, delim, sockdata->path, sizeof(sockdata->path));
98}
99
100/* find unique EXR layer */
101static bool unique_layer_unique_check(void *arg, const char *name)
102{
103 struct Args {
104 ListBase *lb;
105 bNodeSocket *sock;
106 };
107 Args *data = (Args *)arg;
108
109 LISTBASE_FOREACH (bNodeSocket *, sock, data->lb) {
110 if (sock != data->sock) {
111 NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
112 if (STREQ(sockdata->layer, name)) {
113 return true;
114 }
115 }
116 }
117 return false;
118}
120 bNodeSocket *sock,
121 const char defname[],
122 char delim)
123{
124 struct {
125 ListBase *lb;
126 bNodeSocket *sock;
127 } data;
128 data.lb = list;
129 data.sock = sock;
130
131 /* See if we are given an empty string */
132 if (ELEM(nullptr, sock, defname)) {
133 return;
134 }
135
138 unique_layer_unique_check, &data, defname, delim, sockdata->layer, sizeof(sockdata->layer));
139}
140
142 bNode *node,
143 const char *name,
144 const ImageFormatData *im_format)
145{
146 NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
148 ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, nullptr, name);
149
150 /* create format data for the input socket */
151 NodeImageMultiFileSocket *sockdata = MEM_cnew<NodeImageMultiFileSocket>(__func__);
152 sock->storage = sockdata;
153
154 STRNCPY_UTF8(sockdata->path, name);
155 ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
156 STRNCPY_UTF8(sockdata->layer, name);
157 ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
158
159 if (im_format) {
160 BKE_image_format_copy(&sockdata->format, im_format);
162 if (BKE_imtype_is_movie(sockdata->format.imtype)) {
164 }
165 }
166 else {
167 BKE_image_format_init(&sockdata->format, false);
168 }
170
171 /* use node data format by default */
172 sockdata->use_node_format = true;
173 sockdata->save_as_render = true;
174
175 nimf->active_input = BLI_findindex(&node->inputs, sock);
176
177 return sock;
178}
179
181{
182 NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
183 bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->inputs, nimf->active_input);
184 int totinputs = BLI_listbase_count(&node->inputs);
185
186 if (!sock) {
187 return 0;
188 }
189
190 if (nimf->active_input == totinputs - 1) {
191 --nimf->active_input;
192 }
193
194 /* free format data */
195 MEM_freeN(sock->storage);
196
197 blender::bke::node_remove_socket(ntree, node, sock);
198 return 1;
199}
200
201void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name)
202{
204 STRNCPY_UTF8(sockdata->path, name);
205 ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
206}
207
208void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name)
209{
211 STRNCPY_UTF8(sockdata->layer, name);
212 ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
213}
214
216
218
219/* XXX uses initfunc_api callback, regular initfunc does not support context yet */
221{
222 Scene *scene = CTX_data_scene(C);
223 bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
224 bNode *node = (bNode *)ptr->data;
225 NodeImageMultiFile *nimf = MEM_cnew<NodeImageMultiFile>(__func__);
226 nimf->save_as_render = true;
227 ImageFormatData *format = nullptr;
228 node->storage = nimf;
229
230 if (scene) {
231 RenderData *rd = &scene->r;
232
233 STRNCPY(nimf->base_path, rd->pic);
236 if (BKE_imtype_is_movie(nimf->format.imtype)) {
238 }
239
240 format = &nimf->format;
241 }
242 else {
243 BKE_image_format_init(&nimf->format, false);
244 }
246
247 /* add one socket by default */
248 ntreeCompositOutputFileAddSocket(ntree, node, "Image", format);
249}
250
251static void free_output_file(bNode *node)
252{
253 /* free storage data in sockets */
254 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
255 NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
256 BKE_image_format_free(&sockdata->format);
257 MEM_freeN(sock->storage);
258 }
259
260 NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
262 MEM_freeN(node->storage);
263}
264
265static void copy_output_file(bNodeTree * /*dst_ntree*/, bNode *dest_node, const bNode *src_node)
266{
267 bNodeSocket *src_sock, *dest_sock;
268
269 dest_node->storage = MEM_dupallocN(src_node->storage);
270 NodeImageMultiFile *dest_nimf = (NodeImageMultiFile *)dest_node->storage;
271 NodeImageMultiFile *src_nimf = (NodeImageMultiFile *)src_node->storage;
272 BKE_image_format_copy(&dest_nimf->format, &src_nimf->format);
273
274 /* duplicate storage data in sockets */
275 for (src_sock = (bNodeSocket *)src_node->inputs.first,
276 dest_sock = (bNodeSocket *)dest_node->inputs.first;
277 src_sock && dest_sock;
278 src_sock = src_sock->next, dest_sock = (bNodeSocket *)dest_sock->next)
279 {
280 dest_sock->storage = MEM_dupallocN(src_sock->storage);
281 NodeImageMultiFileSocket *dest_sockdata = (NodeImageMultiFileSocket *)dest_sock->storage;
282 NodeImageMultiFileSocket *src_sockdata = (NodeImageMultiFileSocket *)src_sock->storage;
283 BKE_image_format_copy(&dest_sockdata->format, &src_sockdata->format);
284 }
285}
286
287static void update_output_file(bNodeTree *ntree, bNode *node)
288{
289 /* XXX fix for #36706: remove invalid sockets added with bpy API.
290 * This is not ideal, but prevents crashes from missing storage.
291 * FileOutput node needs a redesign to support this properly.
292 */
293 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
294 if (sock->storage == nullptr) {
295 blender::bke::node_remove_socket(ntree, node, sock);
296 }
297 }
298 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
299 blender::bke::node_remove_socket(ntree, node, sock);
300 }
301
302 cmp_node_update_default(ntree, node);
303
304 /* automatically update the socket type based on linked input */
305 ntree->ensure_topology_cache();
306 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
307 if (sock->is_logically_linked()) {
308 const bNodeSocket *from_socket = sock->logically_linked_sockets()[0];
309 if (sock->type != from_socket->type) {
310 blender::bke::node_modify_socket_type_static(ntree, node, sock, from_socket->type, 0);
312 }
313 }
314 }
315}
316
318{
319 PointerRNA imfptr = RNA_pointer_get(ptr, "format");
320 const bool multilayer = RNA_enum_get(&imfptr, "file_format") == R_IMF_IMTYPE_MULTILAYER;
321
322 if (multilayer) {
323 uiItemL(layout, IFACE_("Path:"), ICON_NONE);
324 }
325 else {
326 uiItemL(layout, IFACE_("Base Path:"), ICON_NONE);
327 }
328 uiItemR(layout, ptr, "base_path", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
329}
330
332{
333 Scene *scene = CTX_data_scene(C);
334 PointerRNA imfptr = RNA_pointer_get(ptr, "format");
335 PointerRNA active_input_ptr, op_ptr;
336 uiLayout *row, *col;
337 const bool multilayer = RNA_enum_get(&imfptr, "file_format") == R_IMF_IMTYPE_MULTILAYER;
338 const bool is_multiview = (scene->r.scemode & R_MULTIVIEW) != 0;
339
341
342 {
343 uiLayout *column = uiLayoutColumn(layout, true);
344 uiLayoutSetPropSep(column, true);
345 uiLayoutSetPropDecorate(column, false);
346 uiItemR(column, ptr, "save_as_render", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
347 }
348 const bool save_as_render = RNA_boolean_get(ptr, "save_as_render");
349 uiTemplateImageSettings(layout, &imfptr, save_as_render);
350
351 if (!save_as_render) {
352 uiLayout *col = uiLayoutColumn(layout, true);
353 uiLayoutSetPropSep(col, true);
355
356 PointerRNA linear_settings_ptr = RNA_pointer_get(&imfptr, "linear_colorspace_settings");
357 uiItemR(col, &linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
358 }
359
360 /* disable stereo output for multilayer, too much work for something that no one will use */
361 /* if someone asks for that we can implement it */
362 if (is_multiview) {
363 uiTemplateImageFormatViews(layout, &imfptr, nullptr);
364 }
365
366 uiItemS(layout);
367
368 uiItemO(layout, IFACE_("Add Input"), ICON_ADD, "NODE_OT_output_file_add_socket");
369
370 row = uiLayoutRow(layout, false);
371 col = uiLayoutColumn(row, true);
372
373 const int active_index = RNA_int_get(ptr, "active_input_index");
374 /* using different collection properties if multilayer format is enabled */
375 if (multilayer) {
377 C,
378 "UI_UL_list",
379 "file_output_node",
380 ptr,
381 "layer_slots",
382 ptr,
383 "active_input_index",
384 nullptr,
385 0,
386 0,
387 0,
388 0,
391 ptr, RNA_struct_find_property(ptr, "layer_slots"), active_index, &active_input_ptr);
392 }
393 else {
395 C,
396 "UI_UL_list",
397 "file_output_node",
398 ptr,
399 "file_slots",
400 ptr,
401 "active_input_index",
402 nullptr,
403 0,
404 0,
405 0,
406 0,
409 ptr, RNA_struct_find_property(ptr, "file_slots"), active_index, &active_input_ptr);
410 }
411 /* XXX collection lookup does not return the ID part of the pointer,
412 * setting this manually here */
413 active_input_ptr.owner_id = ptr->owner_id;
414
415 col = uiLayoutColumn(row, true);
416 wmOperatorType *ot = WM_operatortype_find("NODE_OT_output_file_move_active_socket", false);
417 uiItemFullO_ptr(col, ot, "", ICON_TRIA_UP, nullptr, WM_OP_INVOKE_DEFAULT, UI_ITEM_NONE, &op_ptr);
418 RNA_enum_set(&op_ptr, "direction", 1);
420 col, ot, "", ICON_TRIA_DOWN, nullptr, WM_OP_INVOKE_DEFAULT, UI_ITEM_NONE, &op_ptr);
421 RNA_enum_set(&op_ptr, "direction", 2);
422
423 if (active_input_ptr.data) {
424 if (multilayer) {
425 col = uiLayoutColumn(layout, true);
426
427 uiItemL(col, IFACE_("Layer:"), ICON_NONE);
428 row = uiLayoutRow(col, false);
429 uiItemR(row, &active_input_ptr, "name", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
430 uiItemFullO(row,
431 "NODE_OT_output_file_remove_active_socket",
432 "",
433 ICON_X,
434 nullptr,
437 nullptr);
438 }
439 else {
440 col = uiLayoutColumn(layout, true);
441
442 uiItemL(col, IFACE_("File Subpath:"), ICON_NONE);
443 row = uiLayoutRow(col, false);
444 uiItemR(row, &active_input_ptr, "path", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
445 uiItemFullO(row,
446 "NODE_OT_output_file_remove_active_socket",
447 "",
448 ICON_X,
449 nullptr,
452 nullptr);
453
454 /* format details for individual files */
455 imfptr = RNA_pointer_get(&active_input_ptr, "format");
456
457 col = uiLayoutColumn(layout, true);
458 uiItemL(col, IFACE_("Format:"), ICON_NONE);
459 uiItemR(col,
460 &active_input_ptr,
461 "use_node_format",
463 nullptr,
464 ICON_NONE);
465
466 const bool use_node_format = RNA_boolean_get(&active_input_ptr, "use_node_format");
467
468 if (!use_node_format) {
469 {
470 uiLayout *column = uiLayoutColumn(layout, true);
471 uiLayoutSetPropSep(column, true);
472 uiLayoutSetPropDecorate(column, false);
473 uiItemR(column,
474 &active_input_ptr,
475 "save_as_render",
477 nullptr,
478 ICON_NONE);
479 }
480
481 const bool use_color_management = RNA_boolean_get(&active_input_ptr, "save_as_render");
482
483 col = uiLayoutColumn(layout, false);
484 uiTemplateImageSettings(col, &imfptr, use_color_management);
485
486 if (!use_color_management) {
487 uiLayout *col = uiLayoutColumn(layout, true);
488 uiLayoutSetPropSep(col, true);
490
491 PointerRNA linear_settings_ptr = RNA_pointer_get(&imfptr, "linear_colorspace_settings");
492 uiItemR(
493 col, &linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
494 }
495
496 if (is_multiview) {
497 col = uiLayoutColumn(layout, false);
498 uiTemplateImageFormatViews(col, &imfptr, nullptr);
499 }
500 }
501 }
502 }
503}
504
505using namespace blender::realtime_compositor;
506
508 public:
509 FileOutputOperation(Context &context, DNode node) : NodeOperation(context, node)
510 {
511 for (const bNodeSocket *input : node->input_sockets()) {
512 InputDescriptor &descriptor = this->get_input_descriptor(input->identifier);
513 /* Inputs for multi-layer files need to be the same size, while they can be different for
514 * individual file outputs. */
515 descriptor.realization_options.realize_on_operation_domain = this->is_multi_layer();
516 }
517 }
518
519 void execute() override
520 {
521 if (is_multi_layer()) {
523 }
524 else {
526 }
527 }
528
529 /* --------------------
530 * Single Layer Images.
531 */
532
534 {
535 for (const bNodeSocket *input : this->node()->input_sockets()) {
536 const Result &result = get_input(input->identifier);
537 /* We only write images, not single values. */
538 if (result.is_single_value()) {
539 continue;
540 }
541
542 char base_path[FILE_MAX];
543 const auto &socket = *static_cast<NodeImageMultiFileSocket *>(input->storage);
544 get_single_layer_image_base_path(socket.path, base_path);
545
546 /* The image saving code expects EXR images to have a different structure than standard
547 * images. In particular, in EXR images, the buffers need to be stored in passes that are, in
548 * turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to
549 * be stored in views. An exception to this is stereo images, which needs to have the same
550 * structure as non-EXR images. */
551 const auto &format = socket.use_node_format ? node_storage(bnode()).format : socket.format;
552 const bool save_as_render = socket.use_node_format ? node_storage(bnode()).save_as_render :
553 socket.save_as_render;
554 const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR;
555 const int views_count = BKE_scene_multiview_num_views_get(&context().get_render_data());
556 if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) {
557 execute_single_layer_multi_view_exr(result, format, base_path, socket.layer);
558 continue;
559 }
560
561 char image_path[FILE_MAX];
562 get_single_layer_image_path(base_path, format, image_path);
563
564 const int2 size = result.domain().size;
566 image_path, format, size, save_as_render);
567
568 add_view_for_result(file_output, result, context().get_view_name().data());
569
570 add_meta_data_for_result(file_output, result, socket.layer);
571 }
572 }
573
574 /* -----------------------------------
575 * Single Layer Multi-View EXR Images.
576 */
577
579 const ImageFormatData &format,
580 const char *base_path,
581 const char *layer_name)
582 {
583 const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL;
584
585 /* The EXR stores all views in the same file, so we supply an empty view to make sure the file
586 * name does not contain a view suffix. */
587 char image_path[FILE_MAX];
588 const char *path_view = has_views ? "" : context().get_view_name().data();
589 get_multi_layer_exr_image_path(base_path, path_view, image_path);
590
591 const int2 size = result.domain().size;
593 image_path, format, size, true);
594
595 /* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we
596 * add a default unnamed view. */
597 const char *view_name = has_views ? context().get_view_name().data() : "";
598 file_output.add_view(view_name);
599 add_pass_for_result(file_output, result, "", view_name);
600
601 add_meta_data_for_result(file_output, result, layer_name);
602 }
603
604 /* -----------------------
605 * Multi-Layer EXR Images.
606 */
607
609 {
610 const bool store_views_in_single_file = is_multi_view_exr();
611 const char *view = context().get_view_name().data();
612
613 /* If we are saving all views in a single multi-layer file, we supply an empty view to make
614 * sure the file name does not contain a view suffix. */
615 char image_path[FILE_MAX];
616 const char *write_view = store_views_in_single_file ? "" : view;
617 get_multi_layer_exr_image_path(get_base_path(), write_view, image_path);
618
619 const int2 size = compute_domain().size;
620 const ImageFormatData format = node_storage(bnode()).format;
622 image_path, format, size, true);
623
624 /* If we are saving views in separate files, we needn't store the view in the channel names, so
625 * we add an unnamed view. */
626 const char *pass_view = store_views_in_single_file ? view : "";
627 file_output.add_view(pass_view);
628
629 for (const bNodeSocket *input : this->node()->input_sockets()) {
630 const Result &input_result = get_input(input->identifier);
631 const char *pass_name = (static_cast<NodeImageMultiFileSocket *>(input->storage))->layer;
632 add_pass_for_result(file_output, input_result, pass_name, pass_view);
633
634 add_meta_data_for_result(file_output, input_result, pass_name);
635 }
636 }
637
638 /* Read the data stored in the GPU texture of the given result and add a pass of the given name,
639 * view, and read buffer. The pass channel identifiers follows the EXR conventions. */
641 const Result &result,
642 const char *pass_name,
643 const char *view_name)
644 {
645 /* For single values, we fill a buffer that covers the domain of the operation with the value
646 * of the result. */
647 const int2 size = result.is_single_value() ? this->compute_domain().size :
648 result.domain().size;
649
650 /* The image buffer in the file output will take ownership of this buffer and freeing it will
651 * be its responsibility. */
652 float *buffer = nullptr;
653 if (result.is_single_value()) {
654 buffer = this->inflate_result(result, size);
655 }
656 else {
658 buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
659 }
660
661 switch (result.type()) {
662 case ResultType::Color:
663 /* Use lowercase rgba for Cryptomatte layers because the EXR internal compression rules
664 * specify that all uppercase RGBA channels will be compressed, and Cryptomatte should not
665 * be compressed. */
666 if (result.meta_data.is_cryptomatte_layer()) {
667 file_output.add_pass(pass_name, view_name, "rgba", buffer);
668 }
669 else {
670 file_output.add_pass(pass_name, view_name, "RGBA", buffer);
671 }
672 break;
673 case ResultType::Vector:
674 if (result.meta_data.is_4d_vector) {
675 file_output.add_pass(pass_name, view_name, "XYZW", buffer);
676 }
677 else {
678 file_output.add_pass(pass_name, view_name, "XYZ", float4_to_float3_image(size, buffer));
679 }
680 break;
681 case ResultType::Float:
682 file_output.add_pass(pass_name, view_name, "V", buffer);
683 break;
684 default:
685 /* Other types are internal and needn't be handled by operations. */
687 break;
688 }
689 }
690
691 /* Allocates and fills an image buffer of the specified size with the value of the given single
692 * value result. */
693 float *inflate_result(const Result &result, const int2 size)
694 {
695 BLI_assert(result.is_single_value());
696
697 switch (result.type()) {
698 case ResultType::Float: {
699 float *buffer = static_cast<float *>(MEM_malloc_arrayN(
700 size_t(size.x) * size.y, sizeof(float), "File Output Inflated Buffer."));
701
702 const float value = result.get_float_value();
703 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
704 for (const int64_t y : sub_y_range) {
705 for (const int64_t x : IndexRange(size.x)) {
706 buffer[y * size.x + x] = value;
707 }
708 }
709 });
710 return buffer;
711 }
712 case ResultType::Vector:
713 case ResultType::Color: {
714 float *buffer = static_cast<float *>(MEM_malloc_arrayN(
715 size_t(size.x) * size.y, sizeof(float[4]), "File Output Inflated Buffer."));
716
717 const float4 value = result.type() == ResultType::Color ? result.get_color_value() :
718 result.get_vector_value();
719 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
720 for (const int64_t y : sub_y_range) {
721 for (const int64_t x : IndexRange(size.x)) {
722 copy_v4_v4(buffer + ((y * size.x + x) * 4), value);
723 }
724 }
725 });
726 return buffer;
727 }
728 default:
729 /* Other types are internal and needn't be handled by operations. */
730 break;
731 }
732
734 return nullptr;
735 }
736
737 /* Read the data stored in the GPU texture of the given result and add a view of the given name
738 * and read buffer. */
739 void add_view_for_result(FileOutput &file_output, const Result &result, const char *view_name)
740 {
741 /* The image buffer in the file output will take ownership of this buffer and freeing it will
742 * be its responsibility. */
744 float *buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
745
746 const int2 size = result.domain().size;
747 switch (result.type()) {
748 case ResultType::Color:
749 file_output.add_view(view_name, 4, buffer);
750 break;
751 case ResultType::Vector:
752 file_output.add_view(view_name, 3, float4_to_float3_image(size, buffer));
753 break;
754 case ResultType::Float:
755 file_output.add_view(view_name, 1, buffer);
756 break;
757 default:
758 /* Other types are internal and needn't be handled by operations. */
760 break;
761 }
762 }
763
764 /* Given a float4 image, return a newly allocated float3 image that ignores the last channel. The
765 * input image is freed. */
766 float *float4_to_float3_image(int2 size, float *float4_image)
767 {
768 float *float3_image = static_cast<float *>(MEM_malloc_arrayN(
769 size_t(size.x) * size.y, sizeof(float[3]), "File Output Vector Buffer."));
770
771 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
772 for (const int64_t y : sub_y_range) {
773 for (const int64_t x : IndexRange(size.x)) {
774 for (int i = 0; i < 3; i++) {
775 const int pixel_index = y * size.x + x;
776 float3_image[pixel_index * 3 + i] = float4_image[pixel_index * 4 + i];
777 }
778 }
779 }
780 });
781
782 MEM_freeN(float4_image);
783 return float3_image;
784 }
785
786 /* Add Cryptomatte meta data to the file if they exist for the given result of the given layer
787 * name. We do not write any other meta data for now. */
788 void add_meta_data_for_result(FileOutput &file_output, const Result &result, const char *name)
789 {
790 StringRef cryptomatte_layer_name = bke::cryptomatte::BKE_cryptomatte_extract_layer_name(name);
791
792 if (result.meta_data.is_cryptomatte_layer()) {
793 file_output.add_meta_data(
794 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "name"),
795 cryptomatte_layer_name);
796 }
797
798 if (!result.meta_data.cryptomatte.manifest.empty()) {
799 file_output.add_meta_data(
800 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "manifest"),
801 result.meta_data.cryptomatte.manifest);
802 }
803
804 if (!result.meta_data.cryptomatte.hash.empty()) {
805 file_output.add_meta_data(
806 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "hash"),
807 result.meta_data.cryptomatte.hash);
808 }
809
810 if (!result.meta_data.cryptomatte.conversion.empty()) {
811 file_output.add_meta_data(
812 bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "conversion"),
813 result.meta_data.cryptomatte.conversion);
814 }
815 }
816
817 /* Get the base path of the image to be saved, based on the base path of the node. The base name
818 * is an optional initial name of the image, which will later be concatenated with other
819 * information like the frame number, view, and extension. If the base name is empty, then the
820 * base path represents a directory, so a trailing slash is ensured. */
821 void get_single_layer_image_base_path(const char *base_name, char *base_path)
822 {
823 if (base_name[0]) {
824 BLI_path_join(base_path, FILE_MAX, get_base_path(), base_name);
825 }
826 else {
827 BLI_strncpy(base_path, get_base_path(), FILE_MAX);
829 }
830 }
831
832 /* Get the path of the image to be saved based on the given format. */
833 void get_single_layer_image_path(const char *base_path,
834 const ImageFormatData &format,
835 char *image_path)
836 {
838 base_path,
840 context().get_frame_number(),
841 &format,
842 use_file_extension(),
843 true,
844 nullptr);
845 }
846
847 /* Get the path of the EXR image to be saved. If the given view is not empty, its corresponding
848 * file suffix will be appended to the name. */
849 void get_multi_layer_exr_image_path(const char *base_path, const char *view, char *image_path)
850 {
851 const char *suffix = BKE_scene_multiview_view_suffix_get(&context().get_render_data(), view);
853 base_path,
855 context().get_frame_number(),
857 use_file_extension(),
858 true,
859 suffix);
860 }
861
863 {
864 return node_storage(bnode()).format.imtype == R_IMF_IMTYPE_MULTILAYER;
865 }
866
867 const char *get_base_path()
868 {
869 return node_storage(bnode()).base_path;
870 }
871
872 /* Add the file format extensions to the rendered file name. */
874 {
875 return context().get_render_data().scemode & R_EXTENSION;
876 }
877
878 /* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */
880 {
881 if (!is_multi_view_scene()) {
882 return false;
883 }
884
885 return node_storage(bnode()).format.views_format == R_IMF_VIEWS_MULTIVIEW;
886 }
887
889 {
890 return context().get_render_data().scemode & R_MULTIVIEW;
891 }
892};
893
895{
896 return new FileOutputOperation(context, node);
897}
898
899} // namespace blender::nodes::node_composite_file_output_cc
900
902{
904
905 static blender::bke::bNodeType ntype;
906
907 cmp_node_type_base(&ntype, CMP_NODE_OUTPUT_FILE, "File Output", NODE_CLASS_OUTPUT);
908 ntype.draw_buttons = file_ns::node_composit_buts_file_output;
909 ntype.draw_buttons_ex = file_ns::node_composit_buts_file_output_ex;
910 ntype.initfunc_api = file_ns::init_output_file;
911 ntype.flag |= NODE_PREVIEW;
913 &ntype, "NodeImageMultiFile", file_ns::free_output_file, file_ns::copy_output_file);
914 ntype.updatefunc = file_ns::update_output_file;
915 ntype.get_compositor_operation = file_ns::get_compositor_operation;
916
918}
Scene * CTX_data_scene(const bContext *C)
void BKE_image_format_free(ImageFormatData *imf)
void BKE_image_format_init(ImageFormatData *imf, const bool render)
void BKE_image_path_from_imtype(char *filepath, const char *base, const char *relbase, int frame, char imtype, bool use_ext, bool use_frames, const char *suffix)
void BKE_image_format_update_color_space_for_type(ImageFormatData *format)
void BKE_image_path_from_imformat(char *filepath, const char *base, const char *relbase, 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:837
#define NODE_CLASS_OUTPUT
Definition BKE_node.hh:405
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
void BKE_ntree_update_tag_socket_property(bNodeTree *ntree, bNodeSocket *socket)
int BKE_scene_multiview_num_views_get(const RenderData *rd)
Definition scene.cc:2928
const char * BKE_scene_multiview_view_suffix_get(const RenderData *rd, const char *viewname)
Definition scene.cc:3125
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
File and directory operations.
#define LISTBASE_FOREACH(type, var, list)
void * BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define FILE_MAX
#define BLI_path_join(...)
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
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(UniquenameCheckCallback unique_check, void *arg, const char *defname, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(1
#define ELEM(...)
#define STREQ(a, b)
#define IFACE_(msgid)
@ NODE_PREVIEW
@ SOCK_IN
@ SOCK_RGBA
@ R_MULTIVIEW
@ R_EXTENSION
@ R_IMF_VIEWS_MULTIVIEW
@ R_IMF_VIEWS_STEREO_3D
@ R_IMF_VIEWS_INDIVIDUAL
@ R_IMF_IMTYPE_OPENEXR
@ R_IMF_IMTYPE_MULTILAYER
@ R_IMF_COLOR_MANAGEMENT_FOLLOW_SCENE
static AppView * view
void GPU_memory_barrier(eGPUBarrier barrier)
Definition gpu_state.cc:374
@ GPU_BARRIER_TEXTURE_UPDATE
Definition GPU_state.hh:39
void * GPU_texture_read(GPUTexture *texture, eGPUDataFormat data_format, int mip_level)
@ GPU_DATA_FLOAT
Contains defines and structs used throughout the imbuf module.
Read Guarded memory(de)allocation.
@ PROP_NONE
Definition RNA_types.hh:136
void uiItemL(uiLayout *layout, const char *name, int icon)
void uiItemFullO(uiLayout *layout, const char *opname, const char *name, int icon, IDProperty *properties, wmOperatorCallContext context, eUI_Item_Flag flag, PointerRNA *r_opptr)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void uiTemplateImageSettings(uiLayout *layout, PointerRNA *imfptr, bool color_management)
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
void uiItemS(uiLayout *layout)
void uiItemFullO_ptr(uiLayout *layout, wmOperatorType *ot, const char *name, int icon, IDProperty *properties, wmOperatorCallContext context, eUI_Item_Flag flag, PointerRNA *r_opptr)
void uiTemplateList(uiLayout *layout, const bContext *C, const char *listtype_name, const char *list_id, PointerRNA *dataptr, const char *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)
#define UI_ITEM_NONE
void uiItemO(uiLayout *layout, const char *name, int icon, const char *opname)
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
void uiTemplateImageFormatViews(uiLayout *layout, PointerRNA *imfptr, PointerRNA *ptr)
@ UI_TEMPLATE_LIST_FLAG_NONE
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
@ UI_ITEM_R_ICON_ONLY
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:218
@ WM_OP_EXEC_DEFAULT
Definition WM_types.hh:225
constexpr const char * data() const
void execute_single_layer_multi_view_exr(const Result &result, const ImageFormatData &format, const char *base_path, const char *layer_name)
void get_single_layer_image_path(const char *base_path, const ImageFormatData &format, char *image_path)
void add_pass_for_result(FileOutput &file_output, const Result &result, const char *pass_name, const char *view_name)
void get_multi_layer_exr_image_path(const char *base_path, const char *view, char *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)
virtual StringRef get_view_name() const =0
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)
Result & get_input(StringRef identifier) const
Definition operation.cc:144
InputDescriptor & get_input_descriptor(StringRef identifier)
Definition operation.cc:164
FileOutput & get_file_output(std::string path, ImageFormatData format, int2 size, bool save_as_render)
const Domain & domain() const
Definition result.cc:712
uint col
format
descriptor
void *(* MEM_malloc_arrayN)(size_t len, size_t size, const char *str)
Definition mallocn.cc:45
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_dupallocN)(const void *vmemh)
Definition mallocn.cc:39
void node_modify_socket_type_static(bNodeTree *ntree, bNode *node, bNodeSocket *sock, int type, int subtype)
Definition node.cc:2082
bNodeSocket * node_add_static_socket(bNodeTree *ntree, bNode *node, eNodeSocketInOut in_out, int type, int subtype, const char *identifier, const char *name)
Definition node.cc:2359
void node_remove_socket(bNodeTree *ntree, bNode *node, bNodeSocket *sock)
Definition node.cc:2405
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
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)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
void ntreeCompositOutputFileUniqueLayer(ListBase *list, bNodeSocket *sock, const char defname[], char delim)
void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name)
void register_node_type_cmp_output_file()
int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node)
static bool unique_path_unique_check(void *arg, const char *name)
void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name)
static bool unique_layer_unique_check(void *arg, 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, int type, const char *name, short nclass)
void cmp_node_update_default(bNodeTree *, bNode *node)
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:40
void * data
Definition RNA_types.hh:42
struct ImageFormatData im_format
char pic[1024]
struct bNodeSocket * next
ListBase inputs
void * storage
Defines a node type.
Definition BKE_node.hh:218
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:324
void(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:240
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
void(* updatefunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:257
void(* initfunc_api)(const bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:280
PointerRNA * ptr
Definition wm_files.cc:4126
wmOperatorType * ot
Definition wm_files.cc:4125
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)