Planar Iop: Image Processing with 2-Dimensional Outputs
=======================================================

Nuke also has support for Planar Iops, in addition to the usual
Row-based Iops.  These are primarily intended for Iops that find producing
their output in single rows to be unnatural, and would prefer instead to output
data containing multiple rows at a time (either the entire image, or smaller stripes.)

Planar Iops have two levels of abstraction: there is a core PlanarI
interface, which provides a mechanism that PlanarIop then uses.  We encourage users 
to implement PlanarIop rather than PlanarI, as PlanarIop provides additional caching, 
without which performance can be impaired.

Render Stripe
-------------

The main rendering function for a PlanarIop is *renderStripe()*::

   void renderStripe(ImagePlane& imagePlane) 

This is the equivalent to *engine()* for a normal Iop, but is required to
fill in a two-dimensional "stripe" of the output image, rather than a 
single row. 

The implementation of *renderStripe()* should output a rendered stripe to *imagePlane*.
This image plane will already have been set up with the required
bounding box and channels; all the implementation needs to do is to
fill in the output data for this region and channels.

By default, *renderStripe()* will be called for the entire bounding box of the
Iop, as set by *_validate()*.  This is usually the most efficient way to process
for a plugin that takes the entire input image as a single input and produces a
single output image. However, since the majority of Nuke nodes do row-based processing,
users often don't expect to have to wait for a whole frame's worth of data to be calculated 
before they begin to see any output. A node that requires the whole of its input to be calculated
before it can start to produce data might not give the fast feedback they would like,
particularly when there is a large and complicated node graph above it. In addition, if
a node requires a large amount of memory for its processing or needs to process on a device 
with a relatively small amount of memory, such as a GPU, it might not be appropriate or even possible 
to process the whole output area at once.

For the reasons above, a PlanarIop can choose to render in stripes rather than render the whole image
in one go. To do this you would implement the function

 .. code-block:: c

    bool useStripes() const

to return true. 

You can also implement the function

 .. code-block:: c

    int stripeHeight() const

to set the height of the render stripes. The stripes will then be of the height
returned by *stripeHeight()*, apart from the final stripe (furthest from the origin),
which will be clipped to the bounding box size if necessary. The stripes will always
be the full width of the image, in order to interoperate nicely with the Row-based 
operators in Nuke.


.. note::

  Unlike *engine()*, *renderStripe()* is called on a single thread at a time, so if you require multi-threaded processing inside a PlanarIop then you must implement this yourself. The PlanarIop interface was designed to support custom multi-threading by plug-ins which don't fit well with Nuke's standard scanline-based approach, or plug-ins which do their processing on a different device such as a GPU. In the past, such plug-ins had to implement locking inside *engine()* in order to launch their custom processing on a single engine thread, and further implement the reading back of scanlines from the output buffer they produced, as well as caching that output buffer internally somehow. With the PlanarIop interface, now all you need to do is implement the ``renderStripe(ImagePlane& imagePlane)`` function, and Nuke will take care of caching the output plane produced and then reading back scanlines from it as required. 


Request
-------

*Important*: PlanarIops should not implement *_request()*. Instead, they should implement
*getRequests()*, which is a similar, but non-recursive, function::

  virtual void getRequests(const Box& box,
                           const ChannelSet& channels, 
                           int count,
                           RequestOutput &reqData) const

This will be used for more complex render management in future. In addition, for PlanarIops only *getRequests()* is guaranteed to be set up correctly for planar processing; *_request()* might not be called with the correct information, so we strongly recommend that you don't try to implement it for a PlanarIop.

Channels and Layers
-------------------

A PlanarIop will get a separate render stripe call to fill in each set of channels that go to make up an image plane. The default behaviour is to have a plane for each layer, e.g. forward and backward motion vectors will be stored in a single image plane. An image plane will never contain more than four channels.

The following function determines how the channels will be allocated to image planes:

  .. code-block:: c

      virtual PlaneID PlanarI::getPlaneFromChannel(Channel chan);

Since it is virtual, a plugin can change the default behaviour and decide on its own definition of the image planes by overriding this function.

*PlaneID* is a presently a typedef to *ChannelSet* (this might change in subsequent versions). The default
implementation of *getPlaneFromChannel()* implements the following mapping

  .. table:: 

      ===============    =========
      Chan_Red           Mask_RGBA
      Chan_Green         Mask_RGBA
      Chan_Blue          Mask_RGBA
      Chan_Alpha         Mask_RGBA
      Chan_U             Mask_MoVec
      Chan_V             Mask_MoVec
      Chan_Backward_U    Mask_MoVec
      Chan_Backward_V    Mask_MoVec
      ===============    =========
      

As an example, if you wanted the forward and backward vectors to be calculated separately, you could
override *getPlaneFromChannels()* to provide the following mapping

  .. table:: 

      ===============    =========
      Chan_Red           Mask_RGBA
      Chan_Green         Mask_RGBA
      Chan_Blue          Mask_RGBA
      Chan_Alpha         Mask_RGBA
      Chan_U             Mask_MoVecForward
      Chan_V             Mask_MoVecForward
      Chan_Backward_U    Mask_MoVecBackward
      Chan_Backward_V    Mask_MoVecBackward
      ===============    =========

By default, a render stripe call will not necessarily be asked to fill an image plane containing all of the channels that go to make up a plane, but only those channels that have been requested (by one or more Ops below the PlanarIop in the node graph). If your plugin needs to render all the channels in a plane (or would find it more efficient to do so), you can override the following function to return true:

  .. code-block:: c

     virtual bool renderFullPlanes() const;

When *renderFullPlanes()* returns true, the plugin will never be given an image plane to fill that contains less than the full set of channels. 


Packed Preference
-----------------

There is a further virtual function you can implement

  .. code-block:: c

     virtual PackedPreference packedPreference() const

The return value of this indicates whether the Iop prefers to produce packed or unpacked
output planes.  If it returns ePackedPreferenceNone it has no strong preference and 
renderStripe() can be called with either depending upon which the calling code finds more
convenient; if it returns ePackedPreferencePacked ePackedPreferenceUnpacked then renderStripe()
will only be called to fill a packed or unpacked stripe.

PlanarI
-------

We do not encourage plugins to use this directly; rather, they should inherit from PlanarIop.


Image Planes
============

The ImagePlane class is at the heart of the Planar code.  ImagePlanes can be of various formats, with different
bounding box, channels and packness.  The bbox can be obtained with the accessor function *bounds()*; the channels
with *channels()* and the packness with *packed()*.

In all modes there is no padding.  For example, in packed mode, with a 2x2 image, with RGBA channels, the 
floats would be

  .. table:: 

    ========== =========  =========  =========
    red   0,0  green 0,0  blue  0,0  alpha 0,0
    red   0,1  green 0,1  blue  0,1  alpha 0,1
    red   1,0  green 1,0  blue  1,0  alpha 1,0
    red   1,1  green 1,1  blue  1,1  alpha 1,1
    ========== =========  =========  =========

whereas in unpacked mode the data is represented by

  .. table:: 

    ========== =========  =========  =========
    red   0,0  red   0,1  red   1,0  red   1,1
    green 0,0  green 0,1  green 1,0  green 1,1
    blue  0,0  blue  0,1  blue  1,0  blue  1,1
    alpha 0,0  alpha 0,1  alpha 1,0  alpha 1,1
    ========== =========  =========  =========

ImagePlane provides several accessor functions for its data.

Low-level access
----------------

*ImagePlane::readable()* points at the start of the allocation.   For the examples above, this will point at the red at (0, 0).

*rowStride()*, *colStride()* and *chanStride()* can then be used to multiply y, x, and channel values, to add to these.  For example,
in the first,

  .. table:: 

    =============  =========
    colStride()    4
    rowStride()    8 (2 * 4)
    chanStride()   1
    =============  =========

and in the second

  .. table:: 

    =============  =========
    colStride()    1 
    rowStride()    2
    chanStride()   4
    =============  =========
  
*data()* points at the bottom-left corner of the image, which is not necessarily at the origin (0, 0).

There is no support for images with origins at the top-left.

Higher-level access
-------------------

You can access individual pixels with

  .. code-block:: c

    const float& ImagePlane::at(int x, int y, int z)

z is the channel number /within/ the ChannelSet for the ImagePlane, i.e. for a Mask_RGBA
image plane then red = 0, green = 1, blue = 2, alpha = 3.  If it was just red, green and alpha, then, then this would make red = 0, green = 1, alpha = 2.  

The function *ImagePlane::chanNo()*
can be used to look this up but this iterates over the ChannelSet and you should avoid calling
it within a tight loop.

There is also a 

  .. code-block:: c

    const float& ImagePlane::at(int x, int y, Channel z)

which does the lookup of the Channel for you.

These functions give references to the actual image data.  If you want to abstract
some of that away, there are functions that return strided pointers.

  .. code-block:: c

    const ImageTilePtr ImagePlane::readableAt(int y, int z);

This returns a pointer to (0, y, z).  The pointer is strided: so it knows what
*chanStride()* is, so that ++ increments it to the next x value.

Note that this returns a pointer to (0, y, z) even if the x bounds of the origin start
rightwards of the origin.  This means that the
pointer cannot be safely deferenced without incrementing by (at a minimum)
the x bounds.  This is there to make multiplying in the offset slightly easier,
and is consistent with the way that *Row::operator[]* works.

Writing to ImagePlane
---------------------

ImagePlanes use shallow copying when possible.

If you are writing to an ImagePlane that might be shared with what ought to be read-only copies then you should call

  .. code-block:: c

    void ImagePlane::makeUnique()

If you are writing to a new ImagePlane that you made, you should call

  .. code-block:: c

    void ImagePlane::makeWritable()

One pattern that is valid, for example, is
  
  .. code-block:: c

    void renderStripe(ImagePlane& outPlane) {

      input0().fetchPlane(outPlane);

      outPlane.makeUnique()

      Box box = outPlane.bounds();

      foreach(z, outPlane.channels()) {
        for (Box::iterator it = box.begin(); it != box.end(); it++) {
          outPlane.writableAt(it.x, it.y, outPlane.chanNo(z)) *= 2;
        }
      }
    }

If there is another copy that that shares backend data then it will copy the data
and then make the modifications in-place.  If there is no extra copy that is needed,
because it has decided that the input does not need caching (perhaps it is really
cheap to calculate), then it will not need to even do a copy, thus reducing peak
memory use.

If you want to build an output from scratch, you can do something like:

  .. code-block:: c

    outPlane.makeWritable()

    foreach(z, outPlane.channels()) {
      for (Box::iterator it = box.begin(); it != box.end(); it++) {
        outPlane.writableAt(it.x, it.y, outPlane.chanNo(z)) = 0.5;
      }
    }
