Jump to content

How to Force a Single Render Call?


Recommended Posts

I'm a beginner regarding Paint.net plugins. I just wrote a Floyd-Steinberg dithering effect, and it works mostly fine. The only problem is that there are visible seams for images larger than about 128x128. I suspect this is due to Paint.NET calling the render function multiple times, which results in artifacts between different rendering regions, since the color error is not correctly dispersed between each.

 

Even in JPEG form, these seams are visible.

test.thumb.jpg.7b1517ae669fc2a26a8fd624d510f16f.jpg

 

The only way I can think to remedy this would be to force a single call to the render function, but I have no idea how to do that. I'm well aware that such a solution would slow down the effect by a significant amount of time. Of course, if there's a better answer, I'm open to any ideas.

 

Here's the code. I'd like to fix this issue before properly refactoring, so I apologize for the mess.

void Render(Surface dst, Surface src, Rectangle rect)
{
    #if DEBUG
    Debug.WriteLine("Start");
    #endif

    ColorBgra PrimaryColor = EnvironmentParameters.PrimaryColor;
    ColorBgra SecondaryColor = EnvironmentParameters.SecondaryColor;

    ColorBgra CurrentPixel, newCol;
    int brightness;
    float e;
    int[,] propagate = {{1, 0, 7}, {-1, 1, 3}, {0, 1, 5}, {1, 1, 1}}; //Floyd-Steinberg filter

    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            dst[x,y] = src[x,y]; //Copy all pixels to dst, we'll need to edit them on the fly
        }
    }

    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            //Change current color to white or black with threshold
            CurrentPixel = dst[x,y];
            brightness = HsvColor.FromColor(CurrentPixel).Value;
            newCol = brightness <= 50 ? PrimaryColor : SecondaryColor;
            newCol.A = CurrentPixel.A;
            dst[x,y] = newCol;

            //Calculate error
            e = 1f/16f * (brightness <= 50 ? brightness : brightness - 100);

            for (int i = 0; i < propagate.GetLength(0); i++) {
                int dx = propagate[i, 0] + x;
                int dy = propagate[i, 1] + y;
                float de = propagate[i, 2] * e;

                if (dx < rect.Right && dx >= rect.Left && dy < rect.Bottom && dy >= rect.Top)
                {
                    //Disperse current error based on filter
                    HsvColor col = HsvColor.FromColor(dst[dx,dy]);
                    col.Value = (int)Math.Max(0, Math.Min(100, col.Value + de));
                    ColorBgra bruh = ColorBgra.FromColor(col.ToColor());
                    dst[dx,dy] = bruh;
                }
            }
        }
    }

    #if DEBUG
    Debug.WriteLine("Done");
    #endif
}

 

Link to post
Share on other sites

The way I'd do it (and the only way I know how to do it) is to create a separate Surface or pseudo-surface, do the processing in OnSetRenderInfo (PreRender in CodeLab), then copy the processed image to dst in the Render pass.

 

By a pseudo-surface, I mean an array of ColorBrgas or similar the same size as the image. There's often no need to use an actual Surface. In your case, it could perhaps be an int or float brightness array.

 

Two disadvantages to doing pre-render processing are that there's no longer parallelism, and that the staus bar isn't updated correctly. However, the lack of parallelism is necessary for many algorithms, and given the choice between a correct status bar and a correct image, I'll choose the correct image. Also, for relatively quick operations, the ill-effect on the status indications is minimal.

 

BTW: IsCancelRequested works in OnSetRenderInfo, and should be included in any pre-render loops that may take a lot of processing time.

Link to post
Share on other sites

Support for this is built-in to the effect rendering system. When you create your EffectOptions, specify a RenderingSchedule of EffectRenderingSchedule.None. I'm not sure how to do that with CodeLab, but @BoltBait should.

  • Upvote 2

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to post
Share on other sites
1 hour ago, Rick Brewster said:

When you create your EffectOptions, specify a RenderingSchedule of EffectRenderingSchedule.None. I'm not sure how to do that with CodeLab, but @BoltBait should.


You would think so, wouldn’t you... 😂

 

J/K. It’s on the File > New screen as an option. 

  • Upvote 1

Click to play:
j.pngs.pngd.pnga.pngp.png
Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game

Link to post
Share on other sites

In the old plugins that did not have this option it is sufficient to add this line after // URL: and // Help:
// Force Single Render Call

 

Now if you are curious to see the code created, click on View Source when you build the plugin and see that the line has been added automatically.

 

public YourNameEffectPlugin ()
             : base (StaticName, StaticIcon, SubmenuName, new EffectOptions () {Flags = EffectFlags.Configurable, RenderingSchedule = EffectRenderingSchedule.None})
         {
         }

  • Upvote 1
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.

×
×
  • Create New...