Image Objects

Image objects are how kernels interact with and process images within Blink. They are instantiated by declaring an image specification, and are usually the first things listed in a Blink kernel.

Image Specification

A kernel image specification takes the following form. The items specified in the angle brackets <> describe how the image will be used in the kernel.

Image<*ReadSpec*, *AccessPattern*, *EdgeMethod*> image;
ReadSpec
This describes how the data in the image can be accessed. The options are:
  • eRead Read-only access to the image data.

  • eWrite Write-only access to the image data.

  • eReadWrite Both read and write access to the image data.

Note

eReadWrite is not available when writing kernels for BlinkScript nodes in Nuke.

AccessPattern
This describes how the kernel will access pixels in the image within the iteration space.
  • eAccessPoint (Default). Access only the current position in the iteration space.

  • eAccessRanged1D Access a one-dimensional range of positions relative to the current position in the iteration space.

  • eAccessRanged2D Access a two-dimensional range of positions relative to the current position in the iteration space.

  • eAccessRandom Access any pixel in the iteration space using absolute pixel positions.

EdgeMethod
The edge method for an image defines the behaviour if a kernel function tries to access data outside the image bounds.
  • eEdgeNone (Default). Values are undefined outside the image bounds and no within-bounds checks will be done when you access the image. This is the most efficient access method to use when you do not require access outside the bounds, because of the lack of bounds checks.

  • eEdgeClamped The edge values will be repeated outside the image bounds.

  • eEdgeConstant Zero values will be returned outside the image bounds.

../../_images/clamped.png

Sampling outside the image bounds with eEdgeClamped. Red border denotes the original image edges.

../../_images/constant.png

Sampling outside the image bounds with eEdgeConstant. Red border denotes the original image edges.

Image Properties

The following properties are available from an Image. More detail on the different variable types can be found here.

image.kMin

float type. The minimum possible value for any component of the image data. Typically 0.0f.

image.kMax

float type. The maximum possible value for any component of the provided image data. Typically 1.0f.

image.kWhitePoint

float type. The minimum value for any component of the image data which is considered to be white. All values above this will be what are known as “super-whites”. A floating-point image will usually have a white point of 1.0f, though other values are also valid.

image.kComps

int type. The number of components in the image. For example in an RBGA image, this will be 4.

image.kClamps

bool type. Whether the image data should be clamped or not. For example, floating point data can take any value and therefore image.kClamps will be false.

image.bounds

The bounds of the image, used to find information such as the images width and height.

  • image.bounds.x1

    int type. The left-most horizontal value within the bounds.

  • image.bounds.x2

    int type. The right-most horizontal value within the bounds.

  • image.bounds.y1

    int type. The top-most vertical value within the bounds.

  • image.bounds.y2

    int type. The bottom-most vertical value within the bounds.

  • image.bounds.width()

    Returns int type. The width of the bounds. x2 - x1.

  • image.bounds.height()

    Returns int type. The height of the bounds. y2 - y1.

  • image.bounds.size()

    Returns int2 type. The width and height of the bounds as a vector.

Image variables can be used in the process() and init() kernel functions. For example:

kernel ImagePropertiesExample : ImageComputationKernel<eComponentWise>
{
  Image<eRead, eAccessRandom, eEdgeConstant> src;
  Image<eWrite> dst;

local:
  float halfImgWidth;
  float halfImgHeight;

  void init() {
    // Calculate half of the image bounds that will be used by all threads
    halfImgWidth = (float)src.bounds.width() / 2.f;
    halfImgHeight = (float)src.bounds.height() / 2.f;
  }

  void process(int3 pos) {
    // Check that our component position is within the source image's components
    if(pos.z < src.kComps) {
      dst() = src(pos.x * 2 - halfImgWidth, pos.y * 2 - halfImgHeight);
    }
    else {
      dst() = 0.f;
    }
  }
};

Using src.bounds in the init() method allows for expensive divide operations to be performed once and then used at every pixel position, optimising performance. Using src.kComps makes sure that if the number of output components is greater than the number of available input components, for example if this kernel is run on a four channel RGBA dst image with a three channel RGB src image, then the kernel will not try and access data that is not there.