Basic Image Calls

_validate (Region of Definition)

void _validate(bool)

_validate() is called by NUKE on your Op to “validate” it. Validation consists of:

  1. calling, in turn, validate() on all the inputs that the Op is using in its current configuration (it is possible to omit validating inputs that are not used - for example, a mask input controlled

    by a Bool_knob that is off and is not used at all, does not need to be validated).

  2. setting up Op-type-dependent data. For the case of Iops, validate should set the “IopInfo”, which is stored in the info_ member on Iop. For Deep Ops, validate should set the DeepInfo.

The IopInfo contains various fields:

  • Itself, it is a sub-class of Box. This is a simple bounding box. It represents the area in which the Op has pixels and includes any overscan that is available.
  • Two formats: one is the “fullSizeFormat”, and the other is the regular “format”. In non-proxy mode these are the same, but in proxy mode the original, unscaled format is stored in “fullSizeFormat”, and the effective format as used by proxying is stored in “format.” The format is the region to which the bounding box is clipped when displayed in the Viewer.
  • channels - These are the channels for which pixel data is defined. Attempts to fetch channels from Iops which do not have these channels result in zeroes.
  • first_frame/last_frame - These represent the frame range for which the Iop is defined. For example, a Read node fills these in to the temporal range of the source material it is pointed at.
  • black_outside - By default, NUKE edge-extends Iops when you attempt to access them outside their bounding box. This is not always desirable, so Ops can set ‘black_outside’. This is slightly confusing in that rather than changing the default extension behavior, it instead means that the pixel data has been padded with an extra layer of black pixels (and the bbox padded similarly), and therefore for use of the bbox as numbers it strips out that extra padding.
  • ydirection - This indicates the preferred y access order. A negative value indicates that the preferred access order is from the top (high y) to the bottom (low y). A positive value indicates that the preferred access order is the other way round.

_validate is called on the main thread. It should avoid doing excessive calculations, as it will block the UI during its call. In particular, it should avoid calculating the image on the input, as this can take arbitrarily long.

The Iop has another field that can be set in _validate(), namely “out_channels_”. This defaults to Mask_All and has no effect. If it’s set to something smaller, then NUKE can, as an optimization, skip calling _request and engine() on this Iop entirely, and go straight to its input Iop (by default input(0), but this can be changed by setting raw_channels_from). This is not guaranteed, and implementations of engine() should still be able to deal with being called for channels not in out_channels_.

During _validate you can assume that the Op has been built for the right frame and view. outputContext() is valid and the knobs have been stored via knobs().

_request (region of interest)

void _request(int x, int y, int r, int t, ChannelMask, int count)

_request() is called by NUKE on your Iop to indicate the region and channels of interest. In turn your Iop should indicate the region and channels of interest by calling request() on its inputs as appropriate. It is important where possible to keep the region of interest down to the minimum necessary, as NUKE potentially calculates and caches the full region of interest.

In a complex tree, NUKE might call _request on your Iop multiple times - each time it passes the cumulative region and channels of interest.

_request() is usually handled in the main (UI) thread, and, like _validate(), should avoid taking too long.

The parameter count is a hint to the caching, and indicates how many overlapping requests have been made. When calling request() on your inputs, you can specify a value greater than one to force it to cache the data on this input.

engine (image processing)

void engine(int y, int l, int r, ChannelMask channels, Row& row)

engine() is called by NUKE on your Iop as the function that actually processes image data. This is called on the worker threads, and therefore should be thread-safe against itself and not make a large number of calls. In general, it should not call functions on knobs, particularly those that set or get values, as these calls are not thread-safe and may result in crashing. Any calculations based on knob values it requires should have already taken place in _validate, and result in fields within the Iop being set that engine() can later refer to.

It is passed in coordinates representing a segment of a scanline, and a set of channels and a Row is passed by reference. It is supposed to set up the Row so that all the channels are defined in the range x ... r. This value will have been set previously.

A trivial implementation of engine is as follows:

void engine(int, int, int, ChannelMask channels, Row& row) {
row.erase(channels);

}

This blanks all the channels. Blanking must be done explicitly, as the state of Row is not defined to be empty initially. Another example that fills in all the pixels with the value 1 is as follows:

void engine(int y, int l, int r, ChannelMask channels, Row& row) {

foreach(z, channels) {

float* out = row.writable(z); for (int x = l ; x < r ; x++) {

out[x] = 1.0f;

}

}

}

We see here the use of writable(Channel), which obtains a pointer to the Row’s internal buffer for that channel, which the calling code can write into. writable(Channel) is based so that it is valid between the left and right extents of the Row (specified in its constructor, which has been run by the code that calls engine). It is not always valid to simply deference writable(Channel), as x may be higher than 0.

engine() can also fetch data from the input. For example, a simple passthrough operator would have this engine:

void engine(int y, int l, int r, ChannelMask channels, Row& row) {
row.get(input0(), y, l, r, channels);

}

If it is possible to write your operator as a simple in-place modification of its input, we recommend doing so, as this leads to more efficient memory use. For example, a simple gain would end up being:

void engine(int y, int l, int r, ChannelMask channels, Row& row) {

row.get(input0(), y, l, r, channels); foreach(z, channels) {

if (z == Chan_Red || z == Chan_Blue || z == Chan_Green) {

const float* in = row[z]; float* out = row.writable(z); for (int x = l ; x < r ; x++) {

out[x] = 1.0f;

}

}

}

}

For more details about how to fetch image data in engine, please see [section]. Various sub-classes of and helper classes for Iop also exist that are there primarily to remove boilerplate within engine and add extra functionality.

_open

_open is called immediately prior to the first engine() call on an Iop, after it has been _request()ed. Like engine(), it is called on a worker thread, and therefore can take any amount of time without causing the UI to block excessively. Calls to open() are locked so that only one will happen, and this will be complete by the time other calls to engine() begin.

If you have Iop that has to process its entire input, open() would be a good place to do that processing.

Unlike validate/request/engine, _open does not need to and should not recursively call its inputs.

close

_close is called some time after rendering has finished (for the moment). This cannot be relied upon to always happen when rendering finishes.

If NUKE decides that it needs to do more work, it will re-call open().

Unlike validate/request/engine, _close does not need to, and should not, recursively call its inputs.

Table Of Contents