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