Deep Reader

The ‘DeepRead’ node supports different file formats through the use of ‘DeepReader’ plug-ins. This is similar to the relationship between the ‘Read’ node and the ‘Reader’ plug-ins.

A deep file reader should inherit from the class DD::Image::DeepReader, and then register themselves in the symbol table by creating a DD::Image::DeepReader::Description. Here is a trivial example:

#include "DDImage/DeepReader.h"
#include "DDImage/DeepPlane.h"

using namespace DD::Image;

class BlankReaderDeep : public DeepReader
{

public:
  BlankReaderDeep(DeepReaderOwner* op, const std::string& filename) : DeepReader(op)
  {
    setInfo(10, 10, _owner->readerOutputContext(), Mask_RGBA | Mask_Deep);
    _metaData.setData("deep/example", "123");
  }

  virtual void doDeepEngine(Box box, const ChannelSet& channels, DeepOutputPlane& outPlane)
  {
    outPlane = DeepOutputPlane(channels, box);
    for (Box::iterator it = box.begin();
         it != box.end();
         it++) {
      outPlane.addHole();
    }
  }
};

static const DeepReader::Description d("blank\0", "blank", BuildDeepReader<BlankReaderDeep>);

This plug-in is then compiled to the file “blankReaderDeep.so”, and is invoked by NUKE when a DeepRead tries to open a file with the extension “.blank”. See the cdfReaderDeep.cpp sample included with the NDK for a more complex example.

BlankReaderDeep::BlankReaderDeep(DeepReaderOwner *op, const std::string &filename)

This constructor is invoked by the DeepRead when it tries to access image metadata, and in preparation for accessing actual image data. It is given the filename, and a pointer to a small interface allowing certain data on the DeepRead to be accessed. The filename is fully-cooked at this point (i.e. with expressions fully evaluated), and can be passed to an fopen() call.

The constructor opens the file and reads from it sufficient information to determine the image size, number of channels present, and possibly other metadata. It presents the basic information by calling setInfo. This function takes width and height as its first parameters, then an outputContext reference, then the set of channels that are available, and then an optional pixel aspect ratio.

setInfo itself looks for an appropriate matching Format, and if no such Format is found, it creates one. The OutputContext is used to implement downrez. If a format is passed along (the example here passes along the owner’s readerOutputContext, which is usual), then it calculates the proxy format and bounding box along with the full-size one. This means that with a downrez of 2, the bounding box produced by DeepRead for this plug-in is 5x5, not 10x10.

The BlankReaderDeep can also set metadata, as demonstrated in the example.

Other Deep Ops may expect the DeepFront and DeepBack channels to be present. If you only have one depth per sample, mark both DeepFront and DeepBack, and set them to the same data.

virtual void doDeepEngine(Box box, const ChannelSet &channels, DeepOutputPlane &outPlane)

The DeepRead defers the doDeepEngine function directly to the DeepReader. The DeepReader must create a plane and populate it for all the pixels in box and channels in channels. The example here just adds holes, but it could also assemble DeepOutPixels and add those. It is important to handle all the channels, including those which were not part of the mask that the constructor set - these should be filled in with zeroes. Boxes outside the constructor’s bounding box might also be requested - these should be output as holes, 0 sample pixels.

Note that doDeepEngine() has to do its own downrezzing. The box it has been passed has already been downrezzed, and it can use the readerOutputContext().from_proxy_x() and from_proxy_y() functions to convert pixel coordinates back to full resolution for file access.