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