Blender V4.3
COM_FileOutputOperation.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <memory>
6
7#include "BLI_assert.h"
8#include "BLI_fileops.h"
9#include "BLI_index_range.hh"
10#include "BLI_path_utils.hh"
11#include "BLI_string.h"
12#include "BLI_string_utils.hh"
13#include "BLI_task.hh"
14#include "BLI_utildefines.h"
15
16#include "DNA_node_types.h"
17#include "DNA_scene_types.h"
18
19#include "BKE_image.hh"
20#include "BKE_image_format.hh"
21#include "BKE_main.hh"
22#include "BKE_scene.hh"
23
24#include "RE_pipeline.h"
25
27#include "COM_render_context.hh"
28
29namespace blender::compositor {
30
32 DataType data_type,
33 DataType original_data_type)
34 : data(data), data_type(data_type), original_data_type(original_data_type)
35{
36}
37
38static int get_channels_count(DataType datatype)
39{
40 switch (datatype) {
41 case DataType::Value:
42 return 1;
44 return 3;
45 case DataType::Color:
46 return 4;
47 default:
48 return 0;
49 }
50}
51
52static float *initialize_buffer(uint width, uint height, DataType datatype)
53{
54 const int size = get_channels_count(datatype);
55 return static_cast<float *>(
56 MEM_malloc_arrayN(size_t(width) * height, sizeof(float) * size, "File Output Buffer."));
57}
58
60 const NodeImageMultiFile *node_data,
62 : context_(context), node_data_(node_data), file_output_inputs_(inputs)
63{
64 /* Inputs for multi-layer files need to be the same size, while they can be different for
65 * individual file outputs. */
66 const ResizeMode resize_mode = this->is_multi_layer() ? ResizeMode::Center : ResizeMode::None;
67
68 for (const FileOutputInput &input : inputs) {
69 add_input_socket(input.data_type, resize_mode);
70 }
72}
73
75{
76 for (int i = 0; i < file_output_inputs_.size(); i++) {
77 FileOutputInput &input = file_output_inputs_[i];
78 input.image_input = get_input_socket_reader(i);
79 if (!input.image_input) {
80 continue;
81 }
82 input.output_buffer = initialize_buffer(
83 input.image_input->get_width(), input.image_input->get_height(), input.data_type);
84 }
85}
86
88 const rcti & /*area*/,
90{
91 for (int i = 0; i < file_output_inputs_.size(); i++) {
92 const FileOutputInput &input = file_output_inputs_[i];
93 if (!input.output_buffer) {
94 continue;
95 }
96
97 int channels_count = get_channels_count(input.data_type);
98 MemoryBuffer output_buf(
99 input.output_buffer, channels_count, inputs[i]->get_width(), inputs[i]->get_height());
100 output_buf.copy_from(inputs[i], inputs[i]->get_rect(), 0, inputs[i]->get_num_channels(), 0);
101 }
102}
103
105 const FileOutputInput &input)
106{
107 std::unique_ptr<MetaData> meta_data = input.image_input->get_meta_data();
108 if (!meta_data) {
109 return;
110 }
111
113 blender::StringRef(input.data->layer,
114 BLI_strnlen(input.data->layer, sizeof(input.data->layer))));
115 meta_data->replace_hash_neutral_cryptomatte_keys(layer_name);
116 meta_data->for_each_entry([&](const std::string &key, const std::string &value) {
117 file_output.add_meta_data(key, value);
118 });
119}
120
122{
123 /* It is possible that none of the inputs would have an image connected, which will materialize
124 * as a size of zero, so check this here and return early doing nothing. Just make sure to free
125 * the allocated buffers. */
126 const int2 size = int2(get_width(), get_height());
127 if (size == int2(0)) {
128 for (const FileOutputInput &input : file_output_inputs_) {
129 /* Ownership of outputs buffers are transferred to file outputs, so if we are not writing a
130 * file output, we need to free the output buffer here. */
131 if (input.output_buffer) {
132 MEM_freeN(input.output_buffer);
133 }
134 }
135 return;
136 }
137
138 if (is_multi_layer()) {
139 execute_multi_layer();
140 }
141 else {
142 execute_single_layer();
143 }
144}
145
146/* --------------------
147 * Single Layer Images.
148 */
149
150void FileOutputOperation::execute_single_layer()
151{
152 for (const FileOutputInput &input : file_output_inputs_) {
153 /* We only write images, not single values. */
154 if (!input.image_input || input.image_input->get_flags().is_constant_operation) {
155 /* Ownership of outputs buffers are transferred to file outputs, so if we are not writing a
156 * file output, we need to free the output buffer here. */
157 if (input.output_buffer) {
158 MEM_freeN(input.output_buffer);
159 }
160 continue;
161 }
162
163 char base_path[FILE_MAX];
164 get_single_layer_image_base_path(input.data->path, base_path);
165
166 /* The image saving code expects EXR images to have a different structure than standard
167 * images. In particular, in EXR images, the buffers need to be stored in passes that are, in
168 * turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to
169 * be stored in views. An exception to this is stereo images, which needs to have the same
170 * structure as non-EXR images. */
171 const auto &format = input.data->use_node_format ? node_data_->format : input.data->format;
172 const bool save_as_render = input.data->use_node_format ? node_data_->save_as_render :
173 input.data->save_as_render;
174 const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR;
175 const int views_count = BKE_scene_multiview_num_views_get(context_->get_render_data());
176 if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) {
177 execute_single_layer_multi_view_exr(input, format, base_path);
178 continue;
179 }
180
181 char image_path[FILE_MAX];
182 get_single_layer_image_path(base_path, format, image_path);
183
184 const int2 size = int2(input.image_input->get_width(), input.image_input->get_height());
185 realtime_compositor::FileOutput &file_output = context_->get_render_context()->get_file_output(
186 image_path, format, size, save_as_render);
187
188 add_view_for_input(file_output, input, context_->get_view_name());
189
190 add_meta_data_for_input(file_output, input);
191 }
192}
193
194/* -----------------------------------
195 * Single Layer Multi-View EXR Images.
196 */
197
198void FileOutputOperation::execute_single_layer_multi_view_exr(const FileOutputInput &input,
199 const ImageFormatData &format,
200 const char *base_path)
201{
202 const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL;
203
204 /* The EXR stores all views in the same file, so we supply an empty view to make sure the file
205 * name does not contain a view suffix. */
206 char image_path[FILE_MAX];
207 const char *path_view = has_views ? "" : context_->get_view_name();
208 get_multi_layer_exr_image_path(base_path, path_view, image_path);
209
210 const int2 size = int2(input.image_input->get_width(), input.image_input->get_height());
211 realtime_compositor::FileOutput &file_output = context_->get_render_context()->get_file_output(
212 image_path, format, size, true);
213
214 /* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we
215 * add a default unnamed view. */
216 const char *view_name = has_views ? context_->get_view_name() : "";
217 file_output.add_view(view_name);
218 add_pass_for_input(file_output, input, "", view_name);
219
220 add_meta_data_for_input(file_output, input);
221}
222
223/* -----------------------
224 * Multi-Layer EXR Images.
225 */
226
227void FileOutputOperation::execute_multi_layer()
228{
229 const bool store_views_in_single_file = is_multi_view_exr();
230 const char *view = context_->get_view_name();
231
232 /* If we are saving all views in a single multi-layer file, we supply an empty view to make
233 * sure the file name does not contain a view suffix. */
234 char image_path[FILE_MAX];
235 const char *write_view = store_views_in_single_file ? "" : view;
236 get_multi_layer_exr_image_path(get_base_path(), write_view, image_path);
237
238 const int2 size = int2(get_width(), get_height());
239 const ImageFormatData format = node_data_->format;
240 realtime_compositor::FileOutput &file_output = context_->get_render_context()->get_file_output(
241 image_path, format, size, true);
242
243 /* If we are saving views in separate files, we needn't store the view in the channel names, so
244 * we add an unnamed view. */
245 const char *pass_view = store_views_in_single_file ? view : "";
246 file_output.add_view(pass_view);
247
248 for (const FileOutputInput &input : file_output_inputs_) {
249 if (!input.image_input) {
250 /* Ownership of outputs buffers are transferred to file outputs, so if we are not writing a
251 * file output, we need to free the output buffer here. */
252 if (input.output_buffer) {
253 MEM_freeN(input.output_buffer);
254 }
255 continue;
256 }
257
258 const char *pass_name = input.data->layer;
259 add_pass_for_input(file_output, input, pass_name, pass_view);
260
261 add_meta_data_for_input(file_output, input);
262 }
263}
264
265/* Given a float4 image, return a newly allocated float3 image that ignores the last channel. The
266 * input image is freed. */
267static float *float4_to_float3_image(int2 size, float *float4_image)
268{
269 float *float3_image = static_cast<float *>(
270 MEM_malloc_arrayN(size_t(size.x) * size.y, sizeof(float[3]), "File Output Vector Buffer."));
271
272 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
273 for (const int64_t y : sub_y_range) {
274 for (const int64_t x : IndexRange(size.x)) {
275 for (int i = 0; i < 3; i++) {
276 const int pixel_index = y * size.x + x;
277 float3_image[pixel_index * 3 + i] = float4_image[pixel_index * 4 + i];
278 }
279 }
280 }
281 });
282
283 MEM_freeN(float4_image);
284 return float3_image;
285}
286
287/* Allocates and fills an image buffer of the specified size with the value of the given constant
288 * input. */
289static float *inflate_input(const FileOutputInput &input, const int2 size)
290{
291 BLI_assert(input.image_input->get_flags().is_constant_operation);
292
293 switch (input.data_type) {
294 case DataType::Value: {
295 float *buffer = static_cast<float *>(MEM_malloc_arrayN(
296 size_t(size.x) * size.y, sizeof(float), "File Output Inflated Buffer."));
297
298 const float value = input.image_input->get_constant_value_default(0.0f);
299 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
300 for (const int64_t y : sub_y_range) {
301 for (const int64_t x : IndexRange(size.x)) {
302 buffer[y * size.x + x] = value;
303 }
304 }
305 });
306 return buffer;
307 }
308 case DataType::Color: {
309 float *buffer = static_cast<float *>(MEM_malloc_arrayN(
310 size_t(size.x) * size.y, sizeof(float[4]), "File Output Inflated Buffer."));
311
312 const float *value = input.image_input->get_constant_elem_default(nullptr);
313 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
314 for (const int64_t y : sub_y_range) {
315 for (const int64_t x : IndexRange(size.x)) {
316 copy_v4_v4(buffer + ((y * size.x + x) * 4), value);
317 }
318 }
319 });
320 return buffer;
321 }
322 default:
323 /* Vector types are not possible for File output, see get_input_data_type in
324 * COM_FileOutputNode.cc for more information. Other types are internal and needn't be
325 * handled by operations. */
326 break;
327 }
328
330 return nullptr;
331}
332
333void FileOutputOperation::add_pass_for_input(realtime_compositor::FileOutput &file_output,
334 const FileOutputInput &input,
335 const char *pass_name,
336 const char *view_name)
337{
338 /* For constant operations, we fill a buffer that covers the canvas of the operation with the
339 * value of the operation. */
340 const int2 size = input.image_input->get_flags().is_constant_operation ?
341 int2(this->get_width(), this->get_height()) :
342 int2(input.image_input->get_width(), input.image_input->get_height());
343
344 /* The image buffer in the file output will take ownership of this buffer and freeing it will be
345 * its responsibility. So if we don't use the output buffer, we need to free it here. */
346 float *buffer = nullptr;
347 if (input.image_input->get_flags().is_constant_operation) {
348 buffer = inflate_input(input, size);
349 MEM_freeN(input.output_buffer);
350 }
351 else {
352 buffer = input.output_buffer;
353 }
354
355 switch (input.original_data_type) {
356 case DataType::Color:
357 /* Use lowercase rgba for Cryptomatte layers because the EXR internal compression rules
358 * specify that all uppercase RGBA channels will be compressed, and Cryptomatte should not be
359 * compressed. */
360 if (input.image_input->get_meta_data() &&
361 input.image_input->get_meta_data()->is_cryptomatte_layer())
362 {
363 file_output.add_pass(pass_name, view_name, "rgba", buffer);
364 }
365 else {
366 file_output.add_pass(pass_name, view_name, "RGBA", buffer);
367 }
368 break;
369 case DataType::Vector:
370 if (input.image_input->get_meta_data() && input.image_input->get_meta_data()->is_4d_vector) {
371 file_output.add_pass(pass_name, view_name, "XYZW", buffer);
372 }
373 else {
374 file_output.add_pass(pass_name, view_name, "XYZ", float4_to_float3_image(size, buffer));
375 }
376 break;
377 case DataType::Value:
378 file_output.add_pass(pass_name, view_name, "V", buffer);
379 break;
380 case DataType::Float2:
381 /* An internal type that needn't be handled. */
383 break;
384 }
385}
386
387void FileOutputOperation::add_view_for_input(realtime_compositor::FileOutput &file_output,
388 const FileOutputInput &input,
389 const char *view_name)
390{
391 const int2 size = int2(input.image_input->get_width(), input.image_input->get_height());
392 switch (input.original_data_type) {
393 case DataType::Color:
394 file_output.add_view(view_name, 4, input.output_buffer);
395 break;
396 case DataType::Vector:
397 file_output.add_view(view_name, 3, float4_to_float3_image(size, input.output_buffer));
398 break;
399 case DataType::Value:
400 file_output.add_view(view_name, 1, input.output_buffer);
401 break;
402 case DataType::Float2:
403 /* An internal type that needn't be handled. */
405 break;
406 }
407}
408
409void FileOutputOperation::get_single_layer_image_base_path(const char *base_name, char *base_path)
410{
411 if (base_name[0]) {
412 BLI_path_join(base_path, FILE_MAX, get_base_path(), base_name);
413 }
414 else {
415 BLI_strncpy(base_path, get_base_path(), FILE_MAX);
417 }
418}
419
420void FileOutputOperation::get_single_layer_image_path(const char *base_path,
421 const ImageFormatData &format,
422 char *image_path)
423{
425 base_path,
427 context_->get_framenumber(),
428 &format,
429 use_file_extension(),
430 true,
431 nullptr);
432}
433
434void FileOutputOperation::get_multi_layer_exr_image_path(const char *base_path,
435 const char *view,
436 char *image_path)
437{
438 const char *suffix = BKE_scene_multiview_view_suffix_get(context_->get_render_data(), view);
440 base_path,
442 context_->get_framenumber(),
444 use_file_extension(),
445 true,
446 suffix);
447}
448
449bool FileOutputOperation::is_multi_layer()
450{
451 return node_data_->format.imtype == R_IMF_IMTYPE_MULTILAYER;
452}
453
454const char *FileOutputOperation::get_base_path()
455{
456 return node_data_->base_path;
457}
458
459bool FileOutputOperation::use_file_extension()
460{
461 return context_->get_render_data()->scemode & R_EXTENSION;
462}
463
464bool FileOutputOperation::is_multi_view_exr()
465{
466 if (!is_multi_view_scene()) {
467 return false;
468 }
469
470 return node_data_->format.views_format == R_IMF_VIEWS_MULTIVIEW;
471}
472
473bool FileOutputOperation::is_multi_view_scene()
474{
475 return context_->get_render_data()->scemode & R_MULTIVIEW;
476}
477
478} // namespace blender::compositor
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_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)
const char * BKE_main_blendfile_path_from_global()
Definition main.cc:837
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 FILE_MAX
#define BLI_path_join(...)
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
int char char int int int int size_t BLI_strnlen(const char *str, size_t maxlen) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.c:909
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
unsigned int uint
@ 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
static AppView * view
Overall context of the compositor.
const RenderData * get_render_data() const
get the scene of the context
const char * get_view_name() const
get the active rendering view
realtime_compositor::RenderContext * get_render_context() const
get the render context
void update_memory_buffer(MemoryBuffer *output, const rcti &area, Span< MemoryBuffer * > inputs) override
FileOutputOperation(const CompositorContext *context, const NodeImageMultiFile *node_data, Vector< FileOutputInput > inputs)
a MemoryBuffer contains access to the data
void copy_from(const MemoryBuffer *src, const rcti &area)
SocketReader * get_input_socket_reader(unsigned int index)
void add_input_socket(DataType datatype, ResizeMode resize_mode=ResizeMode::Center)
void set_canvas_input_index(unsigned int index)
set the index of the input socket that will determine the canvas of this operation
void add_view(const char *view_name)
void add_meta_data(std::string key, std::string value)
FileOutput & get_file_output(std::string path, ImageFormatData format, int2 size, bool save_as_render)
DataType
possible data types for sockets
Definition COM_defines.h:21
ResizeMode
Resize modes of inputsockets How are the input and working resolutions matched.
@ Vector
Vector data type.
@ Center
Center the input image to the center of the working area of the node, no resizing occurs.
format
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
StringRef BKE_cryptomatte_extract_layer_name(const StringRef render_pass_name)
static constexpr unsigned int RESOLUTION_INPUT_ANY
static float * float4_to_float3_image(int2 size, float *float4_image)
static float * inflate_input(const FileOutputInput &input, const int2 size)
static float * initialize_buffer(uint width, uint height, DataType datatype)
static int get_channels_count(DataType datatype)
static void add_meta_data_for_input(realtime_compositor::FileOutput &file_output, const FileOutputInput &input)
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
VecBase< int32_t, 2 > int2
static blender::bke::bNodeSocketTemplate inputs[]
ImageFormatData format
FileOutputInput(NodeImageMultiFileSocket *data, DataType data_type, DataType original_data_type)