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:

  .. code-block:: c

    #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.

.. cpp:function:: 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.

.. cpp:function:: 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.
