BoltBait.com

How to Write a Paint.NET Bitmap Plugin

Let's examine the simplest plugin type


The Bitmap type effect is used when you need to modify each pixel individually. Go ahead and start CodeLab, then create a new Bitmap Effect by using the following menu:


This will open a new source code window with the default bitmap template ready for you to make changes.


Bitmap Effect Template

Here is the default bitmap template. Currently it does nothing but copy all pixels from the source canvas (your canvas before any changes are made) to the destination canvas (the canvas that holds the results of your changes):

// Name:
// Submenu:
// Author:
// Title:
// Version:
// Desc:
// Keywords:
// URL:
// Help:
#region UICode
IntSliderControl Amount1 = 0; // [0,100] Slider 1 Description
IntSliderControl Amount2 = 0; // [0,100] Slider 2 Description
IntSliderControl Amount3 = 0; // [0,100] Slider 3 Description
#endregion

protected override void OnRender(IBitmapEffectOutput output)
{
    using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32();
    using IBitmapLock<ColorBgra32> sourceLock = Environment.GetSourceBitmapBgra32().Lock(new RectInt32(0, 0, sourceBitmap.Size));
    RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr();

    RectInt32 outputBounds = output.Bounds;
    using IBitmapLock<ColorBgra32> outputLock = output.LockBgra32();
    RegionPtr<ColorBgra32> outputSubRegion = outputLock.AsRegionPtr();
    var outputRegion = outputSubRegion.OffsetView(-outputBounds.Location);

    // Delete any of these lines you don't need
    ColorBgra32 primaryColor = Environment.PrimaryColor;
    ColorBgra32 secondaryColor = Environment.SecondaryColor;
    int canvasCenterX = Environment.Document.Size.Width / 2;
    int canvasCenterY = Environment.Document.Size.Height / 2;
    var selection = Environment.Selection.RenderBounds;
    int selectionCenterX = (selection.Right - selection.Left) / 2 + selection.Left;
    int selectionCenterY = (selection.Bottom - selection.Top) / 2 + selection.Top;

    // Loop through the output canvas tile
    for (int y = outputBounds.Top; y < outputBounds.Bottom; ++y)
    {
        if (IsCancelRequested) return;

        for (int x = outputBounds.Left; x < outputBounds.Right; ++x)
        {
            // Get your source pixel
            ColorBgra32 sourcePixel = sourceRegion[x,y];

            // TODO: Change source pixel according to some algorithm
            //sourcePixel.B = (byte)(Amount1 * 255 / 100); // Blue
            //sourcePixel.G = (byte)(Amount2 * 255 / 100); // Green
            //sourcePixel.R = (byte)(Amount3 * 255 / 100); // Red
            //sourcePixel.A = 255; // Alpha Transparency

            // Save your pixel to the output canvas
            outputRegion[x,y] = sourcePixel;
        }
    }
}

It is the responsibility of your plugin to write to each pixel on the destination canvas otherwise it will be transparent when the effect is finished.


Your effect can read the corresponding pixel from the source canvas and store it in the destination canvas or it can modify it in some way before storing it.

The line "ColorBgra32 sourcePixel = sourceRegion[x,y];" reads one pixel from the source surface into the sourcePixel variable. The line "outputRegion[x,y] = sourcePixel;" stores that pixel onto the destination canvas. Your job is to use your imagination to come up with an algorithm to modify (or not) that pixel on it's way from the source canvas to the destination canvas. In order to do that, just replace the "// TODO:" comment line with your algorithm (formula).

When starting an effect plugin, the first thing you'll need is an idea for what you want your plugin to do. Generally, you'll have some problem or set of steps you find yourself repeating over and over again. These are good candidates for problems you can solve with an effect plugin.

I will not tell you that figuring out the algorithm for a desired effect is easy. But, once you complete all of the lessons here, you might find that it isn't as hard as you had imagined.

For the purposes of this first tutorial, we will create an effect that places a single black dot at the center of our current selection. This is useful for when you need to know the exact center of your selection when drawing lines or other shapes and you want them to be centered. I chose this effect because it does not require any user interface (UI) controls. We will tackle UI controls in another lesson.


OK, let's modify our default script to draw a black dot at the center of the current selection.

At the top of the script, you can see the following lines:
// Name:
// Submenu:
// Author:
// Title:
// Version:
// Desc:
// Keywords:
// URL:
// Help:
#region UICode
IntSliderControl Amount1 = 0; // [0,100] Slider 1 Description
IntSliderControl Amount2 = 0; // [0,100] Slider 2 Description
IntSliderControl Amount3 = 0; // [0,100] Slider 3 Description
#endregion

These lines are used to specify the user interface (UI) of your effect. As mentioned before, we won't be needing a UI for this effect, so go ahead and delete all those lines.

Next, we won't be needing the current Primary or Secondary color, so you can delete these lines as well:
    // Delete any of these lines you don't need
    ColorBgra32 primaryColor = Environment.PrimaryColor;
    ColorBgra32 secondaryColor = Environment.SecondaryColor;

We WILL be needing to know the center of our selection so KEEP these lines:
    int canvasCenterX = Environment.Document.Size.Width / 2;
    int canvasCenterY = Environment.Document.Size.Height / 2;
    var selection = Environment.Selection.RenderBounds;
    int selectionCenterX = (selection.Right - selection.Left) / 2 + selection.Left;
    int selectionCenterY = (selection.Bottom - selection.Top) / 2 + selection.Top;

The inner loops of our effect (x and y) look like this:
    // Loop through the output canvas tile
    for (int y = outputBounds.Top; y < outputBounds.Bottom; ++y)
    {
        if (IsCancelRequested) return;

        for (int x = outputBounds.Left; x < outputBounds.Right; ++x)
        {
            // Get your source pixel
            ColorBgra32 sourcePixel = sourceRegion[x,y];

            // TODO: Change source pixel according to some algorithm
            //sourcePixel.B = (byte)(Amount1 * 255 / 100); // Blue
            //sourcePixel.G = (byte)(Amount2 * 255 / 100); // Green
            //sourcePixel.R = (byte)(Amount3 * 255 / 100); // Red
            //sourcePixel.A = 255; // Alpha Transparency

            // Save your pixel to the output canvas
            outputRegion[x,y] = sourcePixel;
        }
    }

The loops iterate over our destination canvas tile. We just need to replace the "TODO:" lines with our algorithm for placing a dot at the center of our selection. Since we know that inside the innermost loop we're working on the pixel at address x,y we should be able to detect when we're at the center of the selection like this:

if (y == selectionCenterY && x == selectionCenterX)
{

}

Replacing the "TODO:" lines with this block above, our script should now look like this:

protected override void OnRender(IBitmapEffectOutput output)
{
    using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32();
    using IBitmapLock<ColorBgra32> sourceLock = Environment.GetSourceBitmapBgra32().Lock(new RectInt32(0, 0, sourceBitmap.Size));
    RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr();

    RectInt32 outputBounds = output.Bounds;
    using IBitmapLock<ColorBgra32> outputLock = output.LockBgra32();
    RegionPtr<ColorBgra32> outputSubRegion = outputLock.AsRegionPtr();
    var outputRegion = outputSubRegion.OffsetView(-outputBounds.Location);

    int canvasCenterX = Environment.Document.Size.Width / 2;
    int canvasCenterY = Environment.Document.Size.Height / 2;
    var selection = Environment.Selection.RenderBounds;
    int selectionCenterX = (selection.Right - selection.Left) / 2 + selection.Left;
    int selectionCenterY = (selection.Bottom - selection.Top) / 2 + selection.Top;

    // Loop through the output canvas tile
    for (int y = outputBounds.Top; y < outputBounds.Bottom; ++y)
    {
        if (IsCancelRequested) return;

        for (int x = outputBounds.Left; x < outputBounds.Right; ++x)
        {
            // Get your source pixel
            ColorBgra32 sourcePixel = sourceRegion[x,y];

			if (y == selectionCenterY && x == selectionCenterX)
			{

			}
            // Save your pixel to the output canvas
            outputRegion[x,y] = sourcePixel;
        }
    }
}

Only one last thing to do: Modify the center pixel to black. Since Bitmap effects run in the SrgbColor color space, that's easily done like this:

sourcePixel = SrgbColors.Black;

This sets our sourcePixel variable to a black pixel. Place that line of code inside of our "if" block and our final script should look like this:

protected override void OnRender(IBitmapEffectOutput output)
{
    using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32();
    using IBitmapLock<ColorBgra32> sourceLock = Environment.GetSourceBitmapBgra32().Lock(new RectInt32(0, 0, sourceBitmap.Size));
    RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr();

    RectInt32 outputBounds = output.Bounds;
    using IBitmapLock<ColorBgra32> outputLock = output.LockBgra32();
    RegionPtr<ColorBgra32> outputSubRegion = outputLock.AsRegionPtr();
    var outputRegion = outputSubRegion.OffsetView(-outputBounds.Location);

    int canvasCenterX = Environment.Document.Size.Width / 2;
    int canvasCenterY = Environment.Document.Size.Height / 2;
    var selection = Environment.Selection.RenderBounds;
    int selectionCenterX = (selection.Right - selection.Left) / 2 + selection.Left;
    int selectionCenterY = (selection.Bottom - selection.Top) / 2 + selection.Top;

    // Loop through the output canvas tile
    for (int y = outputBounds.Top; y < outputBounds.Bottom; ++y)
    {
        if (IsCancelRequested) return;

        for (int x = outputBounds.Left; x < outputBounds.Right; ++x)
        {
            // Get your source pixel
            ColorBgra32 sourcePixel = sourceRegion[x,y];

			if (y == selectionCenterY && x == selectionCenterX)
			{
				sourcePixel = SrgbColors.Black;
			}
            // Save your pixel to the output canvas
            outputRegion[x,y] = sourcePixel;
        }
    }
}

That's it!

If you think about it, the only thing we actually needed to write ourselves to implement the "Dot at Center" algorithm was this part:

if (y == selectionCenterY && x == selectionCenterX)
{
	sourcePixel = SrgbColors.Black;
}

Everything else was written for you by CodeLab.


Finishing Up

Now that you've finished your first Bitmap plugin, be sure to "File > Save" your DotAtCenter.cs file.

If you'd like to make that plugin a permanent part of your Paint.NET installation, read:

How to Build a DLL from a CodeLab script
How to install a DLL into Paint.NET


What's Next?

Now that you know a bit about the Bitmap effect, let's take a look at the Image plugin:

How to write a Paint.NET Bitmap plugin
How to write a Paint.NET GPU Image plugin ← next up!
How to write a Paint.NET GPU Drawing plugin

Or, Dig Deeper!

Now that you know a bit about the Bitmap effect, let's dig a little deeper and learn how to add a simple UI to our effect:

How to add a UI to your plugin


Donate

The best way to say "Thanks" for teaching you something here, is to fill out this form. It uses PayPal to process your donation. You don't need a PayPal account, just a credit/debit card will do. If PayPal doesn't work for you, no need to worry--just enjoy the tutorials for free!

$
Thank you for your donation. I don't get many, so you can be sure I really appreciate yours!



 

 
 

News



CodeLab 6.12 Released
(February 11, 2024)
This latest release of CodeLab for Paint.NET includes the ability to write GPU accelerated plugins.
More...

Double-Six Dominoes 3.1
(May 10, 2021)
This long-awaited refresh of the most popular dominoes game on Download.com is now available!
More...

HTML Editor 1.5 Released
(March 31, 2016)
This latest release is a complete rewrite adding a wysiwyg editor mode and a much improved UI.
More...