Accessing Images

Accessing image values can only be performed inside the process(...) function. How the access behaves depends on both the kernel granularity and the image specification.

Accessing images is done by calling the () operator on the image object. This returns a reference to the pixel or component data at the specified position in the image. This will allow reading data from eRead images and writing data to eWrite images.

kernel ImageAccessExample : ImageComputationKernel<eComponentWise>
{
  Image<eRead> src;
  Image<eWrite> dst;

  void process()
  {
    // At the current position in the iteration space
    // the value of dst is set to the value of src
    dst() = src();
  }
};

In an eComponentWise kernel, or if a component is specified when accessing an image in an ePixelWise kernel (described below), a reference to a single component will be accessed. For kernels in Nuke BlinkScript nodes, this will be a float type.

In an ePixelWise kernel, a reference to a vector containing the values for all components at the position will be accessed. For kernels in Nuke BlinkScript nodes, this will be a floatn type, such as float2, float3 or float4.

Access Parameters

Depending on the image specification, different parameters will be available for the () accessor operator.

eAccessPoint Access

The simplest access method. Only allows for the current pixel position to be read from or written to. No parameters related to the image specification are needed.

floatn image();
eAccessRanged1D Access

The parameter is an integer offset along the chosen axis for the point in the image to be accessed. The offset is relative to the current position in the iteration space. More information can be found in the 1D Ranged Access section.

floatn image(int offset);
eAccessRanged2D Access

The parameters are integer offsets in the x- and y-axes respectively for the point in the image to be accessed. Both offsets are relative to the current position in the iteration space. More information can be found in the 2D Ranged Access section.

floatn image(int horizontalOffset, int verticalOffset);
eAccessRandom Access

The parameters are the x and y coordinates of the position in the image you wish to access. This will return the value at the absolute position (x, y) in the image, whatever the current position in the iteration space. More information can be found in the Random Access section.

floatn image(int x, int y);
Component Access

Finally, with a kernel granularity of ePixelWise you can specify an extra parameter which is the index, c, of the component you wish to access. As this returns a single component, it will always return a float.

/// eAccessPoint component access
float image(int component);

// eAccessRanged1D component access
float image(int offset, int component);

// eAccessRanged2D component access
float image(int horizontalOffset, int verticalOffset, int component);

// eAccessRandom component access
float image(int x, int y, int component);

Image Type Methods

When accessing images, it is useful for kernels to be able to work with images with different types or different numbers of channels. For example, to make a pixel-wise Saturation kernel work for both RGBA and RGB images, you would need to write two near-identical kernels - one version for the float4 RGBA case and one for the float3 RGB case. This duplication can be avoided using the following image type methods, which allow a kernel to be written once and be applied to images with different numbers of channels.

ValueType(image)

Provides the data type of the components in the given image. For example float, half or int. In BlinkScript nodes in Nuke, this will always be float.

SampleType(image)

Provides the data type for the pixels in the given image. For example, if ValueType(image) is float and there are three components in your image, SampleType(image) will be float3.

Warning

Care must be taken when writing kernels to handle varying numbers of input and output channels. Running a kernel with more outputs than inputs can result in out-of-bounds access errors, which can crash Nuke.

The following kernels demonstrate how these methods can be used in kernels.

Pixel-wise

kernel TypeMethodsExample : ImageComputationKernel<ePixelWise>
{
  Image<eRead> src;
  Image<eWrite> dst;

  void process()
  {
    // ValueType can be initialised from an input image component.
    int component = 0;
    ValueType(src) value = src(component);

    //SampleType can be initialised from an input image.
    SampleType(src) sample = src();

    // SampleType variables can be assigned to output images.
    SampleType(dst) sampleOut = SampleType(dst)(1.0f);
    dst() = sampleOut;

    // ValueType variables can be assigned to output images.
    ValueType(dst) valueOut = ValueType(dst)(1.0f);
    // Implicit conversion from type to typeN
    dst() = valueOut;
  }
};

Component-wise

kernel TypeMethodsExample : ImageComputationKernel<eComponentWise>
{
  Image<eRead> src;
  Image<eWrite> dst;

  void process()
  {
    // ValueType variables can be initialised from an input image component.
    ValueType(src) value = src();

    // SampleType variables can be initialised from an input image component.
    SampleType(src) sample = src();

    // ValueType variables can be assigned to output images.
    ValueType(dst) valueOut = ValueType(dst)(1.0f);
    dst() = valueOut;

    // INVALID: Cannot assign SampleType variables to component-wise outputs.
    // SampleType(dst) sampleOut = SampleType(dst)(1.0f);
    // No conversion from typeN to type
    // dst() = sampleOut;
  }
};

Bilinear Interpolation

The bilinear() function can be used to access pixels or components at non-integer positions within an Image. The function performs bilinear interpolation to estimate the appropriate value from the four pixels or components nearest to the requested position. In a pixel-wise kernel, this will return a floatn value, while in a component-wise kernel the value will be float.

floatn bilinear(Image img1, float x, float y)

Within a pixel-wise kernel you can also specify a component to perform bilinear interpolation at position (x, y) on a single specified component.

float bilinear(Image img1, float x, float y, int component)

Resize kernel example

The Resize kernel demonstrates using bilinear to sample the source image.

// Copyright (c) 2024 The Foundry Visionmongers Ltd.  All Rights Reserved.
kernel Resize : ImageComputationKernel<eComponentWise>
{
  Image<eRead, eAccessRandom, eEdgeConstant> src; // Output will be black outside the original image
  Image<eWrite, eAccessPoint> dst;

param:
  float externalScale;
  bool horizontal;

local:
  float scale;

  void define()
  {
    defineParam(externalScale, "Scale", 0.5f);
    defineParam(horizontal, "Horizontal", true);
  }

  void init()
  {
    scale = 1.0f / externalScale;  // invert the scale as we back-map from dst to src
  }

  void process(int2 pos)
  {
    // Work out the scaled src position.
    const float xPos = (horizontal ? (float)pos.x * scale : (float)pos.x);
    const float yPos = (horizontal ? pos.y : (float)pos.y * scale);

    // Use a bilinear filter to get the value at that src position.
    dst() = bilinear(src, xPos, yPos);    
  }
};