DrawIop: Generating Images from Scratch
========================================

The DrawIop class provides a further specialisation of PixelIop for classes which create a shape to use as a mask for
drawing some pixels on top of the output buffer. DrawIop 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.

It is intended for use by generators of monochrome image data (but not by readers of
data off disk, for that please see :ref:`2d-readerswriters`). For example, the plugins 
"Grid", "Noise", "Radial", "Ramp", "Rectangle" and "Text" that are provided with NUKE are DrawIops. Note that "Roto" and
"RotoPaint" are not based off DrawIop, and thus do not inherit functionality such as ramp drawing. 

* Intended for use by monochrome image data generating Ops.
* Adds colour and ramp functionality to the underlying alogorithm.

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

DrawIops must implement the following function:

.. cpp:function:: bool DrawIop::draw_engine(int y, int x, int r, float* buffer)

 This should fill in buffer[x] to buffer[r] with a mask, which will be combined with the colour, the ramp, the mask input, 
 in the DrawIop's implementation of pixel_engine.   Pixels should be set to 1 for full drawing, 0, for no drawing, and values
 in between for partial. Unlike other engine functions in the iop family, DrawIop returns a bool. If the buffer would be filled
 entirely with zeroes, you can return false, and then it can quickly copy the background input. Otherwise the function should return true.

DrawIop derived Ops can optionally choose to overide the default _validate() provided by DrawIop.

.. cpp:function:: void DrawIop::_validate(bool for_real)

 A DrawIop's implementation of _validate() should call DrawIop::_validate() passing the same boolean *for_real*. If your Op's draw size
 is limited to a smaller size than that of the format it should pass the new bounding box, ie DrawIop::_validate(for_real, x, y, r, t).

As with all Ops, DrawIops must additionally implement Description, and may optionally implement knobs() to provide
user interface elements. There are a number of common controls offered by the DrawIop class, which need to be specifically called in your
knobs function via **input_knobs(Knob_Callback)** and **output_knobs(Knob_Callback, bool ramp=true)**. input_knobs provides a channel selector,
replace incoming image checkbox, and an invert checkbox. output_knobs provides colourisation, and optionally ramp/gradient tools. These replace
the NukeWrapper functionality which can be used by other Iop types.

Building the Rectangle Node
---------------------------

We'll now take a look a couple of examples of DrawIops, building on what we saw in the PixelIop examples. We'll only cover the salient functions, 
as opposed to break down the full listing as done in the previous examples, so do read the PixelIop section if you haven't already. The full source 
code for both these operators is included in the NDK, so feel free to refer to the supporting code listed there.

First up, the rectangle node. As you might expect from the name, this draws a rectangle into the image, on top of the pre-existing image data. The rectangle
has a softness control, plus colourisation and gradient/ramp options.

Interesting sections of the code include the header:

.. code-block:: c

  class RectangleIop : public DrawIop
  {
    // bounding box of the final rectangle
    double x, y, r, t;
    // softness of the rectangle in horizontal and vertical direction
    double soft_x, soft_y;
  public:
    void _validate(bool);
    bool draw_engine(int y, int x, int r, float* buffer);
    // make sure that all members are initialised
    RectangleIop(Node* node) : DrawIop(node)
    {
      x = y = r = t = 0;
      soft_x = soft_y = 0;
    }
    virtual void knobs(Knob_Callback);

    const char* Class() const { return CLASS; }
    const char* node_help() const { return HELP; }
    static const Iop::Description d;
  };

The _validate function:

.. code-block:: c

  void RectangleIop::_validate(bool for_real)
  {
    // don't bother calling the engine in degenerate cases
    if (x >= r || y >= t) {
      set_out_channels(Mask_None);
      copy_info();
      return;
    }
    set_out_channels(Mask_All);
    // make sure that we get enough pixels to build our rectangle
    DrawIop::_validate(for_real,
                       int(floor(x)),
                       int(floor(y)),
                       int(ceil(r)),
                       int(ceil(t)));
  }

The knobs interface call:

.. code-block:: c

  void RectangleIop::knobs(Knob_Callback f)
  {

    // allow DrawIop to add its own knobs to handle input
    input_knobs(f);

    // this knob provides controls for the position and size of our rectangle.
    // It also manages the rectangular handle box in all connected viewers.
    BBox_knob(f, &x, "area");

    // This knob manages user input for the rectangles edge softness
    WH_knob(f, &soft_x, "softness");

    // allow DrawIop to add its own knobs to handle output
    output_knobs(f);
  }

And finally the draw_engine():

.. code-block:: c

  bool RectangleIop::draw_engine(int Y, int X, int R, float* buffer)
  {
    // lets see if there is anything to draw at all
    if (Y < (int)floor(y))
      return false;
    if (Y >= (int)ceil(t))
      return false;
    // calculate the vertical multiplier:
      float m = 1;
    if ( soft_y >= 0.0 ) {
      // if this line is within the softned falloff, change the multiplier
      if (Y < y + soft_y) {
        float T = (Y + 1 - y) / (soft_y + 1);
        if (T < 1)
          m *= (3 - 2 * T) * T * T;
      }
      // same for the 'upper' lines in the image (bottom left is 0,0)
      if (Y > t - soft_y - 1) {
        float T = (t - Y) / (soft_y + 1);
        if (T < 1)
          m *= (3 - 2 * T) * T * T;
      }
    }
    // now fill the line with data
    for (; X < R; X++) {
      float m1 = m;
      // first, calculate the multiplier for the left side falloff
      if (X + 1 <= x || X >= r)
        m1 = 0;
      else if (X < x + soft_x && soft_x >= 0) {
        float T = (X + 1 - x) / (soft_x + 1);
        if (T < 1)
          m1 *= (3 - 2 * T) * T * T;
      }
      // now do the same for the right side falloff
      if (X > r - soft_x - 1 && soft_x >= 0) {
        float T = (r - X) / (soft_x + 1);
        if (T < 1)
          m1 *= (3 - 2 * T) * T * T;
      }
      // finally, we can fill the buffer with the calculated value
      buffer[X] = m1;
    }
    return true;
  }

So the constructor and the **knobs** call initialises a set of storage variables and creates a pair of rectangle specific knobs referencing them respectively. Both the 
**BBox_knob** and the **WH_knob** types are interesting because they are both spatial and aware of proxy scaling. This means that they will automatically update their
values dependant on both the current proxy and active downres settings - to see this ensure you have a visible rectangle with some softness set and switch your viewer into 
proxy mode. Note how the viewer overlay handles and the rectangle itself are updated to be drawn at the correct place to take this scaling into account.
The **knobs** call additionally uses the **input_knobs** and **output_knobs** calls mentioned previously to construct a full DrawIop derived interface.

In **_validate** the Op sets the out_channels to Mask_None (ie telling NUKE to ignore the op) in the situations where the bottom of the rectangle is above the top, or
where the left of the rectangle is further across than the right. It also calls the base **DrawIop::_validate** passing the smaller region which it generates - this way
NUKE knows not to both going to the extent of passing the data external to this region through the Rectangle op. Note the use of the floor and ceil to truncate the float
values to the nearest int value, ensuring the passed integer area is always slightly bigger in the circumstances where the rectangle doesn't sit on integer values. Given
that the softness value isn't taken into account here, this also means that the softness implementation must always blur the edges inside the rectangular area, since
we're never going to be passed anything outside it. Also important is the initial include of DDImage/DDMath.h, rather than <math.h>, to give cross platform, portable
math functions.

Finally in the **draw_engine** we do the real work. Again, most of the engine call is focussed around the intricacies of its particular task, so rather than discuss it in
extensive and rather boring detail, we'll just take a look  at some of the new functions utilised. If you want to check out the minutae of the drawing function, 
fire it up in a debugger and step through.
As before, we initially handle degenerate cases where we can simply return, however in this case we return false to allow rapid copying through of input data, as opposed
to initialising the buffer to zeros. Subsequent drawing code attempts to handle common fast circumstances, before handling the more esoteric and slow configurations.
Vertical softness is calculated on a per row basis, whilst horizontal softness is calculated inside the row loop as required.

Building the Noise Node
-----------------------

Next up, the Noise node. For this example, you'll want to open up the full source code listing from the NDK in your IDE of choice, set up header locations and follow along,
as it gets rather interrelated.

Points of interest here include the use of the NDK provided **noise.h** functions, as well as the utility **Matrix** and **Vector** classes for handling, in this case 4x4
and 3x1 maths for us.

The engine relies on **fBm** and **turbulence** as encompassed by the **noise.h** include. These expect x y and z values to define size, and plane, which are in turn
constructed by generating a 4x4 transform matrix based on user interface controls. Slightly different to the previous example, the row loops are included down the end of
the if tree, since in this case we can avoid evaluating the decisions on every pixel, and optimise by doing the majority on a row by row basis.

As for the **knobs** call, there are a couple of items we like haven't come across before - enumeration knobs and obsolete knobs. Both of these are discussed in more 
detail in the relevant parts in the :ref:`knobs-knobtypes`, and :ref:`intro-plugin-versioning` sections. Most of the other knobs are functionally and visually
similar to other knobs we're already come across, however the **Enumeration_knob** provides a drop down list. The current setting is stored as an int, so it's generally 
wise to setup the static char* array alongside an enumeration defining the entries themselves up at the top of your class, as in the following lines of this example.

.. code-block:: c

  enum { FBM, TURBULENCE };
  const char* const types[] = {
    "fBm", "turbulence", 0
  };

The **Obsolete_knob** provides a method for allowing you to change the interface of your operator whilst maintaining backwards compatibility. As you can see in this example, 
the initial definition of the 'lacunarity' knob had a capital 'L,' which is generally regarded as bad practice in the world of NUKE. This was corrected, but the new knob
had a spelling error - 'lucanarity.' To correct this yet another knob was defined, spelt and capitalised correctly. At each ste, instead of simply removing the previous
incorrect knob, which would have meant that scripts saved with the old version would have not loaded correctly, an **Obsolete_knob** entry was created with the incorrect
name. NUKE would then, when encountering a script saved which such a named knob, call the script defined in this obsolete entry. In both these cases the script simply updates
the correctly named node with **$value** - the script variable holding the setting found in the script. Note that both entries point to the correct knob, as opposed to chaining
from the first obsolete, to the second and then to the correct.

Exercise: Build a Fractal Node
------------------------------

For the second exercise we're going to look at building your first Op from the ground up, and without a prebuilt NUKE node as a point of reference. 

* Create a new DrawIop called Fractal, fill in the required virtuals and get it building.
* Research fractal drawing algorithms (noting that a DrawIop can only fill in a single luminance buffer and not a colour one), and implement draw_engine, _validate and knobs so as to draw the selected algorithm and provide controls dependant on its underlying variables and expected artist requirements. The wikipedia pages on fractals, the mandelbrot set and fractal software offer a selection of useful links and algorithm discussions which might serve as a good starting point.
* Try a few optimisations to get the drawing running as fast as possible.
* If you have clearance to, compile and upload the binaries and/or source code to Nukepedia.
