-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathEXROperation_WriteLayers.cpp
371 lines (318 loc) · 13.4 KB
/
EXROperation_WriteLayers.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
#include "EXROperation_WriteLayers.h"
#include "helpers.h"
#include "DeepImageUtil.h"
using namespace Imf;
using namespace Imath;
EXROperation_WriteLayers::EXROperation_WriteLayers(const SharedConfig &sharedConfig_, string opt, vector<pair<string,string>> arguments):
sharedConfig(sharedConfig_)
{
// --layer-masks specified before any other layer will be applied to all layers.
// Collect them here, and we'll apply them at the end.
vector<MaskDesc> globalMasks;
for(auto it: arguments)
{
string arg = it.first;
string value = it.second;
if(arg == "filename-pattern")
{
outputPattern = value;
}
else if(arg == "layer")
{
// id=name
vector<string> descParts;
split(value, "=", descParts);
if(descParts.size() != 2)
{
printf("Warning: ignored part of layer desc \"%s\"\n", value.c_str());
continue;
}
LayerDesc layer;
layer.objectId = atoi(descParts[0].c_str());
layer.layerName = descParts[1];
layerDescs.push_back(layer);
}
else if(arg == "layer-mask")
{
MaskDesc mask;
mask.ParseOptionsString(value);
if(layerDescs.size())
layerDescs.back().masks.push_back(mask);
else
globalMasks.push_back(mask);
}
else if(arg == "combine")
{
const char *split = strchr(value.c_str(), ',');
if(split == NULL)
{
printf("Invalid --combine (ignored)\n");
continue;
}
int dst = atoi(value.c_str());
int src = atoi(split+1);
combines.push_back(make_pair(dst, src));
}
else
throw StringException("Unknown save-layers option: " + arg);
}
// Apply global masks to all layers.
for(auto &maskDesc: globalMasks)
{
for(auto &layerDesc: layerDescs)
layerDesc.masks.push_back(maskDesc);
}
}
void EXROperation_WriteLayers::AddChannels(shared_ptr<DeepImage> image, DeepFrameBuffer &frameBuffer) const
{
// Add channels used by masks.
for(auto layerDesc: layerDescs)
{
for(auto maskDesc: layerDesc.masks)
{
auto channel = image->AddChannelToFramebuffer<float>(maskDesc.maskChannel, frameBuffer);
// Unpremultiply user masks.
//
// Note that we don't want to unpremultiply if this is a mask we created with
// --create-mask, but that won't happen since the unpremultiplications happen
// before the mask gets created.
channel->needsUnpremultiply = true;
}
}
image->AddChannelToFramebuffer<uint32_t>(sharedConfig.GetIdChannel(image->header), frameBuffer);
}
void EXROperation_WriteLayers::Run(shared_ptr<EXROperationState> state) const
{
shared_ptr<DeepImage> image = state->image;
vector<LayerDesc> layerDescsCopy = layerDescs;
// If no layer was specified for the default object ID, add one at the beginning.
{
bool hasDefaultObjectId = false;
for(auto layer: layerDescsCopy)
if(layer.objectId == DeepImageUtil::NO_OBJECT_ID)
hasDefaultObjectId = true;
if(!hasDefaultObjectId)
{
LayerDesc layerDesc;
layerDesc.objectId = 0;
layerDesc.layerName = "default";
layerDescsCopy.insert(layerDescsCopy.begin(), layerDesc);
}
}
// Create the layer ordering. This just maps each layer's object ID to its position in
// the layer list.
map<int,int> layerOrder;
{
int next = 0;
for(auto layerDesc: layerDescsCopy)
layerOrder[layerDesc.objectId] = next++;
}
// Combine layers. This just changes the object IDs of samples, so we don't need to re-sort.
shared_ptr<TypedDeepImageChannel<uint32_t>> collapsedId(image->GetChannel<uint32_t>(sharedConfig.GetIdChannel(image->header))->Clone());
for(auto combine: combines)
DeepImageUtil::CombineObjectId(collapsedId, combine.second, combine.first);
// Collapse any object IDs that aren't associated with layers into the default layer
// to use with layer separation. Do this after combines, so if we collapsed an object
// ID into one that isn't being output, we also collapse those into NO_OBJECT_ID.
for(int y = 0; y < image->height; y++)
{
for(int x = 0; x < image->width; x++)
{
for(int s = 0; s < image->NumSamples(x, y); ++s)
{
uint32_t value = collapsedId->Get(x,y,s);
if(layerOrder.find(value) == layerOrder.end())
collapsedId->Get(x,y,s) = DeepImageUtil::NO_OBJECT_ID;
}
}
}
int nextOrder = 1;
vector<shared_ptr<OutputImage>> outputImages;
auto createOutputImage = [&](string layerName, string layerType, bool ordered)
{
outputImages.push_back(make_shared<OutputImage>());
shared_ptr<OutputImage> newImage = outputImages.back();
newImage->layerName = layerName;
newImage->layerType = layerType;
if(ordered)
newImage->order = nextOrder++;
newImage->filename = MakeOutputFilename(*newImage.get());
return newImage;
};
auto addLayer = [&](shared_ptr<OutputImage> outputImage, shared_ptr<SimpleImage> imageToAdd)
{
// Copy all image attributes, except for built-in EXR headers that we shouldn't set.
DeepImageUtil::CopyLayerAttributes(image->header, imageToAdd->header);
// Add it to the layer list to be written.
outputImage->layers.push_back(SimpleImage::EXRLayersToWrite(imageToAdd));
return &outputImage->layers.back();
};
// Reorder the samples so we can separate it into layers.
set<string> maskNames;
for(auto layerDesc: layerDescs)
for(auto maskDesc: layerDesc.masks)
maskNames.insert(maskDesc.maskName);
shared_ptr<DeepImage> newImage = DeepImageUtil::OrderSamplesByLayer(image, collapsedId, layerOrder, maskNames);
// Separate the image into its layers.
map<int,shared_ptr<SimpleImage>> separatedLayers;
shared_ptr<const TypedDeepImageChannel<V4f>> rgba = newImage->GetChannel<V4f>("rgba");
shared_ptr<const TypedDeepImageChannel<uint32_t>> id = newImage->GetChannel<uint32_t>("id");
for(auto it: layerOrder)
{
// All we need to do now is blend samples with each object ID, ignoring
// the others.
int objectId = it.first;
shared_ptr<SimpleImage> layerImage = make_shared<SimpleImage>(image->width, image->height);
separatedLayers[objectId] = DeepImageUtil::CollapseEXR(newImage, id, rgba, nullptr, { objectId });
}
for(auto layerDesc: layerDescsCopy)
{
// Skip this layer if we've removed it from layerOrder.
if(layerOrder.find(layerDesc.objectId) == layerOrder.end())
{
// Skip this order number, so filenames stay consistent.
nextOrder++;
continue;
}
string layerName = layerDesc.layerName;
// Create an output image named "color", and extract the layer into it.
auto colorImageOutput = separatedLayers.at(layerDesc.objectId);
// If the color layer is completely empty, don't create it.
if(colorImageOutput->IsEmpty())
{
// Skip this order number, so filenames stay consistent.
nextOrder++;
continue;
}
const auto colorImageOut = createOutputImage(layerName, "color", true);
addLayer(colorImageOut, colorImageOutput);
// Create output layers for each of this color layer's masks.
for(auto maskDesc: layerDesc.masks)
{
auto mask = newImage->GetChannel<float>(maskDesc.maskChannel);
if(mask == nullptr)
continue;
// Extract the mask.
shared_ptr<SimpleImage> maskOut;
if(maskDesc.maskType == MaskDesc::MaskType_CompositedRGB)
{
// Apply the mask to the image into maskOut. Use CollapseMode_Visibility
// when creating masks.
maskOut = DeepImageUtil::CollapseEXR(newImage, id, rgba, mask,
{ layerDesc.objectId },
DeepImageUtil::CollapseMode_Visibility);
}
else
{
// Output an alpha mask for MaskType_Alpha and MaskType_EXRLayer.
maskOut = make_shared<SimpleImage>(newImage->width, newImage->height);
bool useAlpha = maskDesc.maskType != MaskDesc::MaskType_Greyscale;
auto A = newImage->GetAlphaChannel();
DeepImageUtil::ExtractMask(useAlpha, true, mask, A, collapsedId, layerDesc.objectId, maskOut);
}
// If the baked image is completely empty, don't create it. As an exception,
// we do output empty masks in MaskType_EXRLayer.
if(maskDesc.maskType != MaskDesc::MaskType_EXRLayer && maskOut->IsEmpty())
continue;
if(maskDesc.maskType == MaskDesc::MaskType_EXRLayer)
{
// Instead of creating a layer that will be output to its own EXR
// file, put the mask in an EXR layer in the color layer file.
SimpleImage::EXRLayersToWrite *maskLayer = addLayer(colorImageOut, maskOut);
maskLayer->layerName = maskDesc.maskName;
maskLayer->alphaOnly = true;
}
else
{
// Output this mask to a separate file.
auto maskOutImage = createOutputImage(layerName, maskDesc.maskName, false);
addLayer(maskOutImage, maskOut);
}
}
}
// Write the layers.
for(const auto &outputImage: outputImages)
{
printf("Writing %s\n", outputImage->filename.c_str());
SimpleImage::WriteImages(outputImage->filename, outputImage->layers);
}
}
// Do simple substitutions on the output filename.
string EXROperation_WriteLayers::MakeOutputFilename(const OutputImage &layer) const
{
string outputName = outputPattern;
const string originalOutputName = outputName;
// <name>: the name of the object ID that we got from the EXR file, or "#100" if we
// only have a number.
outputName = subst(outputName, "<name>", layer.layerName);
string orderName = "";
if(layer.order > 0)
orderName += ssprintf("#%i ", layer.order);
orderName += layer.layerName;
outputName = subst(outputName, "<ordername>", orderName);
// <layer>: the output layer that we generated. This is currently always "color".
outputName = subst(outputName, "<layer>", layer.layerType);
// <order>: the order this layer should be composited. Putting this early in the
// filename makes filenames sort in comp order, which can be convenient.
outputName = subst(outputName, "<order>", ssprintf("%i", layer.order));
// <inputname>: the input filename, with the directory and ".exr" removed.
string inputName = sharedConfig.inputFilenames[0];
inputName = basename(inputName);
inputName = setExtension(inputName, "");
outputName = subst(outputName, "<inputname>", inputName);
// <frame>: the input filename's frame number, given a "abcdef.1234.exr" filename.
// It would be nice if there was an EXR attribute contained the frame number.
outputName = subst(outputName, "<frame>", GetFrameNumberFromFilename(sharedConfig.inputFilenames[0]));
static bool warned = false;
if(!warned && outputName == originalOutputName)
{
// If the output filename hasn't changed, there are no substitutions in it, which
// means we'll write a single file over and over. That's probably not what was
// wanted.
fprintf(stderr, "Warning: output path \"%s\" doesn't contain any substitutions, so only one file will be written.\n", outputName.c_str());
fprintf(stderr, "Try \"%s\" instead.\n", (outputName + "_<name>.exr").c_str());
warned = true;
}
outputName = sharedConfig.GetFilename(outputName);
return outputName;
}
// Given a filename like "abcdef.1234.exr", return "1234".
string EXROperation_WriteLayers::GetFrameNumberFromFilename(string s) const
{
// abcdef.1234.exr -> abcdef.1234
s = setExtension(s, "");
auto pos = s.rfind(".");
if(pos == string::npos)
return "";
string frameString = s.substr(pos+1);
return frameString;
}
void EXROperation_WriteLayers::MaskDesc::ParseOptionsString(string optionsString)
{
vector<string> options;
split(optionsString, ";", options);
for(string option: options)
{
vector<string> args;
split(option, "=", args);
if(args.size() < 1)
continue;
if(args[0] == "channel" && args.size() > 1)
{
maskChannel = args[1].c_str();
// If no mask name is specified, use the input channel by default.
if(maskName.empty())
maskName = maskChannel;
}
else if(args[0] == "name" && args.size() > 1)
maskName = args[1].c_str();
else if(args[0] == "grey")
maskType = MaskType_Greyscale;
else if(args[0] == "alpha")
maskType = MaskType_Alpha;
else if(args[0] == "rgb")
maskType = MaskType_CompositedRGB;
else if(args[0] == "exrlayer")
maskType = MaskType_EXRLayer;
}
}