1 #ifndef _WIN32
2 # include <unistd.h>
3 #endif
4
5 #include "DDImage/Enumeration_KnobI.h"
6 #include "DDImage/Reader.h"
7 #include "DDImage/Row.h"
8
9 #include <OpenImageIO/imageio.h>
10
11
12 using namespace DD::Image;
13
14
15 /*
16 * TODO:
17 * - Look into using the planar Reader API in Nuke 8, which may map better to
18 * TIFF/OIIO.
19 * - It would be nice to have a way to read in a region, rather than the whole
20 * image, but this would require access to the Read's request region,
21 * which isn't currently possible. A feature request for this is logged
22 * with The Foundry as Bug 46237.
23 */
24
25
26 namespace TxReaderNS {
27
28
29 using namespace OIIO;
30
31
32 static const char* const EMPTY[] = { NULL };
33
34
35 class TxReaderFormat : public ReaderFormat {
36 int mipLevel_;
37 int mipEnumIndex_;
38 Knob* mipLevelKnob_;
39 Knob* mipLevelEnumKnob_;
40
41 public:
TxReaderFormat()42 TxReaderFormat()
43 : mipLevel_(0)
44 , mipEnumIndex_(0)
45 , mipLevelKnob_(NULL)
46 , mipLevelEnumKnob_(NULL)
47 {
48 }
49
knobs(Knob_Callback cb)50 void knobs(Knob_Callback cb)
51 {
52 // The "real" mip level knob that controls the level read by the Reader
53 // class, and whose value is stored when the Read is serialized.
54 mipLevelKnob_ = Int_knob(cb, &mipLevel_, "tx_mip_level", "mip index");
55 SetFlags(cb, Knob::INVISIBLE);
56
57 // The user-facing mip level dropdown. This is populated lazily by the
58 // Reader when it opens a file, and does not directly contribute to the
59 // op hash or get stored when the Read is serialized.
60 mipLevelEnumKnob_ = Enumeration_knob(cb, &mipEnumIndex_, EMPTY,
61 "tx_user_mip_level", "mip level");
62 SetFlags(cb, Knob::EXPAND_TO_WIDTH | Knob::DO_NOT_WRITE
63 | Knob::NO_RERENDER);
64 Tooltip(cb,
65 "The mip level to read from the file. Currently, this will "
66 "be resampled to fill the same resolution as the base image.");
67 }
68
knob_changed(Knob * k)69 int knob_changed(Knob* k)
70 {
71 if (k == mipLevelEnumKnob_)
72 mipLevelKnob_->set_value(mipEnumIndex_);
73 return 1;
74 }
75
append(Hash & hash)76 void append(Hash& hash) { hash.append(mipLevel_); }
77
mipLevel()78 inline int mipLevel() { return mipLevel_; }
79
setMipLabels(std::vector<std::string> items)80 void setMipLabels(std::vector<std::string> items)
81 {
82 if (mipLevelEnumKnob_) {
83 mipLevelEnumKnob_->set_flag(Knob::NO_KNOB_CHANGED);
84 mipLevelEnumKnob_->enumerationKnob()->menu(items);
85 mipLevelEnumKnob_->set_value(
86 std::min((int)items.size() - 1, mipLevel_));
87 mipLevelEnumKnob_->clear_flag(Knob::NO_KNOB_CHANGED);
88 }
89 }
90
help()91 const char* help() { return "Tiled, mipmapped texture format"; }
92 };
93
94
95 class txReader : public Reader {
96 std::unique_ptr<ImageInput> oiioInput_;
97 TxReaderFormat* txFmt_;
98
99 int chanCount_, lastMipLevel_;
100 bool haveImage_, flip_;
101 std::vector<float> imageBuf_;
102 std::map<Channel, int> chanMap_;
103
104 MetaData::Bundle meta_;
105
106 static const Description d;
107
fillMetadata(const ImageSpec & spec,bool isEXR)108 void fillMetadata(const ImageSpec& spec, bool isEXR)
109 {
110 switch (spec.format.basetype) {
111 case TypeDesc::UINT8:
112 case TypeDesc::INT8:
113 meta_.setData(MetaData::DEPTH, MetaData::DEPTH_8);
114 break;
115 case TypeDesc::UINT16:
116 case TypeDesc::INT16:
117 meta_.setData(MetaData::DEPTH, MetaData::DEPTH_16);
118 break;
119 case TypeDesc::UINT32:
120 case TypeDesc::INT32:
121 meta_.setData(MetaData::DEPTH, MetaData::DEPTH_32);
122 break;
123 case TypeDesc::HALF:
124 meta_.setData(MetaData::DEPTH, MetaData::DEPTH_HALF);
125 break;
126 case TypeDesc::FLOAT:
127 meta_.setData(MetaData::DEPTH, MetaData::DEPTH_FLOAT);
128 break;
129 case TypeDesc::DOUBLE:
130 meta_.setData(MetaData::DEPTH, MetaData::DEPTH_DOUBLE);
131 break;
132 default: meta_.setData(MetaData::DEPTH, "Unknown"); break;
133 }
134
135 meta_.setData("tx/tile_width", spec.tile_width);
136 meta_.setData("tx/tile_height", spec.tile_height);
137
138 string_view val;
139
140 val = spec.get_string_attribute("ImageDescription");
141 if (!val.empty())
142 meta_.setData("tx/image_description", val);
143
144 val = spec.get_string_attribute("DateTime");
145 if (!val.empty())
146 meta_.setData(MetaData::CREATED_TIME, val);
147
148 val = spec.get_string_attribute("Software");
149 if (!val.empty())
150 meta_.setData(MetaData::CREATOR, val);
151
152 val = spec.get_string_attribute("textureformat");
153 if (!val.empty())
154 meta_.setData("tx/texture_format", val);
155
156 val = spec.get_string_attribute("wrapmodes");
157 if (!val.empty())
158 meta_.setData("tx/wrap_modes", val);
159
160 val = spec.get_string_attribute("fovcot");
161 if (!val.empty())
162 meta_.setData("tx/fovcot", val);
163
164 val = spec.get_string_attribute("compression");
165 if (!val.empty())
166 meta_.setData("tx/compression", val);
167
168 if (isEXR) {
169 val = spec.get_string_attribute("openexr:lineOrder");
170 if (!val.empty())
171 meta_.setData("exr/line_order", val);
172
173 float cl = spec.get_float_attribute("openexr:dwaCompressionLevel",
174 0.0f);
175 if (val > 0)
176 meta_.setData("exr/dwa_compression_level", cl);
177 } else {
178 val = spec.get_string_attribute("tiff:planarconfig");
179 if (!val.empty())
180 meta_.setData("tiff/planar_config", val);
181 }
182 }
183
setChannels(const ImageSpec & spec)184 void setChannels(const ImageSpec& spec)
185 {
186 ChannelSet mask;
187 Channel chan;
188 int chanIndex = 0;
189
190 for (std::vector<std::string>::const_iterator it
191 = spec.channelnames.begin();
192 it != spec.channelnames.end(); it++) {
193 chan = Reader::channel(it->c_str());
194 mask += chan;
195 chanMap_[chan] = chanIndex++;
196 }
197
198 info_.channels(mask);
199 }
200
201 public:
txReader(Read * iop)202 txReader(Read* iop)
203 : Reader(iop)
204 , oiioInput_(ImageInput::open(filename()))
205 , chanCount_(0)
206 , lastMipLevel_(-1)
207 , haveImage_(false)
208 , flip_(false)
209 {
210 txFmt_ = dynamic_cast<TxReaderFormat*>(iop->handler());
211
212 OIIO::attribute("threads", (int)Thread::numThreads / 2);
213
214 if (!oiioInput_) {
215 iop->internalError("OIIO: Failed to open file %s: %s", filename(),
216 geterror().c_str());
217 return;
218 }
219
220 const ImageSpec& baseSpec = oiioInput_->spec();
221
222 if (!(baseSpec.width * baseSpec.height)) {
223 iop->internalError("tx file has one or more zero dimensions "
224 "(%d x %d)",
225 baseSpec.width, baseSpec.height);
226 return;
227 }
228
229 chanCount_ = baseSpec.nchannels;
230 const bool isEXR = strcmp(oiioInput_->format_name(), "openexr") == 0;
231
232 if (isEXR) {
233 float pixAspect = baseSpec.get_float_attribute("PixelAspectRatio",
234 0);
235 set_info(baseSpec.width, baseSpec.height, 1, pixAspect);
236 meta_.setData(MetaData::PIXEL_ASPECT,
237 pixAspect > 0 ? pixAspect : 1.0f);
238 setChannels(baseSpec); // Fills chanMap_
239 flip_ = true;
240 } else {
241 set_info(baseSpec.width, baseSpec.height, chanCount_);
242 int orientation = baseSpec.get_int_attribute("Orientation", 1);
243 meta_.setData("tiff/orientation", orientation);
244 flip_ = !((orientation - 1) & 2);
245
246 int chanIndex = 0;
247 foreach (z, info_.channels())
248 chanMap_[z] = chanIndex++;
249 }
250
251 fillMetadata(baseSpec, isEXR);
252
253 // Populate mip level pulldown with labels in the form:
254 // "MIPLEVEL - WxH" (e.g. "0 - 1920x1080")
255 std::vector<std::string> mipLabels;
256 std::ostringstream buf;
257 ImageSpec mipSpec(baseSpec);
258 int mipLevel = 0;
259 while (true) {
260 buf << mipLevel << " - " << mipSpec.width << 'x' << mipSpec.height;
261 mipLabels.push_back(buf.str());
262 if (oiioInput_->seek_subimage(0, mipLevel + 1, mipSpec)) {
263 buf.str(std::string());
264 buf.clear();
265 mipLevel++;
266 } else
267 break;
268 }
269
270 meta_.setData("tx/mip_levels", mipLevel + 1);
271
272 txFmt_->setMipLabels(mipLabels);
273 }
274
~txReader()275 virtual ~txReader()
276 {
277 if (oiioInput_)
278 oiioInput_->close();
279 }
280
open()281 void open()
282 {
283 if (lastMipLevel_ != txFmt_->mipLevel()) {
284 ImageSpec mipSpec;
285 if (!oiioInput_->seek_subimage(0, txFmt_->mipLevel(), mipSpec)) {
286 iop->internalError("Failed to seek to mip level %d: %s",
287 txFmt_->mipLevel(),
288 oiioInput_->geterror().c_str());
289 return;
290 }
291
292 if (txFmt_->mipLevel() && mipSpec.nchannels != chanCount_) {
293 iop->internalError("txReader does not support mip levels with "
294 "different channel counts");
295 return;
296 }
297
298 lastMipLevel_ = txFmt_->mipLevel();
299 haveImage_ = false;
300 }
301
302 if (!haveImage_) {
303 const int needSize = oiioInput_->spec().width
304 * oiioInput_->spec().height
305 * oiioInput_->spec().nchannels;
306 if (size_t(needSize) > imageBuf_.size())
307 imageBuf_.resize(needSize);
308 oiioInput_->read_image(&imageBuf_[0]);
309 haveImage_ = true;
310 }
311 }
312
engine(int y,int x,int r,ChannelMask channels,Row & row)313 void engine(int y, int x, int r, ChannelMask channels, Row& row)
314 {
315 if (!haveImage_)
316 iop->internalError("engine called, but haveImage_ is false");
317
318 if (aborted()) {
319 row.erase(channels);
320 return;
321 }
322
323 const bool doAlpha = channels.contains(Chan_Alpha);
324
325 if (flip_)
326 y = height() - y - 1;
327
328 if (lastMipLevel_) { // Mip level other than 0
329 const int mipW = oiioInput_->spec().width;
330 const int mipMult = width() / mipW;
331
332 y = y * oiioInput_->spec().height / height();
333 const int bufX = x ? x / mipMult : 0;
334 const int bufR = r / mipMult;
335 const int bufW = bufR - bufX;
336
337 std::vector<float> chanBuf(bufW);
338 float* chanStart = &chanBuf[0];
339 const int bufStart = y * mipW * chanCount_ + bufX * chanCount_;
340 const float* alpha
341 = doAlpha ? &imageBuf_[bufStart + chanMap_[Chan_Alpha]] : NULL;
342 foreach (z, channels) {
343 from_float(z, &chanBuf[0], &imageBuf_[bufStart + chanMap_[z]],
344 alpha, bufW, chanCount_);
345
346 float* OUT = row.writable(z);
347 for (int stride = 0, c = 0; stride < bufW; stride++, c = 0)
348 for (; c < mipMult; c++)
349 *OUT++ = *(chanStart + stride);
350 }
351 } else { // Mip level 0
352 const int pixStart = y * width() * chanCount_ + x * chanCount_;
353 const float* alpha
354 = doAlpha ? &imageBuf_[pixStart + chanMap_[Chan_Alpha]] : NULL;
355
356 foreach (z, channels) {
357 from_float(z, row.writable(z) + x,
358 &imageBuf_[pixStart + chanMap_[z]], alpha, r - x,
359 chanCount_);
360 }
361 }
362 }
363
fetchMetaData(const char * key)364 const MetaData::Bundle& fetchMetaData(const char* key) { return meta_; }
365 };
366
367
368 } // namespace TxReaderNS
369
370
371 static Reader*
buildReader(Read * iop,int fd,const unsigned char * b,int n)372 buildReader(Read* iop, int fd, const unsigned char* b, int n)
373 {
374 // FIXME: I expect that this close() may be problematic on Windows.
375 // For Linux/gcc, we needed to #include <unistd.h> at the top of
376 // this file. If this is a problem for Windows, a different #include
377 // or a different close call here may be necessary.
378 close(fd);
379 return new TxReaderNS::txReader(iop);
380 }
381
382 static ReaderFormat*
buildformat(Read * iop)383 buildformat(Read* iop)
384 {
385 return new TxReaderNS::TxReaderFormat();
386 }
387
388 static bool
test(int fd,const unsigned char * block,int length)389 test(int fd, const unsigned char* block, int length)
390 {
391 // Big-endian TIFF
392 if (block[0] == 'M' && block[1] == 'M' && block[2] == 0 && block[3] == 42)
393 return true;
394 // Little-endian TIFF
395 if (block[0] == 'I' && block[1] == 'I' && block[2] == 42 && block[3] == 0)
396 return true;
397 // EXR
398 return block[0] == 0x76 && block[1] == 0x2f && block[2] == 0x31
399 && block[3] == 0x01;
400 }
401
402 const Reader::Description TxReaderNS::txReader::d("tx\0TX\0", buildReader, test,
403 buildformat);
404