Coding for Map Lab

Create simple CPU filter

In this example we are going to develop a simple filter for Map Lab that draws smooth vertical stripes to the input image.

 

 

Step 1

Create a new class file called ColorStripesFilter.cs.

The name doesn't matter, you can name it what ever you like.

 

Step 2

Create the ColorStripesFilter class and extend it from TextureFilter. The TextureFilter class is the base class for all filters running in Map Lab core.

[MapLabExtension]
public class ColorStripesFilter : TextureFilter 
{
    public ColorStripesFilter()
    {

    }
}

Every external defined extension has to be decorated with the MapLabExtension attribute to ensure it gets automatically found by the extension manager and doen't have to be manually registered.

Step 3

Importing the needed namespaces.

using ch.sycoforge.Imaging;
using ch.sycoforge.Imaging.Unity;
using ch.sycoforge.MapLab;
using ch.sycoforge.Types;
using ch.sycoforge.Util.Attributes;
using System.Runtime.Serialization;

 

Step 4

Defining the exposed properties.

For this example we need to expose three properties to control the filter.

  1.  A float property named Amplitude to control the blending with the input image.
  2. A float property named Frequency to control the amount and width of stripes rendered.
  3. A FloatColor property named StripeColor to control the color of the rendered stripes.

Each property that you want to have exposed has to be decorated with the Editable attribute. Each property backing field should be marked as DataMember to ensure proper DataContract serialization.

Don't mark the exposed property itself as DataMember, because this way you cannot rename/refactor it anymore after the release. Any kind of change to the public names will break the serialization contract.

The most of the Unity internal types such as Color, Vector2, Vector3, etc have their mapped implementations in the Map Lab framework. This mapping ensures proper serialization and portability to standalone versions.

...
[MapLabExtension]
public class ColorStripesFilter : TextureFilter 
{
    //------------------------------
    // Properties
    //------------------------------
    [Editable(Min = 0f, Max = 1f, Tooltip = "The amplitude of the sine function")]
    public float Amplitude
    {
        get { return amplitude; }
        set { amplitude = value; }
    }

    [Editable(Min = 0f, Max = 10f, Tooltip = "The frequency of the sine function")]
    public float Frequency
    {
        get { return frequency; }
        set { frequency = value; }
    }

    [Editable(Tooltip = "The color of the stripes")]
    public FloatColor StripeColor
    {
        get { return stripeColor; }
        set { stripeColor = value; }
    }

    //------------------------------
    // Fields
    //------------------------------

    [DataMember]
    private float amplitude;

    [DataMember]
    private float frequency;

    [DataMember]
    private FloatColor stripeColor;

    //------------------------------
    // Constructor
    //------------------------------
    public ColorStripesFilter()
    {

    }
}

 

Step 5

Filter initialization using the constructor.

It's very important to initialize your filter with reasonable default values, this way the person using your filter has an appropriate result when pluging in your filter.

 

    public ColorStripesFilter()
    {
        this.filterName = "Color Stripes";
        this.amplitude = 1.0f;
        this.frequency = 1.0f;
        this.stripeColor = FloatColor.red;

        this.compatibility = FilterInfo.DeviceCompatibility.CPU;
    }

 

Step 6

Implementing the actual filter.

Every filter implemented in C# must override the ProcessPixel(...) method. This method gets called for every pixel in the input image in so called passes.

The inner structure of this method should always be the same:

  1. Read pixel from input image
  2. Process pixel in some kind
  3. Write pixel to output image

The pass variable defines the current pass of  the method and x, y are defining its actual position.

Note that if you are not explicitly disable multi-threading in a filter, the ProcessPixel method gets automatically parallelized and concurrently executed by the Map Lab core. So, you have no control over the execution order.

    protected override void ProcessPixel(ProcessPass pass, int x, int y)
    {
        base.ProcessPixel(pass, x, y);

        // Get pixel from input texture
        FloatColor color = input.GetPixel(x, y);

        // Calculate blend factor
        float f = (1.0f + FloatMath.Sin(x * frequency)) * 0.5f * amplitude;

        // Blend final pixel color
        FloatColor result = FloatColor.Lerp(color, stripeColor, f);

        // Write pixel to output texture
        output.SetPixel(x, y, result);
    }

 

Step 7

Testing the filter.

As this simple example is for CPU only, make sure you don't have any OpenCL devices activated in the System Settings.

When now opening the Add-Filter dialog in Map Lab, your custom filter should be listed under the Default category.

 

Full Code:

using ch.sycoforge.Imaging;
using ch.sycoforge.Imaging.Unity;
using ch.sycoforge.MapLab;
using ch.sycoforge.Types;
using ch.sycoforge.Util.Attributes;
using System.Runtime.Serialization;

[MapLabExtension]
public class ColorStripesFilter : TextureFilter 
{
    //------------------------------
    // Properties
    //------------------------------
    [Editable(Min = 0f, Max = 1f, Tooltip = "The amplitude of the sine function")]
    public float Amplitude
    {
        get { return amplitude; }
        set { amplitude = value; }
    }

    [Editable(Min = 0f, Max = 10f, Tooltip = "The frequency of the sine function")]
    public float Frequency
    {
        get { return frequency; }
        set { frequency = value; }
    }

    [Editable(Tooltip = "The color of the stripes")]
    public FloatColor StripeColor
    {
        get { return stripeColor; }
        set { stripeColor = value; }
    }

    //------------------------------
    // Fields
    //------------------------------

    [DataMember]
    private float amplitude;

    [DataMember]
    private float frequency;

    [DataMember]
    private FloatColor stripeColor;

    //------------------------------
    // Constructor
    //------------------------------
    public ColorStripesFilter()
    {
        this.filterName = "Color Stripes";
        this.amplitude = 1.0f;
        this.frequency = 1.0f;
        this.stripeColor = FloatColor.red;

        this.compatibility = FilterInfo.DeviceCompatibility.CPU;
    }

    //------------------------------
    // Methods
    //------------------------------

    protected override void ProcessPixel(ProcessPass pass, int x, int y)
    {
        base.ProcessPixel(pass, x, y);

        // Get pixel from input texture
        FloatColor color = input.GetPixel(x, y);

        // Calculate blend factor
        float f = (1.0f + FloatMath.Sin(x * frequency)) * 0.5f * amplitude;

        // Blend final pixel color
        FloatColor result = FloatColor.Lerp(color, stripeColor, f);

        // Write pixel to output texture
        output.SetPixel(x, y, result);
    }
}