| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright 2011-2019 Blender Foundation | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "render/merge.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "util/util_array.h"
 | 
					
						
							|  |  |  | #include "util/util_map.h"
 | 
					
						
							|  |  |  | #include "util/util_system.h"
 | 
					
						
							|  |  |  | #include "util/util_time.h"
 | 
					
						
							|  |  |  | #include "util/util_unique_ptr.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <OpenImageIO/filesystem.h>
 | 
					
						
							| 
									
										
										
										
											2020-03-19 09:33:03 +01:00
										 |  |  | #include <OpenImageIO/imageio.h>
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | OIIO_NAMESPACE_USING | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CCL_NAMESPACE_BEGIN | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Merge Image Layer */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum MergeChannelOp { | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   MERGE_CHANNEL_NOP, | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   MERGE_CHANNEL_COPY, | 
					
						
							|  |  |  |   MERGE_CHANNEL_SUM, | 
					
						
							|  |  |  |   MERGE_CHANNEL_AVERAGE | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  | struct MergeImagePass { | 
					
						
							|  |  |  |   /* Full channel name. */ | 
					
						
							|  |  |  |   string channel_name; | 
					
						
							|  |  |  |   /* Channel format in the file. */ | 
					
						
							|  |  |  |   TypeDesc format; | 
					
						
							|  |  |  |   /* Type of operation to perform when merging. */ | 
					
						
							|  |  |  |   MergeChannelOp op; | 
					
						
							|  |  |  |   /* Offset of layer channels in input image. */ | 
					
						
							|  |  |  |   int offset; | 
					
						
							|  |  |  |   /* Offset of layer channels in merged image. */ | 
					
						
							|  |  |  |   int merge_offset; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  | struct MergeImageLayer { | 
					
						
							|  |  |  |   /* Layer name. */ | 
					
						
							|  |  |  |   string name; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   /* Passes. */ | 
					
						
							|  |  |  |   vector<MergeImagePass> passes; | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* Sample amount that was used for rendering this layer. */ | 
					
						
							|  |  |  |   int samples; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Merge Image */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  | struct MergeImage { | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* OIIO file handle. */ | 
					
						
							|  |  |  |   unique_ptr<ImageInput> in; | 
					
						
							|  |  |  |   /* Image file path. */ | 
					
						
							|  |  |  |   string filepath; | 
					
						
							|  |  |  |   /* Render layers. */ | 
					
						
							|  |  |  |   vector<MergeImageLayer> layers; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Channel Parsing */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static MergeChannelOp parse_channel_operation(const string &pass_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (pass_name == "Depth" || pass_name == "IndexMA" || pass_name == "IndexOB" || | 
					
						
							|  |  |  |       string_startswith(pass_name, "Crypto")) { | 
					
						
							|  |  |  |     return MERGE_CHANNEL_COPY; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (string_startswith(pass_name, "Debug BVH") || | 
					
						
							|  |  |  |            string_startswith(pass_name, "Debug Ray") || | 
					
						
							|  |  |  |            string_startswith(pass_name, "Debug Render Time")) { | 
					
						
							|  |  |  |     return MERGE_CHANNEL_SUM; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							|  |  |  |     return MERGE_CHANNEL_AVERAGE; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Splits in at its last dot, setting suffix to the part after the dot and
 | 
					
						
							|  |  |  |  * into the part before it. Returns whether a dot was found. */ | 
					
						
							|  |  |  | static bool split_last_dot(string &in, string &suffix) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   size_t pos = in.rfind("."); | 
					
						
							|  |  |  |   if (pos == string::npos) { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   suffix = in.substr(pos + 1); | 
					
						
							|  |  |  |   in = in.substr(0, pos); | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Separate channel names as generated by Blender.
 | 
					
						
							|  |  |  |  * Multiview format: RenderLayer.Pass.View.Channel | 
					
						
							|  |  |  |  * Otherwise: RenderLayer.Pass.Channel */ | 
					
						
							|  |  |  | static bool parse_channel_name( | 
					
						
							|  |  |  |     string name, string &renderlayer, string &pass, string &channel, bool multiview_channels) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (!split_last_dot(name, channel)) { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   string view; | 
					
						
							|  |  |  |   if (multiview_channels && !split_last_dot(name, view)) { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!split_last_dot(name, pass)) { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   renderlayer = name; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   if (multiview_channels) { | 
					
						
							|  |  |  |     renderlayer += "." + view; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool parse_channels(const ImageSpec &in_spec, | 
					
						
							|  |  |  |                            vector<MergeImageLayer> &layers, | 
					
						
							|  |  |  |                            string &error) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   const ParamValue *multiview = in_spec.find_attribute("multiView"); | 
					
						
							|  |  |  |   const bool multiview_channels = (multiview && multiview->type().basetype == TypeDesc::STRING && | 
					
						
							|  |  |  |                                    multiview->type().arraylen >= 2); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   layers.clear(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* Loop over all the channels in the file, parse their name and sort them
 | 
					
						
							|  |  |  |    * by RenderLayer. | 
					
						
							|  |  |  |    * Channels that can't be parsed are directly passed through to the output. */ | 
					
						
							|  |  |  |   map<string, MergeImageLayer> file_layers; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   for (int i = 0; i < in_spec.nchannels; i++) { | 
					
						
							|  |  |  |     MergeImagePass pass; | 
					
						
							|  |  |  |     pass.channel_name = in_spec.channelnames[i]; | 
					
						
							|  |  |  |     pass.format = (in_spec.channelformats.size() > 0) ? in_spec.channelformats[i] : in_spec.format; | 
					
						
							|  |  |  |     pass.offset = i; | 
					
						
							|  |  |  |     pass.merge_offset = i; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |     string layername, passname, channelname; | 
					
						
							|  |  |  |     if (parse_channel_name( | 
					
						
							|  |  |  |             pass.channel_name, layername, passname, channelname, multiview_channels)) { | 
					
						
							| 
									
										
										
										
											2021-02-05 16:23:34 +11:00
										 |  |  |       /* Channel part of a render layer. */ | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |       pass.op = parse_channel_operation(passname); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |       /* Other channels are added in unnamed layer. */ | 
					
						
							|  |  |  |       layername = ""; | 
					
						
							|  |  |  |       pass.op = parse_channel_operation(pass.channel_name); | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |     file_layers[layername].passes.push_back(pass); | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 16:23:34 +11:00
										 |  |  |   /* Loop over all detected render-layers, check whether they contain a full set of input channels.
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |    * Any channels that won't be processed internally are also passed through. */ | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   for (auto &i : file_layers) { | 
					
						
							|  |  |  |     const string &name = i.first; | 
					
						
							|  |  |  |     MergeImageLayer &layer = i.second; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     layer.name = name; | 
					
						
							|  |  |  |     layer.samples = 0; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |     /* Determine number of samples from metadata. */ | 
					
						
							|  |  |  |     if (layer.name == "") { | 
					
						
							|  |  |  |       layer.samples = 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (layer.samples < 1) { | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |       string sample_string = in_spec.get_string_attribute("cycles." + name + ".samples", ""); | 
					
						
							|  |  |  |       if (sample_string != "") { | 
					
						
							|  |  |  |         if (!sscanf(sample_string.c_str(), "%d", &layer.samples)) { | 
					
						
							|  |  |  |           error = "Failed to parse samples metadata: " + sample_string; | 
					
						
							|  |  |  |           return false; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     if (layer.samples < 1) { | 
					
						
							|  |  |  |       error = string_printf( | 
					
						
							|  |  |  |           "No sample number specified in the file for layer %s or on the command line", | 
					
						
							|  |  |  |           name.c_str()); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     layers.push_back(layer); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool open_images(const vector<string> &filepaths, vector<MergeImage> &images, string &error) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   for (const string &filepath : filepaths) { | 
					
						
							|  |  |  |     unique_ptr<ImageInput> in(ImageInput::open(filepath)); | 
					
						
							|  |  |  |     if (!in) { | 
					
						
							|  |  |  |       error = "Couldn't open file: " + filepath; | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     MergeImage image; | 
					
						
							|  |  |  |     image.in = std::move(in); | 
					
						
							|  |  |  |     image.filepath = filepath; | 
					
						
							|  |  |  |     if (!parse_channels(image.in->spec(), image.layers, error)) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     if (image.layers.size() == 0) { | 
					
						
							|  |  |  |       error = "Could not find a render layer for merging"; | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     if (image.in->spec().deep) { | 
					
						
							|  |  |  |       error = "Merging deep images not supported."; | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     if (images.size() > 0) { | 
					
						
							|  |  |  |       const ImageSpec &base_spec = images[0].in->spec(); | 
					
						
							|  |  |  |       const ImageSpec &spec = image.in->spec(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |       if (base_spec.width != spec.width || base_spec.height != spec.height || | 
					
						
							|  |  |  |           base_spec.depth != spec.depth || base_spec.format != spec.format || | 
					
						
							|  |  |  |           base_spec.deep != spec.deep) { | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |         error = "Images do not have matching size and data layout."; | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     images.push_back(std::move(image)); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void merge_render_time(ImageSpec &spec, | 
					
						
							|  |  |  |                               const vector<MergeImage> &images, | 
					
						
							|  |  |  |                               const string &name, | 
					
						
							|  |  |  |                               const bool average) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   double time = 0.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const MergeImage &image : images) { | 
					
						
							|  |  |  |     string time_str = image.in->spec().get_string_attribute(name, ""); | 
					
						
							|  |  |  |     time += time_human_readable_to_seconds(time_str); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (average) { | 
					
						
							|  |  |  |     time /= images.size(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void merge_layer_render_time(ImageSpec &spec, | 
					
						
							|  |  |  |                                     const vector<MergeImage> &images, | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |                                     const string &layer_name, | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |                                     const string &time_name, | 
					
						
							|  |  |  |                                     const bool average) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   string name = "cycles." + layer_name + "." + time_name; | 
					
						
							|  |  |  |   double time = 0.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const MergeImage &image : images) { | 
					
						
							|  |  |  |     string time_str = image.in->spec().get_string_attribute(name, ""); | 
					
						
							|  |  |  |     time += time_human_readable_to_seconds(time_str); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (average) { | 
					
						
							|  |  |  |     time /= images.size(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void merge_channels_metadata(vector<MergeImage> &images, | 
					
						
							|  |  |  |                                     ImageSpec &out_spec, | 
					
						
							|  |  |  |                                     vector<int> &channel_total_samples) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   /* Based on first image. */ | 
					
						
							|  |  |  |   out_spec = images[0].in->spec(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   /* Merge channels and compute offsets. */ | 
					
						
							|  |  |  |   out_spec.nchannels = 0; | 
					
						
							|  |  |  |   out_spec.channelformats.clear(); | 
					
						
							|  |  |  |   out_spec.channelnames.clear(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   for (MergeImage &image : images) { | 
					
						
							|  |  |  |     for (MergeImageLayer &layer : image.layers) { | 
					
						
							|  |  |  |       for (MergeImagePass &pass : layer.passes) { | 
					
						
							|  |  |  |         /* Test if matching channel already exists in merged image. */ | 
					
						
							|  |  |  |         bool found = false; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |         for (size_t i = 0; i < out_spec.nchannels; i++) { | 
					
						
							|  |  |  |           if (pass.channel_name == out_spec.channelnames[i]) { | 
					
						
							|  |  |  |             pass.merge_offset = i; | 
					
						
							|  |  |  |             channel_total_samples[i] += layer.samples; | 
					
						
							|  |  |  |             /* First image wins for channels that can't be averaged or summed. */ | 
					
						
							|  |  |  |             if (pass.op == MERGE_CHANNEL_COPY) { | 
					
						
							|  |  |  |               pass.op = MERGE_CHANNEL_NOP; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             found = true; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |         if (!found) { | 
					
						
							|  |  |  |           /* Add new channel. */ | 
					
						
							|  |  |  |           pass.merge_offset = out_spec.nchannels; | 
					
						
							|  |  |  |           channel_total_samples.push_back(layer.samples); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |           out_spec.channelnames.push_back(pass.channel_name); | 
					
						
							|  |  |  |           out_spec.channelformats.push_back(pass.format); | 
					
						
							|  |  |  |           out_spec.nchannels++; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   /* Merge metadata. */ | 
					
						
							|  |  |  |   merge_render_time(out_spec, images, "RenderTime", false); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   map<string, int> layer_num_samples; | 
					
						
							|  |  |  |   for (MergeImage &image : images) { | 
					
						
							|  |  |  |     for (MergeImageLayer &layer : image.layers) { | 
					
						
							|  |  |  |       if (layer.name != "") { | 
					
						
							|  |  |  |         layer_num_samples[layer.name] += layer.samples; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   for (const auto &i : layer_num_samples) { | 
					
						
							|  |  |  |     string name = "cycles." + i.first + ".samples"; | 
					
						
							|  |  |  |     out_spec.attribute(name, TypeDesc::STRING, string_printf("%d", i.second)); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |     merge_layer_render_time(out_spec, images, i.first, "total_time", false); | 
					
						
							|  |  |  |     merge_layer_render_time(out_spec, images, i.first, "render_time", false); | 
					
						
							|  |  |  |     merge_layer_render_time(out_spec, images, i.first, "synchronization_time", true); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void alloc_pixels(const ImageSpec &spec, array<float> &pixels) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   const size_t width = spec.width; | 
					
						
							|  |  |  |   const size_t height = spec.height; | 
					
						
							|  |  |  |   const size_t num_channels = spec.nchannels; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const size_t num_pixels = (size_t)width * (size_t)height; | 
					
						
							|  |  |  |   pixels.resize(num_pixels * num_channels); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool merge_pixels(const vector<MergeImage> &images, | 
					
						
							|  |  |  |                          const ImageSpec &out_spec, | 
					
						
							|  |  |  |                          const vector<int> &channel_total_samples, | 
					
						
							|  |  |  |                          array<float> &out_pixels, | 
					
						
							|  |  |  |                          string &error) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   alloc_pixels(out_spec, out_pixels); | 
					
						
							|  |  |  |   memset(out_pixels.data(), 0, out_pixels.size() * sizeof(float)); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   for (const MergeImage &image : images) { | 
					
						
							|  |  |  |     /* Read all channels into buffer. Reading all channels at once is
 | 
					
						
							|  |  |  |      * faster than individually due to interleaved EXR channel storage. */ | 
					
						
							|  |  |  |     array<float> pixels; | 
					
						
							|  |  |  |     alloc_pixels(image.in->spec(), pixels); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |     if (!image.in->read_image(TypeDesc::FLOAT, pixels.data())) { | 
					
						
							|  |  |  |       error = "Failed to read image: " + image.filepath; | 
					
						
							|  |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |     for (size_t li = 0; li < image.layers.size(); li++) { | 
					
						
							|  |  |  |       const MergeImageLayer &layer = image.layers[li]; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 18:38:02 +01:00
										 |  |  |       const size_t stride = image.in->spec().nchannels; | 
					
						
							|  |  |  |       const size_t out_stride = out_spec.nchannels; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |       const size_t num_pixels = pixels.size(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |       for (const MergeImagePass &pass : layer.passes) { | 
					
						
							|  |  |  |         size_t offset = pass.offset; | 
					
						
							| 
									
										
										
										
											2019-03-20 18:38:02 +01:00
										 |  |  |         size_t out_offset = pass.merge_offset; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |         switch (pass.op) { | 
					
						
							|  |  |  |           case MERGE_CHANNEL_NOP: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case MERGE_CHANNEL_COPY: | 
					
						
							| 
									
										
										
										
											2019-03-20 18:38:02 +01:00
										 |  |  |             for (; offset < num_pixels; offset += stride, out_offset += out_stride) { | 
					
						
							|  |  |  |               out_pixels[out_offset] = pixels[offset]; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case MERGE_CHANNEL_SUM: | 
					
						
							| 
									
										
										
										
											2019-03-20 18:38:02 +01:00
										 |  |  |             for (; offset < num_pixels; offset += stride, out_offset += out_stride) { | 
					
						
							|  |  |  |               out_pixels[out_offset] += pixels[offset]; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case MERGE_CHANNEL_AVERAGE: | 
					
						
							|  |  |  |             /* Weights based on sample metadata. Per channel since not
 | 
					
						
							|  |  |  |              * all files are guaranteed to have the same channels. */ | 
					
						
							| 
									
										
										
										
											2019-03-20 18:38:02 +01:00
										 |  |  |             const int total_samples = channel_total_samples[out_offset]; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |             const float t = (float)layer.samples / (float)total_samples; | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 18:38:02 +01:00
										 |  |  |             for (; offset < num_pixels; offset += stride, out_offset += out_stride) { | 
					
						
							|  |  |  |               out_pixels[out_offset] += t * pixels[offset]; | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   return true; | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool save_output(const string &filepath, | 
					
						
							|  |  |  |                         const ImageSpec &spec, | 
					
						
							|  |  |  |                         const array<float> &pixels, | 
					
						
							|  |  |  |                         string &error) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   /* Write to temporary file path, so we merge images in place and don't
 | 
					
						
							|  |  |  |    * risk destroying files when something goes wrong in file saving. */ | 
					
						
							|  |  |  |   string extension = OIIO::Filesystem::extension(filepath); | 
					
						
							|  |  |  |   string unique_name = ".merge-tmp-" + OIIO::Filesystem::unique_path(); | 
					
						
							|  |  |  |   string tmp_filepath = filepath + unique_name + extension; | 
					
						
							|  |  |  |   unique_ptr<ImageOutput> out(ImageOutput::create(tmp_filepath)); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   if (!out) { | 
					
						
							|  |  |  |     error = "Failed to open temporary file " + tmp_filepath + " for writing"; | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* Open temporary file and write image buffers. */ | 
					
						
							|  |  |  |   if (!out->open(tmp_filepath, spec)) { | 
					
						
							|  |  |  |     error = "Failed to open file " + tmp_filepath + " for writing: " + out->geterror(); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   bool ok = true; | 
					
						
							|  |  |  |   if (!out->write_image(TypeDesc::FLOAT, pixels.data())) { | 
					
						
							|  |  |  |     error = "Failed to write to file " + tmp_filepath + ": " + out->geterror(); | 
					
						
							|  |  |  |     ok = false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   if (!out->close()) { | 
					
						
							|  |  |  |     error = "Failed to save to file " + tmp_filepath + ": " + out->geterror(); | 
					
						
							|  |  |  |     ok = false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   out.reset(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 16:23:34 +11:00
										 |  |  |   /* Copy temporary file to output filepath. */ | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   string rename_error; | 
					
						
							|  |  |  |   if (ok && !OIIO::Filesystem::rename(tmp_filepath, filepath, rename_error)) { | 
					
						
							|  |  |  |     error = "Failed to move merged image to " + filepath + ": " + rename_error; | 
					
						
							|  |  |  |     ok = false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   if (!ok) { | 
					
						
							|  |  |  |     OIIO::Filesystem::remove(tmp_filepath); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   return ok; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Image Merger */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ImageMerger::ImageMerger() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool ImageMerger::run() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (input.empty()) { | 
					
						
							|  |  |  |     error = "No input file paths specified."; | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (output.empty()) { | 
					
						
							|  |  |  |     error = "No output file path specified."; | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* Open images and verify they have matching layout. */ | 
					
						
							|  |  |  |   vector<MergeImage> images; | 
					
						
							|  |  |  |   if (!open_images(input, images, error)) { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   /* Merge metadata and setup channels and offsets. */ | 
					
						
							|  |  |  |   ImageSpec out_spec; | 
					
						
							|  |  |  |   vector<int> channel_total_samples; | 
					
						
							|  |  |  |   merge_channels_metadata(images, out_spec, channel_total_samples); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   /* Merge pixels. */ | 
					
						
							|  |  |  |   array<float> out_pixels; | 
					
						
							|  |  |  |   if (!merge_pixels(images, out_spec, channel_total_samples, out_pixels, error)) { | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* We don't need input anymore at this point, and will possibly
 | 
					
						
							|  |  |  |    * overwrite the same file. */ | 
					
						
							|  |  |  |   images.clear(); | 
					
						
							| 
									
										
										
										
											2019-04-17 06:17:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  |   /* Save output file. */ | 
					
						
							| 
									
										
										
										
											2019-03-20 16:02:38 +01:00
										 |  |  |   return save_output(output, out_spec, out_pixels, error); | 
					
						
							| 
									
										
										
										
											2019-03-19 14:38:57 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CCL_NAMESPACE_END |