Blender V4.3
merge.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "session/merge.h"
6
7#include "util/array.h"
8#include "util/map.h"
9#include "util/system.h"
10#include "util/time.h"
11#include "util/unique_ptr.h"
12
13#include <OpenImageIO/filesystem.h>
14#include <OpenImageIO/imageio.h>
15
16OIIO_NAMESPACE_USING
17
19
20/* Merge Image Layer */
21
29
31 /* Full channel name. */
33 /* Pass name. */
34 string name;
35 /* Channel format in the file. */
37 /* Type of operation to perform when merging. */
39 /* Offset of layer channels in input image. */
40 int offset;
41 /* Offset of layer channels in merged image. */
43};
44
46 /* Total number of samples. */
47 int total;
48 /* Buffer for actual number of samples rendered per pixel. */
50};
51
53 /* Layer name. */
54 string name;
55 /* Passes. */
57 /* Sample amount that was used for rendering this layer. */
59 /* Indicates if this layer has "Debug Sample Count" pass. */
61 /* Offset of the "Debug Sample Count" pass if it exists. */
63};
64
65/* Merge Image */
66
67struct MergeImage {
68 /* OIIO file handle. */
69 unique_ptr<ImageInput> in;
70 /* Image file path. */
71 string filepath;
72 /* Render layers. */
74};
75
76/* Channel Parsing */
77
78static MergeChannelOp parse_channel_operation(const string &pass_name)
79{
80 if (pass_name == "Depth" || pass_name == "IndexMA" || pass_name == "IndexOB" ||
81 string_startswith(pass_name, "Crypto"))
82 {
83 return MERGE_CHANNEL_COPY;
84 }
85 else if (string_startswith(pass_name, "Debug BVH") ||
86 string_startswith(pass_name, "Debug Ray") ||
87 string_startswith(pass_name, "Debug Render Time"))
88 {
89 return MERGE_CHANNEL_SUM;
90 }
91 else if (string_startswith(pass_name, "Debug Sample Count")) {
93 }
94 else {
96 }
97}
98
99/* Splits in at its last dot, setting suffix to the part after the dot and
100 * into the part before it. Returns whether a dot was found. */
101static bool split_last_dot(string &in, string &suffix)
102{
103 size_t pos = in.rfind(".");
104 if (pos == string::npos) {
105 return false;
106 }
107 suffix = in.substr(pos + 1);
108 in = in.substr(0, pos);
109 return true;
110}
111
112/* Separate channel names as generated by Blender.
113 * Multiview format: RenderLayer.Pass.View.Channel
114 * Otherwise: RenderLayer.Pass.Channel */
116 string name, string &renderlayer, string &pass, string &channel, bool multiview_channels)
117{
118 if (!split_last_dot(name, channel)) {
119 return false;
120 }
121 string view;
122 if (multiview_channels && !split_last_dot(name, view)) {
123 return false;
124 }
125 if (!split_last_dot(name, pass)) {
126 return false;
127 }
128 renderlayer = name;
129
130 if (multiview_channels) {
131 renderlayer += "." + view;
132 }
133
134 return true;
135}
136
137static bool parse_channels(const ImageSpec &in_spec,
139 string &error)
140{
141 const ParamValue *multiview = in_spec.find_attribute("multiView");
142 const bool multiview_channels = (multiview && multiview->type().basetype == TypeDesc::STRING &&
143 multiview->type().arraylen >= 2);
144
145 layers.clear();
146
147 /* Loop over all the channels in the file, parse their name and sort them
148 * by RenderLayer.
149 * Channels that can't be parsed are directly passed through to the output. */
150 map<string, MergeImageLayer> file_layers;
151 for (int i = 0; i < in_spec.nchannels; i++) {
152 MergeImagePass pass;
153 pass.channel_name = in_spec.channelnames[i];
154 pass.format = (in_spec.channelformats.size() > 0) ? in_spec.channelformats[i] : in_spec.format;
155 pass.offset = i;
156 pass.merge_offset = i;
157
158 string layername, channelname;
160 pass.channel_name, layername, pass.name, channelname, multiview_channels))
161 {
162 /* Channel part of a render layer. */
163 pass.op = parse_channel_operation(pass.name);
164 }
165 else {
166 /* Other channels are added in unnamed layer. */
167 layername = "";
168 pass.op = parse_channel_operation(pass.channel_name);
169 }
170
171 file_layers[layername].passes.push_back(pass);
172 }
173
174 /* If file contains a single unnamed layer, name it after the first layer metadata we find. */
175 if (file_layers.size() == 1 && file_layers.find("") != file_layers.end()) {
176 for (const ParamValue &attrib : in_spec.extra_attribs) {
177 const string attrib_name = attrib.name().string();
178 if (string_startswith(attrib_name, "cycles.") && string_endswith(attrib_name, ".samples")) {
179 /* Extract layer name. */
180 const size_t start = strlen("cycles.");
181 const size_t end = attrib_name.size() - strlen(".samples");
182 const string layername = attrib_name.substr(start, end - start);
183
184 /* Reinsert as named instead of unnamed layer. */
185 const MergeImageLayer layer = file_layers[""];
186 file_layers.clear();
187 file_layers[layername] = layer;
188 }
189 }
190 }
191
192 /* Loop over all detected render-layers, check whether they contain a full set of input
193 * channels. Any channels that won't be processed internally are also passed through. */
194 for (auto &[name, layer] : file_layers) {
195 layer.name = name;
196 layer.samples = 0;
197
198 /* Determine number of samples from metadata. */
199 if (layer.name == "") {
200 layer.samples = 1;
201 }
202 else if (layer.samples < 1) {
203 string sample_string = in_spec.get_string_attribute("cycles." + name + ".samples", "");
204 if (sample_string != "") {
205 if (!sscanf(sample_string.c_str(), "%d", &layer.samples)) {
206 error = "Failed to parse samples metadata: " + sample_string;
207 return false;
208 }
209 }
210 }
211
212 if (layer.samples < 1) {
214 "No sample number specified in the file for layer %s or on the command line",
215 name.c_str());
216 return false;
217 }
218
219 /* Check if the layer has "Debug Sample Count" pass. */
220 auto sample_pass_it = find_if(
221 layer.passes.begin(), layer.passes.end(), [](const MergeImagePass &pass) {
222 return pass.name == "Debug Sample Count";
223 });
224 if (sample_pass_it != layer.passes.end()) {
225 layer.has_sample_pass = true;
226 layer.sample_pass_offset = distance(layer.passes.begin(), sample_pass_it);
227 }
228 else {
229 layer.has_sample_pass = false;
230 }
231
232 layers.push_back(layer);
233 }
234
235 return true;
236}
237
238static bool open_images(const vector<string> &filepaths, vector<MergeImage> &images, string &error)
239{
240 for (const string &filepath : filepaths) {
241 unique_ptr<ImageInput> in(ImageInput::open(filepath));
242 if (!in) {
243 error = "Couldn't open file: " + filepath;
244 return false;
245 }
246
248 image.in = std::move(in);
249 image.filepath = filepath;
250 if (!parse_channels(image.in->spec(), image.layers, error)) {
251 return false;
252 }
253
254 if (image.layers.size() == 0) {
255 error = "Could not find a render layer for merging";
256 return false;
257 }
258
259 if (image.in->spec().deep) {
260 error = "Merging deep images not supported.";
261 return false;
262 }
263
264 if (images.size() > 0) {
265 const ImageSpec &base_spec = images[0].in->spec();
266 const ImageSpec &spec = image.in->spec();
267
268 if (base_spec.width != spec.width || base_spec.height != spec.height ||
269 base_spec.depth != spec.depth || base_spec.format != spec.format ||
270 base_spec.deep != spec.deep)
271 {
272 error = "Images do not have matching size and data layout.";
273 return false;
274 }
275 }
276
277 images.push_back(std::move(image));
278 }
279
280 return true;
281}
282
283static void merge_render_time(ImageSpec &spec,
284 const vector<MergeImage> &images,
285 const string &name,
286 const bool average)
287{
288 double time = 0.0;
289
290 for (const MergeImage &image : images) {
291 string time_str = image.in->spec().get_string_attribute(name, "");
292 time += time_human_readable_to_seconds(time_str);
293 }
294
295 if (average) {
296 time /= images.size();
297 }
298
299 spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
300}
301
302static void merge_layer_render_time(ImageSpec &spec,
303 const vector<MergeImage> &images,
304 const string &layer_name,
305 const string &time_name,
306 const bool average)
307{
308 string name = "cycles." + layer_name + "." + time_name;
309 double time = 0.0;
310
311 for (const MergeImage &image : images) {
312 string time_str = image.in->spec().get_string_attribute(name, "");
313 time += time_human_readable_to_seconds(time_str);
314 }
315
316 if (average) {
317 time /= images.size();
318 }
319
320 spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
321}
322
323static void merge_channels_metadata(vector<MergeImage> &images, ImageSpec &out_spec)
324{
325 /* Based on first image. */
326 out_spec = images[0].in->spec();
327
328 /* Merge channels and compute offsets. */
329 out_spec.nchannels = 0;
330 out_spec.channelformats.clear();
331 out_spec.channelnames.clear();
332
333 for (MergeImage &image : images) {
334 for (MergeImageLayer &layer : image.layers) {
335 for (MergeImagePass &pass : layer.passes) {
336 /* Test if matching channel already exists in merged image. */
337 auto channel = find_if(
338 out_spec.channelnames.begin(),
339 out_spec.channelnames.end(),
340 [&pass](const auto &channel_name) { return pass.channel_name == channel_name; });
341
342 if (channel != out_spec.channelnames.end()) {
343 int index = distance(out_spec.channelnames.begin(), channel);
344 pass.merge_offset = index;
345
346 /* First image wins for channels that can't be averaged or summed. */
347 if (pass.op == MERGE_CHANNEL_COPY) {
348 pass.op = MERGE_CHANNEL_NOP;
349 }
350 }
351 else {
352 /* Add new channel. */
353 pass.merge_offset = out_spec.nchannels;
354
355 out_spec.channelnames.push_back(pass.channel_name);
356 out_spec.channelformats.push_back(pass.format);
357 out_spec.nchannels++;
358 }
359 }
360 }
361 }
362
363 /* Merge metadata. */
364 merge_render_time(out_spec, images, "RenderTime", false);
365
366 map<string, int> layer_num_samples;
367 for (MergeImage &image : images) {
368 for (MergeImageLayer &layer : image.layers) {
369 if (layer.name != "") {
370 layer_num_samples[layer.name] += layer.samples;
371 }
372 }
373 }
374
375 for (const auto &[layer_name, layer_samples] : layer_num_samples) {
376 string name = "cycles." + layer_name + ".samples";
377 out_spec.attribute(name, TypeDesc::STRING, to_string(layer_samples));
378
379 merge_layer_render_time(out_spec, images, layer_name, "total_time", false);
380 merge_layer_render_time(out_spec, images, layer_name, "render_time", false);
381 merge_layer_render_time(out_spec, images, layer_name, "synchronization_time", true);
382 }
383}
384
385static void alloc_pixels(const ImageSpec &spec, array<float> &pixels)
386{
387 const size_t width = spec.width;
388 const size_t height = spec.height;
389 const size_t num_channels = spec.nchannels;
390
391 const size_t num_pixels = width * height;
392 pixels.resize(num_pixels * num_channels);
393}
394
395static bool merge_pixels(const vector<MergeImage> &images,
396 const ImageSpec &out_spec,
397 const unordered_map<string, SampleCount> &layer_samples,
398 array<float> &out_pixels,
399 string &error)
400{
401 alloc_pixels(out_spec, out_pixels);
402 memset(out_pixels.data(), 0, out_pixels.size() * sizeof(float));
403
404 for (const MergeImage &image : images) {
405 /* Read all channels into buffer. Reading all channels at once is
406 * faster than individually due to interleaved EXR channel storage. */
407 array<float> pixels;
408 alloc_pixels(image.in->spec(), pixels);
409 const int num_channels = image.in->spec().nchannels;
410 if (!image.in->read_image(0, 0, 0, num_channels, TypeDesc::FLOAT, pixels.data())) {
411 error = "Failed to read image: " + image.filepath;
412 return false;
413 }
414
415 for (const MergeImageLayer &layer : image.layers) {
416 const size_t stride = image.in->spec().nchannels;
417 const size_t out_stride = out_spec.nchannels;
418 const size_t num_pixels = pixels.size();
419
420 for (const MergeImagePass &pass : layer.passes) {
421 size_t offset = pass.offset;
422 size_t out_offset = pass.merge_offset;
423
424 switch (pass.op) {
426 break;
428 for (; offset < num_pixels; offset += stride, out_offset += out_stride) {
429 out_pixels[out_offset] = pixels[offset];
430 }
431 break;
433 for (; offset < num_pixels; offset += stride, out_offset += out_stride) {
434 out_pixels[out_offset] += pixels[offset];
435 }
436 break;
438 /* Weights based on sample count passes and sample metadata. Per channel since not
439 * all files are guaranteed to have the same channels. */
440 size_t sample_pass_offset = layer.sample_pass_offset;
441 const auto &samples = layer_samples.at(layer.name);
442
443 for (size_t i = 0; offset < num_pixels;
444 offset += stride, sample_pass_offset += stride, out_offset += out_stride, i++)
445 {
446 const float total_samples = samples.per_pixel[i];
447
448 float layer_samples;
449 if (layer.has_sample_pass) {
450 layer_samples = pixels[sample_pass_offset] * layer.samples;
451 }
452 else {
453 layer_samples = layer.samples;
454 }
455
456 out_pixels[out_offset] += pixels[offset] * (1.0f * layer_samples / total_samples);
457 }
458 break;
459 }
461 const auto &samples = layer_samples.at(layer.name);
462 for (size_t i = 0; offset < num_pixels;
463 offset += stride, out_offset += out_stride, i++)
464 {
465 out_pixels[out_offset] = 1.0f * samples.per_pixel[i] / samples.total;
466 }
467 break;
468 }
469 }
470 }
471 }
472 }
473
474 return true;
475}
476
477static bool save_output(const string &filepath,
478 const ImageSpec &spec,
479 const array<float> &pixels,
480 string &error)
481{
482 /* Write to temporary file path, so we merge images in place and don't
483 * risk destroying files when something goes wrong in file saving. */
484 string extension = OIIO::Filesystem::extension(filepath);
485 string unique_name = ".merge-tmp-" + OIIO::Filesystem::unique_path();
486 string tmp_filepath = filepath + unique_name + extension;
487 unique_ptr<ImageOutput> out(ImageOutput::create(tmp_filepath));
488
489 if (!out) {
490 error = "Failed to open temporary file " + tmp_filepath + " for writing";
491 return false;
492 }
493
494 /* Open temporary file and write image buffers. */
495 if (!out->open(tmp_filepath, spec)) {
496 error = "Failed to open file " + tmp_filepath + " for writing: " + out->geterror();
497 return false;
498 }
499
500 bool ok = true;
501 if (!out->write_image(TypeDesc::FLOAT, pixels.data())) {
502 error = "Failed to write to file " + tmp_filepath + ": " + out->geterror();
503 ok = false;
504 }
505
506 if (!out->close()) {
507 error = "Failed to save to file " + tmp_filepath + ": " + out->geterror();
508 ok = false;
509 }
510
511 out.reset();
512
513 /* Copy temporary file to output filepath. */
514 string rename_error;
515 if (ok && !OIIO::Filesystem::rename(tmp_filepath, filepath, rename_error)) {
516 error = "Failed to move merged image to " + filepath + ": " + rename_error;
517 ok = false;
518 }
519
520 if (!ok) {
521 OIIO::Filesystem::remove(tmp_filepath);
522 }
523
524 return ok;
525}
526
528 unordered_map<string, SampleCount> &layer_samples)
529{
530 for (auto &image : images) {
531 const ImageSpec &in_spec = image.in->spec();
532
533 for (auto &layer : image.layers) {
534 bool initialize = (layer_samples.count(layer.name) == 0);
535 auto &current_layer_samples = layer_samples[layer.name];
536
537 if (initialize) {
538 current_layer_samples.total = 0;
539 current_layer_samples.per_pixel.resize(in_spec.width * in_spec.height);
540 std::fill(
541 current_layer_samples.per_pixel.begin(), current_layer_samples.per_pixel.end(), 0.0f);
542 }
543
544 if (layer.has_sample_pass) {
545 /* Load the "Debug Sample Count" pass and add the samples to the layer's sample count. */
546 array<float> sample_count_buffer;
547 sample_count_buffer.resize(in_spec.width * in_spec.height);
548
549 image.in->read_image(0,
550 0,
551 layer.sample_pass_offset,
552 layer.sample_pass_offset,
553 TypeDesc::FLOAT,
554 (void *)sample_count_buffer.data());
555
556 for (size_t i = 0; i < current_layer_samples.per_pixel.size(); i++) {
557 current_layer_samples.per_pixel[i] += sample_count_buffer[i] * layer.samples;
558 }
559 }
560 else {
561 /* Use sample count from metadata if there's no "Debug Sample Count" pass. */
562 for (size_t i = 0; i < current_layer_samples.per_pixel.size(); i++) {
563 current_layer_samples.per_pixel[i] += layer.samples;
564 }
565 }
566
567 current_layer_samples.total += layer.samples;
568 }
569 }
570}
571/* Image Merger */
572
574
576{
577 if (input.empty()) {
578 error = "No input file paths specified.";
579 return false;
580 }
581 if (output.empty()) {
582 error = "No output file path specified.";
583 return false;
584 }
585
586 /* Open images and verify they have matching layout. */
587 vector<MergeImage> images;
588 if (!open_images(input, images, error)) {
589 return false;
590 }
591
592 /* Load and sum sample count for each render layer. */
593 unordered_map<string, SampleCount> layer_samples;
594 read_layer_samples(images, layer_samples);
595
596 /* Merge metadata and setup channels and offsets. */
597 ImageSpec out_spec;
598 merge_channels_metadata(images, out_spec);
599
600 /* Merge pixels. */
601 array<float> out_pixels;
602 if (!merge_pixels(images, out_spec, layer_samples, out_pixels, error)) {
603 return false;
604 }
605
606 /* We don't need input anymore at this point, and will possibly
607 * overwrite the same file. */
608 images.clear();
609
610 /* Save output file. */
611 return save_output(output, out_spec, out_pixels, error);
612}
613
static AppView * view
void initialize()
bool run()
Definition merge.cpp:575
string error
Definition merge.h:21
T * resize(size_t newsize)
size_t size() const
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
#define CCL_NAMESPACE_END
static const char * to_string(const Interpolation &interp)
Definition gl_shader.cc:82
static bool parse_channels(const ImageSpec &in_spec, vector< MergeImageLayer > &layers, string &error)
Definition merge.cpp:137
static bool parse_channel_name(string name, string &renderlayer, string &pass, string &channel, bool multiview_channels)
Definition merge.cpp:115
static bool split_last_dot(string &in, string &suffix)
Definition merge.cpp:101
MergeChannelOp
Definition merge.cpp:22
@ MERGE_CHANNEL_SAMPLES
Definition merge.cpp:27
@ MERGE_CHANNEL_AVERAGE
Definition merge.cpp:26
@ MERGE_CHANNEL_SUM
Definition merge.cpp:25
@ MERGE_CHANNEL_NOP
Definition merge.cpp:23
@ MERGE_CHANNEL_COPY
Definition merge.cpp:24
static bool merge_pixels(const vector< MergeImage > &images, const ImageSpec &out_spec, const unordered_map< string, SampleCount > &layer_samples, array< float > &out_pixels, string &error)
Definition merge.cpp:395
static bool open_images(const vector< string > &filepaths, vector< MergeImage > &images, string &error)
Definition merge.cpp:238
static void merge_layer_render_time(ImageSpec &spec, const vector< MergeImage > &images, const string &layer_name, const string &time_name, const bool average)
Definition merge.cpp:302
static void alloc_pixels(const ImageSpec &spec, array< float > &pixels)
Definition merge.cpp:385
static void merge_channels_metadata(vector< MergeImage > &images, ImageSpec &out_spec)
Definition merge.cpp:323
static void merge_render_time(ImageSpec &spec, const vector< MergeImage > &images, const string &name, const bool average)
Definition merge.cpp:283
static void read_layer_samples(vector< MergeImage > &images, unordered_map< string, SampleCount > &layer_samples)
Definition merge.cpp:527
static MergeChannelOp parse_channel_operation(const string &pass_name)
Definition merge.cpp:78
static bool save_output(const string &filepath, const ImageSpec &spec, const array< float > &pixels, string &error)
Definition merge.cpp:477
static void error(const char *str)
static void unique_name(bNode *node)
float distance(float a, float b)
long long TypeDesc
bool string_startswith(const string_view s, const string_view start)
Definition string.cpp:103
CCL_NAMESPACE_BEGIN string string_printf(const char *format,...)
Definition string.cpp:23
bool string_endswith(const string_view s, const string_view end)
Definition string.cpp:114
bool has_sample_pass
Definition merge.cpp:60
int sample_pass_offset
Definition merge.cpp:62
vector< MergeImagePass > passes
Definition merge.cpp:56
string name
Definition merge.cpp:54
string name
Definition merge.cpp:34
string channel_name
Definition merge.cpp:32
int merge_offset
Definition merge.cpp:42
TypeDesc format
Definition merge.cpp:36
MergeChannelOp op
Definition merge.cpp:38
vector< MergeImageLayer > layers
Definition merge.cpp:73
string filepath
Definition merge.cpp:71
unique_ptr< ImageInput > in
Definition merge.cpp:69
int total
Definition merge.cpp:47
array< float > per_pixel
Definition merge.cpp:49
double time_human_readable_to_seconds(const string &time_string)
Definition time.cpp:82
string time_human_readable_from_seconds(const double seconds)
Definition time.cpp:67