Jump to content
How to Install Plugins ×

CodeLab v6.12 for Paint.NET 5.0.12 (Updated February 11, 2024)


Recommended Posts

When building DLL, in source code, some strings are always "Untitled" such as AssemblyProduct(Untitled), namespace(UntitledEffect), class(UntitledEffectPlugin). As a result, paint.net gets an issue when loading more than one plugins created by CodeLab.

To avoid this issue, ALWAYS save your source code (File > Save as) before building to DLL.

The reason for this is that CodeLab uses the saved filename as the namespace string.

EDIT: I think I'm going to put a warning in CodeLab if you build before saving the file. Look for this in the next update.

Link to comment
Share on other sites

Not a warning - make it compulsory. If (BuildDll) and ChangedFlag == True then GoSaveYourWork ;)

  • Upvote 2
Link to comment
Share on other sites

  • 5 weeks later...

Bug Report - Double Slider CodeLab 2.4 (PDN 4.0.5)

 

if double slider had Min:0 Default:8  Max:15 the numericupdown cannot increase the value above 8.03 unless the slider is already set above 8.03

 

Repro: Create Double slider  Min:0 Default:8  Max:15. Compile Plugin - run plugin press up arrow and the value will stop at 8.03

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Link to comment
Share on other sites

Bug Report - Double Slider CodeLab 2.4 (PDN 4.0.5)

 

if double slider had Min:0 Default:8  Max:15 the numericupdown cannot increase the value above 8.03 unless the slider is already set above 8.03

 

Repro: Create Double slider  Min:0 Default:8  Max:15. Compile Plugin - run plugin press up arrow and the value will stop at 8.03

 

I can hardly believe that this is a CodeLab bug. Should be tested against IndirectUI.

midoras signature.gif

Link to comment
Share on other sites

I notice when building the DLL, there's a new Single Threaded build option. When I looked at the generated source code, I couldn't tell what was different between building with or without the option. What exactly (or even inexactly) does the option do?

 

Edit: I looked back at the previous comments and found one I somehow missed which kind of explains the Single Threaded option; though it mostly just says it runs the plugin single-threaded. I'm still curious to know what it does differently. Normally, to produce a single-threaded plugin I'd put the code in (as I recall) OnSetRenderInfo instead of Render, but that doesn't seem to be what happens when the option is set. Is there a practical difference between using the OnSetRenderInfo approach (in Visual Studio, of course) and using the CodeLab option, and can and should the CodeLab approach (whatever it is) be used instead of OnSetRenderInfo for Visual Studio single-threaded plugins?

Edited by MJW
Link to comment
Share on other sites

MJW, the key difference is in this code that CodeLab generates:

public nothingEffectPlugin()
    : base(StaticName, StaticIcon, null, EffectFlags.Configurable | EffectFlags.SingleThreaded)
{
}
As you can see the constructor is specified with the SingleThreaded effect flag. In this case, paint.net will not break up the selection into separate work units. But, instead will pass the entire selection to the render function in a single block and therefore only use one thread. The main benefit of this is that the render is computed in an orderly way (from top to bottom, from left to right).

You can see a good example here: http://forums.getpaint.net/index.php?/topic/29428-

CodeLab does not make any other changes than just specifying the effect flag. The single-threaded rendering is supported by paint.net itself.

This option should not be used unless absolutely necessary as the built effect will run slower than an effect built the normal way.

  • Upvote 1
Link to comment
Share on other sites

I had another question about the single-threaded switch. Perhaps I misunderstand it, or perhaps I'm using it wrong (or perhaps I just made a stupid mistake in my code).
 
As an experiment (but also because I thought it might be useful), I wrote a plugin that uses the gradient of a cloud texture to displace the pixels in an image. It's supposed to work as follows: First I generate the (black and white) cloud texture into the dst buffer. I then use the red channel of dst to compute a simple linear gradient, yielding an (x, y) displacement vector, which I store as a scaled integer version in the blue and green channels of dst. Finally, I index into the src buffer with the (x, y) values offset by scaled versions of the displacement vectors. It has to be done in a single thread so that the red channel for the cloud texture isn't overwritten by the modified src data before it can be used to compute the gradient.
 
When I compile the plugin with the single-thread flag set, I see problems at the fairly regularly spaced horizontal lines. I assume they are where the slices meet and where the cloud texture was overwritten by one thread before it could be used by another.
 

Hidden Content:
// Title: Cloud Displacement
// Author: MJW
// Name: Cloud Displacement
// Submenu: Distort
// Force Single Threaded
// Keywords: Cloud|Displacement
// Desc: Displace pixels based on cloud gradient.
// URL: http://www.BoltBait.com/pdn
#region UICode
int Amount1 = 250; // [2,1000] Size Scale
double Amount2 = 0.2; // [0,1] Roughness
double Amount3 = 1; // [0,10] Height Scale
bool Amount4 = false; // [0,1] Wrap
byte Amount5 = 0; // [255] Reseed
#endregion

// Setup for calling the Render Clouds effect
private CloudsEffect cloudsEffect = new CloudsEffect();
private PropertyCollection cloudsProps;

// Here is the main render loop function
Surface Dst;
double gradPreScale = 2.5;
float gradScale;
bool wrap;
void Render(Surface dst, Surface src, Rectangle rect)
{
    Dst = dst;
    wrap = Amount4;
    gradScale = (float)(0.2 * Amount3);
    
    // Call the Render Clouds function
    cloudsProps = cloudsEffect.CreatePropertyCollection();
    PropertyBasedEffectConfigToken CloudsParameters = new PropertyBasedEffectConfigToken(cloudsProps);
    CloudsParameters.SetPropertyValue(CloudsEffect.PropertyNames.Scale, Amount1);
    CloudsParameters.SetPropertyValue(CloudsEffect.PropertyNames.Power, Amount2);
    CloudsParameters.SetPropertyValue(CloudsEffect.PropertyNames.BlendMode,
        LayerBlendModeUtil.CreateCompositionOp(LayerBlendMode.Normal)
);
    CloudsParameters.SetPropertyValue(CloudsEffect.PropertyNames.Seed, (int)Amount5);
    cloudsEffect.SetRenderInfo(CloudsParameters, new RenderArgs(dst), new RenderArgs(src));
    // Call the Clouds function using Black and White
    cloudsEffect.Render(new Rectangle[1] {rect}, 0, 1);

    int width = dst.Width, height = dst.Height;
    int top = rect.Top, right = rect.Right, bottom = rect.Bottom, left = rect.Left;
    int gradLeft = (left == 0) ? 1 : left;
    int gradRight = (right == width - 1) ? width - 2 : width - 1;
    // Now in the main render loop, the dst canvas has a render of clouds
    
    ColorBgra CurrentPixel = ColorBgra.Black;
    for (int y = top; y < bottom; y++)
    {
        if (IsCancelRequested) return;
        if ((y == 0) || (y == height - 1))
        {
            for (int x = left; x < right; x++)
            {
               ClrDstGrad(x, y);
            }
        }
        else
        {
            if (left != gradLeft)
            {
                ClrDstGrad(0, y);
            }
            if (right != gradRight)
            {
                ClrDstGrad(right - 1, y);
            }
            for (int x = gradLeft; x < gradRight; x++)
            {         
                int UM, UR, MR, LR, LM, LL, ML, UL;
                UM = dst[x, y - 1].R;
                UR = dst[x + 1, y - 1].R;
                MR = dst[x + 1, y].R;
                LR = dst[x + 1, y + 1].R;
                LM = dst[x, y + 1].R;
                LL = dst[x - 1, y + 1].R;
                ML = dst[x - 1, y].R;
                UL = dst[x - 1, y - 1].R;
        
                int dX = (UL - UR) + 2 * (ML - MR) + (LL - LR);
                int dY = (UL - LL) + 2 * (UM - LM) + (UR - LR);
                SetDstGrad(x, y, dX, dY);
            }
        }
    }
    
    for (int y = top; y < bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = left; x < right; x++)
        {
            float dX = gradScale * (float)(dst[x, y].G - 127);
            float dY = gradScale * (float)(dst[x, y].B - 127);
            dst[x, y] = wrap ?
                src.GetBilinearSampleWrapped(x + dX, y + dY) :
                src.GetBilinearSample(x + dX, y + dY);
        }
    }
}
void SetDstGrad(int x, int y, int dX, int dY)
{
    dX = 127 + (int)(gradPreScale * (double)dX + 0.5);
    if (dX < 0)
        dX = 0;
    else if (dX > 255)
        dX = 255;
    dY = 127 + (int)(gradPreScale * (double)dY + 0.5);
    if (dY < 0)
        dY = 0;
    else if (dY > 255)
        dY = 255;
    Dst[x, y] = ColorBgra.FromBgr((byte)dX, (byte)dY, Dst[x, y].R);
}

void ClrDstGrad(int x, int y)
{
    Dst[x, y] = ColorBgra.FromBgr((byte)0, (byte)0, Dst[x, y].R);
}

 

(My handling of the  gradient at the edges is totally inelegant -- Hey, it's just an experiment for now!)
 

Edited by MJW
Link to comment
Share on other sites

You need to read this page in regards to working with the Clouds effect:

http://boltbait.com/pdn/CodeLab/help/tutorial3.php

And, remember, while in CodeLab itself, the effect is still multi-threaded. Single threading only works on effects that are compiled to a dll file.

EDIT: I don't see any obvious errors. I tested clouds in a compiled dll with single-threading on and it worked fine. Perhaps the error is in your calculations.

Link to comment
Share on other sites

I think I realized what's happening. Paint.Net is still calling the render routine one slice at a time, it's just doing so in a single thread; therefore, the final image overwrites the cloud image before the next slice is processed. Since the gradient requires pixels from the line above the current pixel, the computation is wrong at the boundaries I thought when single-threaded was set, Paint.Net would call the render routine once with the rectangle set to the entire region. I wish it worked that way.

 

EDIT: If it works the way I think it does, there's a fairly simple kludge to fix the problem for this particular plugin: I'll just compute the gradient from the current line and the next two lines. That will, in effect, shift the cloud texture a pixel up, which, of course, doesn't matter. It also means there will be a valid gradient for the top line, but none for the bottom two lines.

Edited by MJW
Link to comment
Share on other sites

Why not fire up Visual Studio and render your clouds to a scratch surface in one go? CodeLab even gives you the source to start the project.

Link to comment
Share on other sites

I'll probably do that. I was hoping that the single-thread switch would provide a quick way to write simple plugins that need to access the dst surface in random order, but it seems to be quite limited in what it can do.

 

The biggest problem I have with developing Visual Studio plugins is the user interface. IndirectIU doesn't seem very easy to use outside of CodeLab, so I usually start by writing a stub CodeLab plugin, and save the compiled source. The problem comes after I've added a bunch of non-CodeLab code, then want to change the user interface controls. Of course, I could just use the Visual Studio controls, but then there's the look-and-feel problem -- especially the controls' reset-to-default button, which is a really nice feature. Maybe some day I'll get ambitious enough to write Visual Studio lookalike versions of the IndirectIU controls.

Edited by MJW
Link to comment
Share on other sites

  • 1 month later...

I do believe I've found a minor bug. If there happens to be a .bat file already on the desktop, then CodeLab doesn't create one to move the generated .dll file.

 

You mean, if the install_xxx.bat file already exists, it won't write a new one?  Or, are you saying that if ANY bat file exists on the desktop then the install_xxx.bat file won't be written?

Link to comment
Share on other sites

You mean, if the install_xxx.bat file already exists, it won't write a new one?  Or, are you saying that if ANY bat file exists on the desktop then the install_xxx.bat file won't be written?

 

The second... if ANY bat file exists on the desktop then the install_xxx.bat file won't be written.

(September 25th, 2023)  Sorry about any broken images in my posts. I am aware of the issue.

bp-sig.png
My Gallery  |  My Plugin Pack

Layman's Guide to CodeLab

Link to comment
Share on other sites

Today I was trying to use a built in effect in a script, but for some reason the code for the effect was getting ignored when I added the main loop code. After a while, I copy & pasted an example from Part 3 of the CodeLab tutorial that uses Gaussian Blur, and tried to see how that one worked. However, even in the example code, the Gaussian Blur was not being applied as it should.

 

This is the specific code example that I got from the tutorial.

Hidden Content:

#region UICode
int Amount1=10;    //[1,20]Radius
#endregion

private UserBlendOp darkenOp = new UserBlendOps.DarkenBlendOp();

void Render(Surface dst, Surface src, Rectangle rect)
{
    // Call the Gaussian Blur effect
    GaussianBlurEffect blurEffect = new GaussianBlurEffect();
    PropertyCollection bProps = blurEffect.CreatePropertyCollection();
    PropertyBasedEffectConfigToken bParameters = new PropertyBasedEffectConfigToken(bProps);
    bParameters.SetPropertyValue(GaussianBlurEffect.PropertyNames.Radius, Amount1);
    blurEffect.SetRenderInfo(bParameters, new RenderArgs(dst), new RenderArgs(src));
    blurEffect.Render(new Rectangle[1] {rect},0,1);
    
    // Loop through all pixels and calculate final pixel values
    ColorBgra CurrentPixel;
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        for (int x = rect.Left; x < rect.Right; x++)
        {
            // Get the current pixel from source canvas
            CurrentPixel = src[x,y];
            // Combine source pixel with blured dest pixel
            // using the darken blend operation.
            CurrentPixel = darkenOp.Apply(CurrentPixel, dst[x,y]);
            // Save the results for final display.
            dst[x,y] = CurrentPixel;
        }
    }
}

Is it borked, or I just a noob?

(September 25th, 2023)  Sorry about any broken images in my posts. I am aware of the issue.

bp-sig.png
My Gallery  |  My Plugin Pack

Layman's Guide to CodeLab

Link to comment
Share on other sites

I have tried and tried, but I just can't get this thing to fail.

 

My apologies, I had assumed that was what was causing it. I checked for sure this time. The only time it doesn't create the .bat file is when you are still using the "Untitled" instance of CodeLab. (without opening a .cs file or going to File>New)

 

For something simple like this, just use the File > New templates to write the code for you.

 

No worries, I had the effect call code right all along. It was the loop code that was overriding the surface. I had to switch to Visual Studio. Now rendering the Pixelation effect to a scratch surface, and then apply the loop to the scratch surface. Your source code to 'Inner Shadow' proved to be a great example of how to use a scratch surface. Thank You.

(September 25th, 2023)  Sorry about any broken images in my posts. I am aware of the issue.

bp-sig.png
My Gallery  |  My Plugin Pack

Layman's Guide to CodeLab

Link to comment
Share on other sites

My apologies, I had assumed that was what was causing it. I checked for sure this time. The only time it doesn't create the .bat file is when you are still using the "Untitled" instance of CodeLab. (without opening a .cs file or going to File>New)

 

As mentioned before in this thread, this has already been fixed for the next release.

Link to comment
Share on other sites

  • 2 weeks later...

I added a feature to CodeLab which I think is quite useful. I've always hated inserting new controls in the middle of some previously-defined controls, and having to renumber the Amount variables; so I added a "Renumber UI Variables" option to the Edit list. A new control can be created with any unused number and inserted, out of order, into the middle of the other UI variables. Running the renumber option renumbers the UI variables consecutively, and changes all the UI variable uses in the code to match. (Though I explained it in terms of adding a new variable, renumbering also allows UI variables to be rearranged in any order, and then renumbered.)

 

I changed only two files: CodeLabConfigDialog.cs to add the option into the Edit menu along with the mouse-click event handler, and CodeTextBox.cs to add the renumbering code. I also added a little renumber icon PNG file.

 

I have no intention of starting a separate ColeLab line (heaven forbid!). I was hoping that perhaps if the feature is popular, BoltBait might possibly add it (or some variation of it) to a future revision of CodeLab.

 

The renumbering code is:

Hidden Content:

        public bool CanRenumberUIVariables
        {
            get
            {
                // Might want enable only if there are UI variables.
                return !string.IsNullOrEmpty(this.Text);
            }
        }

        // Renumber the Amount vaiables so they are in order from 1 to n.
        // The variables can originally be numbered in almost any manner.
        // I try to catch most error conditions. I don't catch the condition where there's a gap in the UI
        // numbers, and that variable is created in the user (non-UICode) part of the code.
        private int[] varNum = null; // Must be accessable to MatchEvaluator.
        private int numVars = 0;
        public bool RenumberUIVars()
        {
            string text = this.Text;
            varNum = new int[100];
            numVars = GetCurrentUINumbering(text, varNum);

            if (numVars == -1)
            {
                MessageBox.Show("Could not process the UICode region.", "Renumber Error");
                return false;
            }

            // There are obviously more efficent appoaches that don't require searching through
            // the array. But this method is straight forward and given the number of variables,
            // should work just fine.

            // Check for duplicate entries and determine if the variables are already in order.
            bool needsRenumbering = false;
            for (int i = 0; i < numVars; i++)
            {
                int myNum = varNum[i];
                if (myNum != i + 1)
                    needsRenumbering = true;

                // Check for duplicates.
                for (int j = i + 1; j < numVars; j++)
                {
                    if (myNum == varNum[j])
                    {
                        MessageBox.Show("Amount" + myNum.ToString() + " occurs more than once.", "Renumber Error");
                        return false;
                    }
                }
            }

            if (!needsRenumbering)
            {
                MessageBox.Show("No renumbering required.", "Renumber");
                return true;
            }

            // Change the Amount variable names so renumbered and non-renumbered variables aren't confused.
            // The substituted string is random, but shouldn't occur in any source code.
            text = Regex.Replace(text, @"\bAmount(\d+)\b", "Amt%!%$1");

            // Change the old names to the new names.
            this.Text = Regex.Replace(text, @"Amt%!%\d+", ReplaceWithRenumbered);

            MessageBox.Show("Renumbering complete.", "Renumber");
            return true;
        }

        private string ReplaceWithRenumbered(Match m)
        {
            int n;
            if (int.TryParse(m.Value.Substring(6), out n))
            {
                // If in range, substitute the renumbered value, else used original value.
                // Try to find the new number in the renumber array.
                int i;
                for (i = 0; (varNum[i] != n) && (i < numVars); i++)
                    ;
                return "Amount" + ((i == numVars) ? n : i + 1).ToString();
            }
            else
            {
                // This shouldn't happen, but just in case.
                return "Amount" + m.Value.Substring(6);
            }
        }

        private int GetCurrentUINumbering(string text, int[] results)
        {
            int varCnt = 0;
            int max = results.Length - 1;

            string findUIRegion = @"\#region UICode$(?s:(?<uicode>.*?))\n\#endregion";
            Match m = Regex.Match(text, findUIRegion, RegexOptions.Multiline);
            if (!m.Success)
                return -1;

            string textUI = m.Groups["uicode"].Value;
            string pattern = @"\s*Amount(?<number>\d+)[ =;\t]";
            foreach (Match mv in Regex.Matches(textUI, pattern))
            {
                int n;
                if (int.TryParse(mv.Groups["number"].Value, out n))
                {
                    // (This should always find an integer.)
                    results[varCnt] = n;
                    if (varCnt++ == max)
                        break;  // Just ignore any unprocessed variables (this shouldn't happen!)
                }
            }

            return varCnt;
        }

 

If anyone wants to try it, the DLL is here: <snip>

 

The VS (2013) project is here:

Edited by BoltBait
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...