Marcsine Posted December 16, 2019 Posted December 16, 2019 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. 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 } Quote
MJW Posted December 16, 2019 Posted December 16, 2019 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. Quote
BoltBait Posted December 16, 2019 Posted December 16, 2019 Take a look at this plugin: https://forums.getpaint.net/topic/113220-boltbaits-plugin-pack-for-pdn-v41-and-beyond-updated-december-1-2018/?do=findComment&comment=552962 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game
Rick Brewster Posted December 16, 2019 Posted December 16, 2019 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. 2 Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html
xod Posted December 16, 2019 Posted December 16, 2019 Until now, to avoid the situation mentioned by @Marcsine, I was putting the code in the OnSetRenderInfo routine (working in Visual Studio). Thanks Rick for this very useful information. Quote
BoltBait Posted December 16, 2019 Posted December 16, 2019 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. 1 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game
xod Posted December 16, 2019 Posted December 16, 2019 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}) { } 1 Quote
toe_head2001 Posted December 17, 2019 Posted December 17, 2019 Also keep in mind you have to resort to building a DLL to get 'Single Render Call'. You can force CodeLab to run with 'Single Render Call', so you can have the feature while you're developing your code. Quote My Gallery | My Plugin Pack Layman's Guide to CodeLab
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.