Sign in to follow this  
toe_head2001

Mini Tutorials/Tips for Plugin Developers

Recommended Posts

Here are a few Mini Tutorials and Tips that are hopefully helpful for new plugin developers. All of these are geared towards effects in Visual Studio. If you find mistakes, please point them out.
 
Dynamic values for IndirectUI sliders
Sometimes you have a parameter for size, and you're not sure what the Upper Bound (Maximum vale) should be set to. Is 1000px enough? What if some one has a 5000px wide selection/canvas?

Spoiler

I will demonstrate how to get a good value based on the current selection (remember, if there's no selection, the whole canvas is used).
 
First, here's our IndirectUI slider. The default value is hard-code at 100, and the max value is hard-coded at 1000. 


protected override PropertyCollection OnCreatePropertyCollection()
{
    List<Property> props = new List<Property>();
    props.Add(new Int32Property(PropertyNames.Amount1, 100, 1, 1000));

    return new PropertyCollection(props);
}

 Let's create an Size object that represents the Height and Width of the selection. 


protected override PropertyCollection OnCreatePropertyCollection()
{
    Size selSize = EnvironmentParameters.GetSelection(EnvironmentParameters.SourceSurface.Bounds).GetBoundsInt().Size; // Gets the selection dimensions

    List<Property> props = new List<Property>();
    props.Add(new Int32Property(PropertyNames.Amount1, 100, 1, 1000));

    return new PropertyCollection(props);
}

Now we'll want to determine if the Height or Width is the shorter value of the two, and we'll use that for the Upper Bound on the slider. We'll make an int variable to hold the value.


protected override PropertyCollection OnCreatePropertyCollection()
{
    Size selSize = EnvironmentParameters.GetSelection(EnvironmentParameters.SourceSurface.Bounds).GetBoundsInt().Size;
    int upperBound = Math.Min(selSize.Width, selSize.Height); // use the smallest of the two dimensions

    List<Property> props = new List<Property>();
    props.Add(new Int32Property(PropertyNames.Amount1, 100, 1, upperBound)); // Use the upperBound variable here, in place of the 1000

    return new PropertyCollection(props);
}

How about the default value? Let's set that to half the value of the Upper Bound.


protected override PropertyCollection OnCreatePropertyCollection()
{
    Size selSize = EnvironmentParameters.GetSelection(EnvironmentParameters.SourceSurface.Bounds).GetBoundsInt().Size;
    int upperBound = Math.Min(selSize.Width, selSize.Height);
    int default = upperBound / 2; // simply divide upperBound by 2

    List<Property> props = new List<Property>();
    props.Add(new Int32Property(PropertyNames.Amount1, default, 1, upperBound)); // Use the default variable here, in place of the 1000

    return new PropertyCollection(props);
}

 


Copying Surfaces
Sometimes you create an extra Surface or two in your effect. In these examples I'll demonstrate the many ways to set the Destination Surface (dst) to one of these extra surfaces.

 

Spoiler

If you're not doing any pixel processing, and your Surfaces are the same dimensions, you can simply copy like so.


void Render(Surface dst, Surface src, Rectangle rect)
{
    dst.CopySurface(surfaceXYZ, rect.Location, rect);
}

 
Every one should know this one. This if you you doing some pixel processing. Both surfaces need to have the same dimensions.


void Render(Surface dst, Surface src, Rectangle rect)
{
    for (int y = rect.Top; y < rect.Bottom; ++y)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; ++x)
        {
            CurrentPixel = surfaceXYZ[x, y];
            CurrentPixel.A = 128;

            dst[x, y] = CurrentPixel;
        }
    }
}

-------
Ok, say your Surface is smaller than dst, so it does Not have the same dimensions. There are a few options. You can choose one of the following.


CurrentPixel = surfaceXYZ.GetBilinearSample(x, y); // this will place the content of surfaceXYZ at the top left of the canvas
CurrentPixel = surfaceXYZ.GetBilinearSampleClamped(x, y); // this will place the content of surfaceXYZ at the top left of the canvas, and repeat the edge pixel across the reset of the canvas (good if the edges are a solid color)
CurrentPixel = surfaceXYZ.GetBilinearSampleWrapped(x, y); // this will place the content of surfaceXYZ at the top left of the canvas, and then repeat it across the reset of the canvas (aka tiled)

Here's an example


void Render(Surface dst, Surface src, Rectangle rect)
{
    for (int y = rect.Top; y < rect.Bottom; ++y)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; ++x)
        {
            CurrentPixel = surfaceXYZ.GetBilinearSampleWrapped(x, y)

            dst[x, y] = CurrentPixel;
        }
    }
}

-------
Another option is to resize your Surface so that it has the same dimensions as dst. In many cases, this will stretch/smash your image due to the aspect ratio changing.
(It is possible write some code to calculate the aspect ratio, and clip either the height or width. It more complex than I care to demonstrate. Look at my Blur Fill plugin, if you're interested.)


dst.FitSurface(ResamplingAlgorithm.Bicubic, surfaceXYZ); // Bicubic is usually used to enlarge images
dst.FitSurface(ResamplingAlgorithm.Fant, surfaceXYZ); // Fant is usually used to downsize images

Here's an example


void Render(Surface dst, Surface src, Rectangle rect)
{
    dst.FitSurface(ResamplingAlgorithm.Bicubic, surfaceXYZ);
}

 
Use a Surface as an inverted Alpha Mask
This allows you to set the Alpha channel of one Surface based on the Alpha channel of another. I use this method in my Jigsaw Puzzle plugin, so I refer to that in my example.

 

Spoiler

The puzzle cut lines are drawn on a separate transparent Surface. Then the Alpha channel of the Source Surface is subtracted from the Alpha value of the puzzle Surface.
 


void Render(Surface dst, Surface src, Rectangle rect)
{
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            puzzlePixel = puzzleSurface.GetBilinearSample(x, y); // Grab the current pixel from the puzzle surface

            sourcePixel = src[x, y]; // Grab the current pixel from the Source surface
            sourcePixel.A = Int32Util.ClampToByte(sourcePixel.A - puzzlePixel.A); // Change the Alpha Channel on the Source pixel. ClampToByte ensures the value stays between 0 and 255.

            dst[x, y] = sourcePixel;
        }
    }
}


Unneed unsafe?
The master template in CodeLab places 'unsafe' in the OnRender function, but many times it's not needed.

Spoiler

protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length)

If your plugin doesn't use any unsafe code, you can shave off 0.5KB from the file size of your compiled DLL.
Delete the 'unsafe' from the OnRender function, and disable Unsafe Code in the project properties (Release and Debug).

 

 

 

Edited by toe_head2001
  • Upvote 1

Share this post


Link to post
Share on other sites

This:

void Render(Surface dst, Surface src, Rectangle rect)
{
    dst = surfaceXYZ;
}

doesn't actually do anything. You're assigning something that doesn't exist to one of the function's parameters?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this