Creating and applying custom filters

CMSoft Stereoscopic Picture Editor allows advanced users to create their own custom filters to apply in 3D pictures. All filters should use the .CLFilter extension and be copied to the application startup folder.

1. Creating a new custom filter

In order to create a new custom filter, it is necessary to create a text file with .CLFilter extension in the application startup folder. The name of the file will be the name which appears listed under the Filters menu.

The language to be used is C (more specifically, OpenCL C99). Once the file is created, the user should input the code for the desired filter. If you need information about OpenCL C99 and OpenCLTemplate please visit the tutorial.

2. Built-in variables

There are 4 built-in variables that are already declared in the environment:

float4 P[7][7] – RGBA values of pixels obtained starting from current pixel;
float4 outP – RGBA values of pixel which will be written in output image. This pixel will replace P[3][3] and, initially, outP = P[3][3];

float  xCoord, yCoord – image coordinates, from 0 to 1, of current pixel in width (horizontally) and height (vertically) respectively.

These variables can’t be declared again as they are already declared in the header string. Should the user need other variables, those should be declared.

The values of P correspond to the pixels of the original image in RGBA order. We recommend being careful about the alpha component because the image will not appear if alpha is set to zero.

In the pixel window, the value P[3][3] corresponds to the central element:

P[0][0] P[0][1] P[0][2] P[0][3] P[0][4] P[0][5] P[0][6]
P[1][0] P[1][1] P[1][2] P[1][3] P[1][4] P[1][5] P[1][6]
P[2][0] P[2][1] P[2][2] P[2][3] P[2][4] P[2][5] P[2][6]
P[3][0] P[3][1] P[3][2] P[3][3] P[3][4] P[3][5] P[3][6]
P[4][0] P[4][1] P[4][2] P[4][3] P[4][4] P[4][5] P[4][6]
P[5][0] P[5][1] P[5][2] P[5][3] P[5][4] P[5][5] P[5][6]
P[6][0] P[6][1] P[6][2] P[6][3] P[6][4] P[6][5] P[6][6]

3. Filter examples

3.1 Simple edge detector

A simple edge detection algorithm can be implemented as follows: subtract from 2x the value of the central pixel the values of its 2 closest horizontal neighbors and store the result back in the central pixel. This can be implemented as follows:

outP = fabs(2*P[3][3]-P[3][2]-P[3][4])*20.0f;

Remember that these are vector operations and that outP.w (the alpha component) will come out zero if the picture is not transparent since P[3][3].w = P[3][2].w = P[3][4].w = 1.0f, which implies 2*P[3][3].w – P[3][2].w – P[3][4].w = 0. Function fabs() is used to compute absolute value of the result.

We then pick the greatest value stored in outP since we want a monochromatic border. We use a scalar command to perform this operation:
outP.x = fmax(fmax(outP.x,outP.y),outP.z);

 

Notice that we store this maximum in the X component of outP (outP.x).

Then, we copy the value to outP’s RGB components:

outP.y = outP.x; outP.z = outP.x;

Finally, since we want borders to be black and the background to be white, we invert outP’s color:

outP = (float4)(1.0f, 1.0f, 1.0f, 1.0f) – outP;

If you want white borders and black background, swap the last line with:

outP.w = 1.0f;

because, as explained, outP.w was zero initially.

3.2 Simple flare

Let’s create a simple flare which will be centered at the 30% if the image width and 30% of image height.

First, we compute an intensity value so that exp(-intens) decreases as we move away from [0.3, 0.3] image coordinate:

float intens = (xCoord-0.3f)*(xCoord-0.3f) + (yCoord-0.2f)*(yCoord-0.2f);

Notice that we need to declare the non-built-in intens variable.

Remembering that outP = P[3][3] initially, we modify outP’s red and green components using this intensity value:

outP.x *= 0.9f + 0.6f*exp(-intens*10.0f);
outP.y *= 0.9f + 0.4f*exp(-intens*20.0f);

Appendix: OpenCL C99 header, footer and host code used to apply filters

Assuming that the string inside the filter file is CoreCode and that the file name is kernelName, the following function returns the kernel code which will be compiled using OpenCL:

public static string CreateKernelCode(string kernelName, string CoreCode)
{

string kernelVoid = "__kernel void " + kernelName.Replace(" ", "").ToLower() + " ";string header = kernelVoid + @"
(__read_only image2d_t img1,

__write_only image2d_t img2)

{

const sampler_t smp = CLK_NORMALIZED_COORDS_FALSE | //Natural coordinates

CLK_ADDRESS_CLAMP | //Clamp to zeros

CLK_FILTER_NEAREST; //Don't interpolate

 

int x0 = get_global_id(0);

int y0 = get_global_id(1);

 

int2 coord = (int2)(x0+3, y0+3);

uint4 val = (uint4)(0,0,0,0);

for (int i = 0; i < 7; i++)

{

for (int j = 0; j < 7; j++)

{

coord = (int2)(x0+i, y0+j);

val += read_imageui(img1, smp, coord);

}

}

uint4 pixel;

float4 P[7][7];

for (int i = 0; i < 7; i++)

{

for (int j = 0; j < 7; j++)

{

coord = (int2)(x0+i, y0+j);

pixel = read_imageui(img1, smp, coord);

//Converts to RGBA format

P[i][j] = (float4)((float)pixel.z, (float)pixel.y, (float)pixel.x, (float)pixel.w);

P[i][j] *= 0.00392156862745098f; //Normalize to 0-1

}

}

float4 outP = P[2][2];

float xCoord = (float)x0/(float)get_global_size(0);

float yCoord = (float)y0/(float)get_global_size(1);

";
//User manipulates P[][].

//User has to know that outP is the output pixel

string footer = @"
coord.x = x0+3;coord.y = y0+3;

outP *= 255.0f;

//outputs C# BGRA color format from RGBA

uint4 writeValue = (uint4)((uint)outP.z, (uint)outP.y, (uint)outP.x, (uint)outP.w);

 

//Some GPUs appear to have problems with Clamp function in OpenCL

writeValue.x = writeValue.x < 0 ? 0 : writeValue.x;

writeValue.x = writeValue.x > 255 ? 255 : writeValue.x;

writeValue.y = writeValue.y < 0 ? 0 : writeValue.y;

writeValue.y = writeValue.y > 255 ? 255 : writeValue.y;

writeValue.z = writeValue.z < 0 ? 0 : writeValue.z;

writeValue.z = writeValue.z > 255 ? 255 : writeValue.z;

writeValue.w = writeValue.w < 0 ? 0 : writeValue.w;

writeValue.w = writeValue.w > 255 ? 255 : writeValue.w;

 

write_imageui(img2, coord, writeValue);

}

";

return header + CoreCode + footer;
}

When the filter is called, it is then executed in the OpenCL device using the following OpenCLTemplate commands:

CLCalc.Program.Image2D CLImgSrc0 = new CLCalc.Program.Image2D(bmps[0]);
CLCalc.Program.Image2D CLImgDst0 = new CLCalc.Program.Image2D(bmps[0]);
CLCalc.Program.MemoryObject[] args0 = new CLCalc.Program.MemoryObject[] { CLImgSrc0, CLImgDst0 };
CLFilters[id].FilterKernel.Execute(args0, new int[] { bmps[0].Width - 7, bmps[0].Height - 7 });

Where bmps[0] is the original bitmap, CLImgSrc0 is its copy in Device memory and CLImgDst0 is the new image created in Device memory.

Leave a Reply

Your email address will not be published. Required fields are marked *