13#include <OpenImageIO/filesystem.h>
14#include <OpenImageIO/imageio.h>
69 unique_ptr<ImageInput>
in;
80 if (pass_name ==
"Depth" || pass_name ==
"IndexMA" || pass_name ==
"IndexOB" ||
103 size_t pos = in.rfind(
".");
104 if (
pos == string::npos) {
107 suffix = in.substr(
pos + 1);
108 in = in.substr(0,
pos);
116 string name,
string &renderlayer,
string &pass,
string &channel,
bool multiview_channels)
130 if (multiview_channels) {
131 renderlayer +=
"." +
view;
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);
150 map<string, MergeImageLayer> file_layers;
151 for (
int i = 0; i < in_spec.nchannels; i++) {
154 pass.format = (in_spec.channelformats.size() > 0) ? in_spec.channelformats[i] : in_spec.format;
156 pass.merge_offset = i;
158 string layername, channelname;
160 pass.channel_name, layername, pass.name, channelname, multiview_channels))
171 file_layers[layername].passes.push_back(pass);
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();
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);
187 file_layers[layername] = layer;
194 for (
auto &[name, layer] : file_layers) {
199 if (layer.name ==
"") {
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;
212 if (layer.samples < 1) {
214 "No sample number specified in the file for layer %s or on the command line",
220 auto sample_pass_it = find_if(
221 layer.passes.begin(), layer.passes.end(), [](
const MergeImagePass &pass) {
222 return pass.name ==
"Debug Sample Count";
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);
229 layer.has_sample_pass =
false;
232 layers.push_back(layer);
240 for (
const string &filepath : filepaths) {
241 unique_ptr<ImageInput> in(ImageInput::open(filepath));
243 error =
"Couldn't open file: " + filepath;
248 image.
in = std::move(in);
249 image.filepath = filepath;
254 if (image.layers.size() == 0) {
255 error =
"Could not find a render layer for merging";
259 if (image.in->spec().deep) {
260 error =
"Merging deep images not supported.";
264 if (images.size() > 0) {
265 const ImageSpec &base_spec = images[0].in->spec();
266 const ImageSpec &spec = image.in->spec();
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)
272 error =
"Images do not have matching size and data layout.";
277 images.push_back(std::move(image));
291 string time_str = image.in->spec().get_string_attribute(name,
"");
296 time /= images.size();
304 const string &layer_name,
305 const string &time_name,
308 string name =
"cycles." + layer_name +
"." + time_name;
312 string time_str = image.in->spec().get_string_attribute(name,
"");
317 time /= images.size();
326 out_spec = images[0].in->spec();
329 out_spec.nchannels = 0;
330 out_spec.channelformats.clear();
331 out_spec.channelnames.clear();
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; });
342 if (channel != out_spec.channelnames.end()) {
343 int index =
distance(out_spec.channelnames.begin(), channel);
344 pass.merge_offset = index;
353 pass.merge_offset = out_spec.nchannels;
355 out_spec.channelnames.push_back(pass.channel_name);
356 out_spec.channelformats.push_back(pass.format);
357 out_spec.nchannels++;
366 map<string, int> layer_num_samples;
369 if (layer.name !=
"") {
370 layer_num_samples[layer.name] += layer.samples;
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));
387 const size_t width = spec.width;
388 const size_t height = spec.height;
389 const size_t num_channels = spec.nchannels;
391 const size_t num_pixels = width * height;
392 pixels.resize(num_pixels * num_channels);
396 const ImageSpec &out_spec,
397 const unordered_map<string, SampleCount> &layer_samples,
402 memset(out_pixels.
data(), 0, out_pixels.
size() *
sizeof(
float));
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;
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();
421 size_t offset = pass.offset;
422 size_t out_offset = pass.merge_offset;
428 for (; offset < num_pixels; offset += stride, out_offset += out_stride) {
429 out_pixels[out_offset] = pixels[offset];
433 for (; offset < num_pixels; offset += stride, out_offset += out_stride) {
434 out_pixels[out_offset] += pixels[offset];
440 size_t sample_pass_offset = layer.sample_pass_offset;
441 const auto &samples = layer_samples.at(layer.name);
443 for (
size_t i = 0; offset < num_pixels;
444 offset += stride, sample_pass_offset += stride, out_offset += out_stride, i++)
446 const float total_samples = samples.per_pixel[i];
449 if (layer.has_sample_pass) {
450 layer_samples = pixels[sample_pass_offset] * layer.samples;
453 layer_samples = layer.samples;
456 out_pixels[out_offset] += pixels[offset] * (1.0f * layer_samples / total_samples);
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++)
465 out_pixels[out_offset] = 1.0f * samples.per_pixel[i] / samples.total;
478 const ImageSpec &spec,
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));
490 error =
"Failed to open temporary file " + tmp_filepath +
" for writing";
495 if (!out->open(tmp_filepath, spec)) {
496 error =
"Failed to open file " + tmp_filepath +
" for writing: " + out->geterror();
501 if (!out->write_image(TypeDesc::FLOAT, pixels.data())) {
502 error =
"Failed to write to file " + tmp_filepath +
": " + out->geterror();
507 error =
"Failed to save to file " + tmp_filepath +
": " + out->geterror();
515 if (ok && !OIIO::Filesystem::rename(tmp_filepath, filepath, rename_error)) {
516 error =
"Failed to move merged image to " + filepath +
": " + rename_error;
521 OIIO::Filesystem::remove(tmp_filepath);
528 unordered_map<string, SampleCount> &layer_samples)
530 for (
auto &image : images) {
531 const ImageSpec &in_spec = image.in->spec();
533 for (
auto &layer : image.layers) {
534 bool initialize = (layer_samples.count(layer.name) == 0);
535 auto ¤t_layer_samples = layer_samples[layer.name];
538 current_layer_samples.total = 0;
539 current_layer_samples.per_pixel.resize(in_spec.width * in_spec.height);
541 current_layer_samples.per_pixel.begin(), current_layer_samples.per_pixel.end(), 0.0f);
544 if (layer.has_sample_pass) {
547 sample_count_buffer.
resize(in_spec.width * in_spec.height);
549 image.in->read_image(0,
551 layer.sample_pass_offset,
552 layer.sample_pass_offset,
554 (
void *)sample_count_buffer.
data());
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;
562 for (
size_t i = 0; i < current_layer_samples.per_pixel.size(); i++) {
563 current_layer_samples.per_pixel[i] += layer.samples;
567 current_layer_samples.total += layer.samples;
578 error =
"No input file paths specified.";
581 if (output.empty()) {
582 error =
"No output file path specified.";
593 unordered_map<string, SampleCount> layer_samples;
T * resize(size_t newsize)
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)
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)
float distance(float a, float b)
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)