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