PixelIop: Getting Started With Image Processing 
================================================

The PixelIop class provides a specialised version of Iop, adding boilerplate code for convenience of use at the expense 
of limiting potential use cases. PixelIop inherits the majority of its functionality from the base image processing class Iop, so if
you haven't already, please read :ref:`2d-architecture`, before coming back to this section.

PixelIop is intended for image processing operators that are not spatial in any way - ie each pixel in the output depends upon the pixel at the
same coordinates in the input, and no other pixels.  Examples of Ops that might be implemented as PixelIops could include Gain
or Desaturate. 

PixelIops are an excellent way to get started with using the NDK in NUKE, and have a very low memory footprint. If the algorithm you
are looking to implement can be reduced to only rely on the input pixel or row, and thus be implemented as a PixelIop, then it should.

* Intended for use by image manipulation operators which, for each pixel in the output, rely solely on the corresponding pixel in the input (althought strictly speaking it can use any pixel in the same scanline row of the input as the current output pixel's row).
* Adds simplified channel manipulation functions.

The PixelIop Class Specifics & Required Virtual Calls
-----------------------------------------------------

PixelIop varies from Iop by having two new pure virtual calls that a Op built on it must implement.  These are:

.. cpp:function:: void PixelIop::in_channels(int input, ChannelSet& mask)
 
 in_channels is called by the PixelIop's definition of _request, for each input.  It passes the input number as 'input' and
 a reference to a ChannelSet as 'mask'.  This ChannelSet is initialised with the channels that have been requested 
 - in_channels should fill in any other channels that are needed (or remove those that are unnecessary) on the given input.

 For example, a simple Desaturate would implement in_channels like so:

  .. code-block:: c

    virtual void in_channels(int input, ChannelSet& mask) const 
    {
      if (mask.contains(Mask_RGB))
        mask += Mask_RGB;
    }

 That is, if any of the Red, Green or Blue channels have been requested, it needs to request all of them from its input.

.. cpp:function:: void PixelIop::pixel_engine(const Row &in, int y, int x, int r, ChannelMask, Row &)

 The pixel_engine function is called with the row from the input already fetched with the correct channels from in_channels
 Generally the output row and the input row are actually the same Row, but this should not be relied upon.  A simple 
 implementation of pixel_engine that merely multiples all the pixels by 0.5 is as follows:

 .. code-block:: c

    virtual void pixel_engine(const Row &in, int y, int l, int r, ChannelMask channels, Row& out)
    {
      foreach(z, channels) {
        const float* inptr = in[z];
        float* outptr = out.writable(z);
        for (int x = l; x < r; x++) {
          outptr[x] = inptr[x] * 0.5;
        }
      }
    }

A PixelIop derived Op can optionally choose to override the default :ref:`2d-iop-call-order-validate` provided by Iop.

.. cpp:function:: void PixelIop::_validate(bool for_real)
 
 The default Iop provided :ref:`2d-iop-call-order-validate` merges IopInfo from all inputs into one, and turns on all the channels set in out_channels.
 By default out_channels is set to Mask_All, but it can be set as you desire by overriding _validate, calling set_out_channels, 
 and then calling the base class' validate. For example:

 .. code-block:: c
 
    void _validate(bool for_real) {
      set_out_channels(Mask_RGB);
      PixelIop::_validate(for_real);
    }

 Another common situation is that the current interface settings mean your Op does not actually do anything to the underlying
 image data. In this situation you should instead call disable() in your _validate() which will allow NUKE to optimise the Op tree.

 .. code-block:: c
 
    void _validate(bool for_real) {
      if(_doingAnyWork) {
        set_out_channels(Mask_RGB);
        PixelIop::_validate(for_real);
      } else {
        disable();
      }
    }

 More _validate circumstances are described in the :ref:`2d-architecture` section.


As with all Ops, PixelIops must additionally implement Description, and may optionally implement knobs() to provide
user interface elements. 

Getting Started: The Basic Node
--------------------------------

Congratulations for wading through the theory thus far! Now for the start of the fun stuff - writing your first operator. Get yourself
setup with a compiler as per :ref:`appendixa-index` and compile up the basic example. We've replicated the Basic code below for convenience.

.. code-block:: c
 
  static const char* const HELP = "Basic: Does nothing but copy the input from input0 to the output";

  #include <DDImage/NukeWrapper.h> 
  #include <DDImage/PixelIop.h> 
  #include <DDImage/Row.h> 
  #include <DDImage/Knobs.h>

  using namespace DD::Image;
 
  class Basic : public PixelIop {
  public: 
   void in_channels(int input, ChannelSet& mask) const; 
   Basic(Node *node) : PixelIop(node) {
   }
 
   void pixel_engine(const Row& in, int y, int x, int r, ChannelMask, Row& out);
   static const Iop::Description d; 
   const char* Class() const {return d.name;} 
   const char* node_help() const {return HELP;} 
   void _validate(bool);
  };

  void Basic::_validate(bool for_real) { 
    copy_info();
    set_out_channels(Mask_All); 
  }

  void Basic::in_channels(int input, ChannelSet& mask) const {
  //mask is unchanged
  }

  void Basic::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out){
    foreach (z, channels) { 
      const float* inptr = in[z]+x; 
      const float* END = inptr+(r-x); 
      float* outptr = out.writable(z)+x; 
        while (inptr < END) *outptr++ = *inptr++;
    }
  }

  static Iop* build(Node *node) {return new NukeWrapper(new Basic(node));} 
  const Iop::Description Basic::d("Basic", "Basic", build);

First, some notes about this plug-in, from top to bottom.The **HELP** string and **node_help** function do as the names suggest, provides help to the user 
in the form of a “tooltip” when the user clicks the "?" button in the node’s properties. The **in_channels** function is required by  the parent **PixelIop** 
class and tells NUKE what channels of the image we would like to access. In general, we would like all channels, and so leave **ChannelSet& mask** untouched. If we 
wanted, we could restrict the channels being operated on, for example, if we only desired the red channel: **mask &= Mask_Red;**. Lastly, the **build(Node *node)** 
function takes our image operator (Iop) and wraps it into a NUKE’s node for us via the **NukeWrapper** class, while the **d("Basic", "Basic", build)** defines 
the class / Iop name which'll be used to create the node. It is vitally important that the first parameter string matches the filename of the compiled plug-in, 
i.e. "Basic" will refer to 
Basic.dll (on Windows), or Basic.so or Basic.dylib depending on your system. **NukeWrapper** will be discussed in more detail in :ref:`2d-nukewrapper`,
for now simply be aware that this is a convenience function which adds common controls and functionality to the operator. In this circumstance it is responsible
for adding the channel, mask, and mix controls you see when you open the node's parameter panel.

The actual interesting things in this snippet are the **_validate** and **pixel_engine** functions. Essentially, the **_validate** function tells NUKE about the 
"size and format of the generated images", including the image channels we’re going to access and create. In this example, **copy_info()** is called, which takes the format 
information from the input image, and copies it onto the format of the current “Basic” node.The **set_out_channels(Mask_All);** call simply says that we will 
be looking at (and modifying) all channels in the image. The **Mask_All** could be replaced by **Mask_Red** or **Mask_Alpha** if we just wanted to look at the 
red or alpha image channels respectively.

Now, the **pixel_engine** function is where the pixel reading / writing happens. Notice that the class **Row** is used a lot in this function.This is because 
NUKE likes to operate on scanline rows of images at a time. This makes sense for many reasons, such as operating on different scanlines in parallel (multiple threads) 
or more importantly, only needing to keep a handful of image rows in memory at any given time. The row index you are currently working on is given by **y**, with the 
offset into the scanline given by **x**. Note that either or both of these can be negative (for example, if the input image is outside the bounds of the frame rectangle). 
The last index of the scanline is given by **r**, the width of the input scan-line is therefore given by **r-x-1**. The channels that you have access to are set 
in the **channels**. As you can see from the code, **in[z]+x** and **out.writable(z)+x** give you pointers to the input and output row data for channel **z**,
where z is obtained using the **foreach** function to iterate over the collection of channels passed in.
The output pointer can obviously then be accessed to do what ever you’d like.


Have a good play around with this code and start making some changes. Take what you've learned and apply it to understanding the Add.cpp example shipped with the NDK.
This is the actual source code for the Add node compiled into NUKE, and is a great starting point for exploring PixelIops. If you run into areas you're not sure about,
jump on to the next section which covers some further fundamentals, with reference to the more complex Grade example.

Building the Grade Node
-----------------------

Now lets take a look at our first 'real' node; namely the grade operator. The source code from which this node is actually built is shipped with the NDK so again, 
compile yourself up a copy referencing :ref:`appendixa-index`. The source code is duplicated below for convenience.

.. code-block:: c

  // Grade.C
  // Copyright (c) 2009 The Foundry Visionmongers Ltd.  All Rights Reserved.

  const char* const HELP =
    "<p>Applies a linear ramp followed by a gamma function to each color channel.</p>"
    "<p>  A = multiply * (gain-lift)/(whitepoint-blackpoint)<br>"
    "  B = offset + lift - A*blackpoint<br>"
    "  output = pow(A*input + B, 1/gamma)</p>"
    "The <i>reverse</i> option is also provided so that you can copy-paste this node to "
    "invert the grade. This will do the opposite gamma correction followed by the "
    "opposite linear ramp.";

  #include "DDImage/PixelIop.h"
  #include "DDImage/Row.h"
  #include "DDImage/DDMath.h"
  #include "DDImage/NukeWrapper.h"
  #include <string.h>

  using namespace DD::Image;

  static const char* const CLASS = "Grade";

  class GradeIop : public PixelIop
  {
    float blackpoint[4];
    float whitepoint[4];
    float black[4];
    float white[4];
    float add[4];
    float multiply[4];
    float gamma[4];
    bool reverse;
    bool black_clamp;
    bool white_clamp;
  public:
    GradeIop(Node* node) : PixelIop(node)
    {
      for (int n = 0; n < 4; n++) {
        black[n] = blackpoint[n] = add[n] = 0.0f;
        white[n] = whitepoint[n] = multiply[n] = 1.0f;
        gamma[n] = 1.0f;
      }
      reverse = false;
      black_clamp = true;
      white_clamp = false;
    }
    // indicate that channels only depend on themselves:
    void in_channels(int, ChannelSet& channels) const { }
    void pixel_engine(const Row &in, int y, int x, int r, ChannelMask, Row &);
    virtual void knobs(Knob_Callback);
    const char* Class() const { return CLASS; }
    const char* node_help() const { return HELP; }
    static const Iop::Description d;

    void _validate(bool for_real);
  };

  void GradeIop::_validate(bool for_real)
  {
    bool change_any = black_clamp | white_clamp;
    bool change_zero = false;
    for (int z = 0; z < 4; z++) {
      float A = whitepoint[z] - blackpoint[z];
      A = A ? (white[z] - black[z]) / A : 10000.0f;
      A *= multiply[z];
      float B = add[z] + black[z] - blackpoint[z] * A;
      if (A != 1 || B || gamma[z] != 1.0f) {
        change_any = true;
        if (B)
          change_zero = true;
      }
    }
    set_out_channels(change_any ? Mask_All : Mask_None);
    PixelIop::_validate(for_real);
    if (change_zero)
      info_.black_outside(false);
  }

  void GradeIop::pixel_engine(const Row& in, int y, int x, int r,
                              ChannelMask channels, Row& out)
  {
    foreach (n, channels) {
      unsigned z = colourIndex(n);
      if (z > 3) {
        out.copy(in, n, x, r);
        continue;
      }
      float A = whitepoint[z] - blackpoint[z];
      A = A ? (white[z] - black[z]) / A : 10000.0f;
      A *= multiply[z];
      float B = add[z] + black[z] - blackpoint[z] * A;
      if (!B && in.is_zero(n)) {
        out.erase(n);
        continue;
      }
      float G = gamma[z];
      // patch for linux alphas because the pow function behaves badly
      // for very large or very small exponent values.
  #ifdef __alpha
      if (G < 0.008f)
        G = 0.0f;
      if (G > 125.0f)
        G = 125.0f;
  #endif
      const float* inptr = in[n] + x;
      float* OUTBUF = out.writable(n) + x;
      float* END = OUTBUF + (r - x);
      if (!reverse) {
        // do the linear interpolation:
        if (A != 1 || B) {
          for (float* outptr = OUTBUF; outptr < END;)
            *outptr++ = *inptr++ *A + B;
          inptr = OUTBUF;
        }
        // clamp
        if (white_clamp || black_clamp) {
          for (float* outptr = OUTBUF; outptr < END;) {
            float a = *inptr++;
            if (a < 0.0f && black_clamp)
              a = 0.0f;
            else if (a > 1.0f && white_clamp)
              a = 1.0f;
            *outptr++ = a;
          }
          inptr = OUTBUF;
        }
        // do the gamma:
        if (G <= 0) {
          for (float* outptr = OUTBUF; outptr < END;) {
            float V = *inptr++;
            if (V < 1.0f)
              V = 0.0f;
            else if (V > 1.0f)
              V = INFINITY;
            *outptr++ = V;
          }
        }
        else if (G != 1.0f) {
          G = 1.0f / G;
          for (float* outptr = OUTBUF; outptr < END;) {
            float V = *inptr++;
            if (V <= 0.0f)
              ;              //V = 0.0f;
  #ifdef __alpha
            else if (V <= 1e-6f && G > 1.0f)
              V = 0.0f;
  #endif
            else if (V < 1)
              V = powf(V, G);
            else
              V = 1.0f + (V - 1.0f) * G;
            *outptr++ = V;
          }
        }
        else if (inptr != OUTBUF) {
          memcpy(OUTBUF, inptr, (END - OUTBUF) * sizeof(*OUTBUF));
        }
      }
      else {
        // Reverse gamma:
        if (G <= 0) {
          for (float* outptr = OUTBUF; outptr < END;)
            *outptr++ = *inptr++ > 0.0f ? 1.0f : 0.0f;
          inptr = OUTBUF;
        }
        else if (G != 1.0f) {
          for (float* outptr = OUTBUF; outptr < END;) {
            float V = *inptr++;
            if (V <= 0.0f)
              ;              //V = 0.0f;
  #ifdef __alpha
            else if (V <= 1e-6f && G > 1.0f)
              V = 0.0f;
  #endif
            else if (V < 1.0f)
              V = powf(V, G);
            else
              V = 1.0f + (V - 1.0f) * G;
            *outptr++ = V;
          }
          inptr = OUTBUF;
        }
        // Reverse the linear part:
        if (A != 1.0f || B) {
          if (A)
            A = 1 / A;
          else
            A = 1.0f;
          B = -B * A;
          for (float* outptr = OUTBUF; outptr < END;)
            *outptr++ = *inptr++ *A + B;
          inptr = OUTBUF;
        }
        // clamp
        if (white_clamp || black_clamp) {
          for (float* outptr = OUTBUF; outptr < END;) {
            float a = *inptr++;
            if (a < 0.0f && black_clamp)
              a = 0.0f;
            else if (a > 1.0f && white_clamp)
              a = 1.0f;
            *outptr++ = a;
          }
          inptr = OUTBUF;
        }
        else if (inptr != OUTBUF) {
          memcpy(OUTBUF, inptr, (END - OUTBUF) * sizeof(*OUTBUF));
        }
      }
    }
  }

  #include "DDImage/Knobs.h"

  void GradeIop::knobs(Knob_Callback f)
  {
    AColor_knob(f, blackpoint, IRange(-1, 1), "blackpoint");
    Tooltip(f, "This color is turned into black");
    AColor_knob(f, whitepoint, IRange(0, 4), "whitepoint");
    Tooltip(f, "This color is turned into white");
    AColor_knob(f, black, IRange(-1, 1), "black", "lift");
    Tooltip(f, "Black is turned into this color");
    AColor_knob(f, white, IRange(0, 4), "white", "gain");
    Tooltip(f, "White is turned into this color");
    AColor_knob(f, multiply, IRange(0, 4), "multiply");
    Tooltip(f, "Constant to multiply result by");
    AColor_knob(f, add, IRange(-1, 1), "add", "offset");
    Tooltip(f, "Constant to add to result (raises both black & white, unlike lift)");
    AColor_knob(f, gamma, IRange(.2, 5), "gamma");
    Tooltip(f, "Gamma correction applied to final result");
    Newline(f, "  ");
    Bool_knob(f, &reverse, "reverse");
    Tooltip(f, "Invert the math to undo the correction");
    Bool_knob(f, &black_clamp, "black_clamp", "black clamp");
    Tooltip(f, "Output that is less than zero is changed to zero");
    Bool_knob(f, &white_clamp, "white_clamp", "white clamp");
    Tooltip(f, "Output that is greater than 1 is changed to 1");
  }

  static Iop* build(Node* node)
  {
    return (new NukeWrapper(new GradeIop(node)))->channelsRGBoptionalAlpha();
  }
  const Iop::Description GradeIop::d(CLASS, "Color/Correct/Grade", build);

Once again, notes from top to bottom. Standard **HELP** definition, set of includes and namespace
declaration, followed by a static declaration of the **CLASS** name, used in a variety of places throughout the remainder of the Op. 
This can be useful when developing as it allows the class name to be easily altered during the development cycle - 
see the :ref:`intro-plugin-versioning` discussion for areas where this may come in handy.
 
We then declare our subclass of PixelIop, with member variables used to store values in use by the interface knobs. Often, depending on relevant coding
standards, you'll see such member variables prefixed with an underscore. We initialise these to 
their default values in the class constructor, and setup **in_channels** to define no extra dependancy for an output channel than the corresponding
input channel.

Next up we have our **_validate** implementation. Rather more interesting than the basic example, it figures out if the current interface settings
are going to make any difference to the image data on any of the 4 potential channels, if not calling **set_out_channels** with a **Mask_None**, 
otherwise with **Mask_All**. This has an
identical effect to using **disable()** in that NUKE will optimise this operator out of the tree. It then calls the default PixelIop implementation which
merges all the input's **IopInfo** structures into the output stream, in this case just copying through the single input's structure.
Subsequently the op sets its **IopInfo.black_outside** flag dependant on whether the Op itself is altering the 0 level in any of the image channels affected.
This has the effect of switching off repetition of the bounding box edge pixel out to the format size, in the circumstance the bbox is smaller than the format
and the tree has not had a black pixel padding inserted into the stream. It means that if you alter a comped element's black point it won't edge repeat over 
a larger background element. See the IopInfo detail in the :ref:`2d-iop-call-order-validate` section for more information on this mechanism.

The majority of the body of the operator is taken up with the **pixel_engine**, which is responsible for the colour correction operation being applied to the
pixels. We won't take you through line by line, instead we'll introduce the fundamental functions utilised within. Breaking down the actual colour correction
operation itself is an excellent first execise for learning to use a debugger, so if you're a little rusty there, do take this as a good opportunity to do so.
From this point forward we will assume that you are able to hook up a debugger and inspect variables, insert breakpoints and so on.

As in the Basic example **foreach** is used to iterate across all the passed in channels, however in this circumstance we then test for the current channel's
index (an associated integer identifier) and if it's above 3 (ie not **Chan_Red**, **Chan_Green**, or **Chan_Blue**) then we copy the data through untouched using the
**row.copy** method and moving on to the next channel in the set. **row.copy** is preferred in this circumstance over setting up a pointer on the source row
and a writeable pointer on the target row as in some circumstances it can simply switch the pointer over, avoiding the overhead of the data copy.

If the incoming channel is in the colour channel range then the engine sets up to do the calculation necessary to build the output row. It uses a shortcut for
the circumstance where the input row is all zero and the current settings don't involve changing the the 0 value. This helps speed up processing on the common
usecase of 3d elements with poor bbox settings and large regions of black. To do this it uses **row.is_zero(Channel)** to, as you would imagine, identify if the
row is all zero for the selected channel, and **row.erase(Channel)** to zero the selected channel.

As before there's the definition of the source and target pointers, and the decision tree associated with the various grade node settings. This is where the
image processing actually happens - if you're interested in the mechanics feel free to hook up that debugger. An interesting point to note is the use of 
**memcpy**s from input to output buffer. Note if you call **row.copy** instead, having already written other parts of your row you may end up blitzing already
calculated data.

After the engine functionality we have the **knobs(Knob_Callback)** call. As mentioned previously, this is where your user interface is defined. In this
circumstance we have a number of **AColor_knob** entries, which appear as a slider which can be split 4 ways plus defined by a colour swatch alongside the standard 
NUKE animation controls and a couple of **Bool_knob** entries which appear as checkboxes. As with the Basic example you can also see in our **build(Node*)**  method 
we use the **NukeWrapper** class to add channel, mix and mask knobs. We also call the **NukeWrapper.channelsRGBOptionalAlpha()** method to preset our 
*channel* knob to be RGB by default.
 
Each knob call specifies the type of knob, the variable in which to store its value, the name, optionally its label which appears on the interface (if you 
want it to be different to the name), in the case of slider based knobs a range to be shown, and a tooltip to be shown to the user on a hover event. 
There are a vast array of options for knob types, interface layout and so forth, as covered in the :ref:`knobs-knobtypes` and :ref:`knobs-knobflags` sections.
Note that for the variable passed to the **Bool_knob** entries we use the address of a bool variable, whilst for the **AColor_knob** entries we use an array pointer. The
array must have the correct number of entries in the array of the correct type.

As a final point of interest, note how the **Description** method here is passed a hierarchy for the plug-in name. This is an old technique for layout out
menus in NUKE. It is less flexible than the newer menu.py python approach, but can be used to insert Ops into pre-existing menu structures. Not recommended
for third party development.

Exercise: Build a Mult Node
---------------------------

Now lets take what we've covered so far and apply it. For this first exercise, take the Add.cpp source code in the NDK, get it building, then:

* Change its name to Mult, and ensure it can be created in the DAG.
* Amend the pixel_engine to multiply the image data by the user set value, rather than offset it. Use the inbuilt NUKE node 'Multiply' as a point of comparison to get this right.
* Amend the default value to be 1 rather than 0.
* Amend the help and tooltip text to reflect this.
* Amend the NukeWrapper call to set the channel knob to be RGB by default, rather than 'all.'
* Relax with a satisfying cup of tea.
