12#include <OpenImageIO/filesystem.h>
13#include <OpenImageIO/imageio.h>
79 if (pass_name ==
"Depth" || pass_name ==
"IndexMA" || pass_name ==
"IndexOB" ||
99 const size_t pos =
in.rfind(
".");
100 if (
pos == string::npos) {
103 suffix =
in.substr(
pos + 1);
112 string name,
string &renderlayer,
string &pass,
string &channel,
bool multiview_channels)
126 if (multiview_channels) {
127 renderlayer +=
"." +
view;
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);
146 map<string, MergeImageLayer> file_layers;
147 for (
int i = 0;
i < in_spec.nchannels;
i++) {
150 pass.
format = (!in_spec.channelformats.empty()) ? in_spec.channelformats[
i] : in_spec.format;
168 file_layers[layername].passes.push_back(pass);
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();
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);
184 file_layers[layername] = layer;
191 for (
auto &[
name, layer] : file_layers) {
196 if (layer.name.empty()) {
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;
209 if (layer.samples < 1) {
211 "No sample number specified in the file for layer %s or on the command line",
217 auto sample_pass_it = find_if(
218 layer.passes.begin(), layer.passes.end(), [](
const MergeImagePass &pass) {
219 return pass.name ==
"Debug Sample Count";
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);
226 layer.has_sample_pass =
false;
229 layers.push_back(layer);
237 for (
const string &filepath : filepaths) {
240 error =
"Couldn't open file: " + filepath;
245 image.
in = std::move(
in);
251 if (image.
layers.empty()) {
252 error =
"Could not find a render layer for merging";
256 if (image.
in->spec().deep) {
257 error =
"Merging deep images not supported.";
261 if (!images.empty()) {
262 const ImageSpec &base_spec = images[0].in->spec();
263 const ImageSpec &spec = image.
in->spec();
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)
269 error =
"Images do not have matching size and data layout.";
274 images.push_back(std::move(image));
288 const string time_str = image.in->spec().get_string_attribute(
name,
"");
293 time /= images.size();
301 const string &layer_name,
302 const string &time_name,
305 const string name =
"cycles." + layer_name +
"." + time_name;
309 const string time_str = image.in->spec().get_string_attribute(
name,
"");
314 time /= images.size();
323 out_spec = images[0].in->spec();
326 out_spec.nchannels = 0;
327 out_spec.channelformats.clear();
328 out_spec.channelnames.clear();
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; });
339 if (channel != out_spec.channelnames.end()) {
340 const int index =
distance(out_spec.channelnames.begin(), channel);
353 out_spec.channelformats.push_back(pass.
format);
354 out_spec.nchannels++;
363 map<string, int> layer_num_samples;
366 if (!layer.
name.empty()) {
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));
384 const size_t width = spec.width;
385 const size_t height = spec.height;
386 const size_t num_channels = spec.nchannels;
388 const size_t num_pixels = width * height;
389 pixels.
resize(num_pixels * num_channels);
393 const ImageSpec &out_spec,
394 const unordered_map<string, SampleCount> &layer_samples,
399 memset(out_pixels.
data(), 0, out_pixels.
size() *
sizeof(
float));
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;
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();
418 size_t offset = pass.
offset;
425 for (; offset < num_pixels; offset += stride, out_offset += out_stride) {
426 out_pixels[out_offset] = pixels[offset];
430 for (; offset < num_pixels; offset += stride, out_offset += out_stride) {
431 out_pixels[out_offset] += pixels[offset];
438 const auto &samples = layer_samples.at(layer.
name);
440 for (
size_t i = 0; offset < num_pixels;
441 offset += stride, sample_pass_offset += stride, out_offset += out_stride,
i++)
443 const float total_samples = samples.per_pixel[
i];
447 layer_samples = pixels[sample_pass_offset] * layer.
samples;
453 out_pixels[out_offset] += pixels[offset] * (1.0f * layer_samples / total_samples);
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++)
462 out_pixels[out_offset] = 1.0f * samples.per_pixel[
i] / samples.total;
475 const ImageSpec &spec,
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;
487 error =
"Failed to open temporary file " + tmp_filepath +
" for writing";
492 if (!
out->open(tmp_filepath, spec)) {
493 error =
"Failed to open file " + tmp_filepath +
" for writing: " +
out->geterror();
498 if (!
out->write_image(TypeDesc::FLOAT, pixels.
data())) {
499 error =
"Failed to write to file " + tmp_filepath +
": " +
out->geterror();
504 error =
"Failed to save to file " + tmp_filepath +
": " +
out->geterror();
512 if (ok && !OIIO::Filesystem::rename(tmp_filepath, filepath, rename_error)) {
513 error =
"Failed to move merged image to " + filepath +
": " + rename_error;
518 OIIO::Filesystem::remove(tmp_filepath);
525 unordered_map<string, SampleCount> &layer_samples)
527 for (
auto &image : images) {
528 const ImageSpec &in_spec = image.in->spec();
530 for (
auto &layer : image.layers) {
531 const bool initialize = (layer_samples.count(layer.name) == 0);
532 auto ¤t_layer_samples = layer_samples[layer.name];
535 current_layer_samples.total = 0;
536 current_layer_samples.per_pixel.resize(in_spec.width * in_spec.height);
538 current_layer_samples.per_pixel.begin(), current_layer_samples.per_pixel.end(), 0.0f);
541 if (layer.has_sample_pass) {
544 sample_count_buffer.
resize(in_spec.width * in_spec.height);
546 image.in->read_image(0,
548 layer.sample_pass_offset,
549 layer.sample_pass_offset,
551 (
void *)sample_count_buffer.
data());
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;
559 for (
size_t i = 0;
i < current_layer_samples.per_pixel.size();
i++) {
560 current_layer_samples.per_pixel[
i] += layer.samples;
564 current_layer_samples.total += layer.samples;
575 error =
"No input file paths specified.";
579 error =
"No output file path specified.";
590 unordered_map<string, SampleCount> layer_samples;
T * resize(const size_t newsize)
#define CCL_NAMESPACE_END
static const char * to_string(const Interpolation &interp)
float distance(VecOp< float, D >, VecOp< float, D >) RET
static bool parse_channels(const ImageSpec &in_spec, vector< MergeImageLayer > &layers, string &error)
static bool parse_channel_name(string name, string &renderlayer, string &pass, string &channel, bool multiview_channels)
static bool split_last_dot(string &in, string &suffix)
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)
static bool open_images(const vector< string > &filepaths, vector< MergeImage > &images, string &error)
static void merge_layer_render_time(ImageSpec &spec, const vector< MergeImage > &images, const string &layer_name, const string &time_name, const bool average)
static void alloc_pixels(const ImageSpec &spec, array< float > &pixels)
static void merge_channels_metadata(vector< MergeImage > &images, ImageSpec &out_spec)
static void merge_render_time(ImageSpec &spec, const vector< MergeImage > &images, const string &name, const bool average)
static void read_layer_samples(vector< MergeImage > &images, unordered_map< string, SampleCount > &layer_samples)
static MergeChannelOp parse_channel_operation(const string &pass_name)
static bool save_output(const string &filepath, const ImageSpec &spec, const array< float > &pixels, string &error)
static void error(const char *str)
static void unique_name(bNode *node)
bool string_startswith(const string_view s, const string_view start)
CCL_NAMESPACE_BEGIN string string_printf(const char *format,...)
bool string_endswith(const string_view s, const string_view end)
vector< MergeImagePass > passes
vector< MergeImageLayer > layers
unique_ptr< ImageInput > in
double time_human_readable_to_seconds(const string &time_string)
string time_human_readable_from_seconds(const double seconds)
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)