Rick Brewster

CodeLab v4.3 (for advanced users) Released July 13, 2019

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

Thank you, that looks very useful! I'm eager to give that a try.

Edited by MJW

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
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.

Share this post


Link to post
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?

Share this post


Link to post
Share on other sites

Is it borked, or I just a noob?

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

Share this post


Link to post
Share on other sites

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

 

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

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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.