toe_head2001 Posted December 2, 2015 Share Posted December 2, 2015 (edited) Here are a few Mini Tutorials and Tips that are hopefully helpful for new plugin developers. All of these are geared towards effects in Visual Studio. If you find mistakes, please point them out. Dynamic values for IndirectUI sliders Sometimes you have a parameter for size, and you're not sure what the Upper Bound (Maximum vale) should be set to. Is 1000px enough? What if some one has a 5000px wide selection/canvas? Spoiler I will demonstrate how to get a good value based on the current selection (remember, if there's no selection, the whole canvas is used). First, here's our IndirectUI slider. The default value is hard-code at 100, and the max value is hard-coded at 1000. protected override PropertyCollection OnCreatePropertyCollection() { List<Property> props = new List<Property>(); props.Add(new Int32Property(PropertyNames.Amount1, 100, 1, 1000)); return new PropertyCollection(props); } Let's create an Size object that represents the Height and Width of the selection. protected override PropertyCollection OnCreatePropertyCollection() { Size selSize = EnvironmentParameters.SelectionBounds.Size; // Gets the selection dimensions List<Property> props = new List<Property>(); props.Add(new Int32Property(PropertyNames.Amount1, 100, 1, 1000)); return new PropertyCollection(props); } Now we'll want to determine if the Height or Width is the shorter value of the two, and we'll use that for the Upper Bound on the slider. We'll make an int variable to hold the value. protected override PropertyCollection OnCreatePropertyCollection() { Size selSize = EnvironmentParameters.SelectionBounds.Size; int upperBound = Math.Min(selSize.Width, selSize.Height); // use the smallest of the two dimensions List<Property> props = new List<Property>(); props.Add(new Int32Property(PropertyNames.Amount1, 100, 1, upperBound)); // Use the upperBound variable here, in place of the 1000 return new PropertyCollection(props); } How about the default value? Let's set that to half the value of the Upper Bound. protected override PropertyCollection OnCreatePropertyCollection() { Size selSize = EnvironmentParameters.SelectionBounds.Size; int upperBound = Math.Min(selSize.Width, selSize.Height); int default = upperBound / 2; // simply divide upperBound by 2 List<Property> props = new List<Property>(); props.Add(new Int32Property(PropertyNames.Amount1, default, 1, upperBound)); // Use the default variable here, in place of the 1000 return new PropertyCollection(props); } Copying Surfaces Sometimes you create an extra Surface or two in your effect. In these examples I'll demonstrate the many ways to set the Destination Surface (dst) to one of these extra surfaces. Spoiler If you're not doing any pixel processing, and your Surfaces are the same dimensions, you can simply copy like so. void Render(Surface dst, Surface src, Rectangle rect) { dst.CopySurface(surfaceXYZ, rect.Location, rect); } Every one should know this one. This if you you doing some pixel processing. Both surfaces need to have the same dimensions. void Render(Surface dst, Surface src, Rectangle rect) { for (int y = rect.Top; y < rect.Bottom; ++y) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; ++x) { CurrentPixel = surfaceXYZ[x, y]; CurrentPixel.A = 128; dst[x, y] = CurrentPixel; } } } ------- Ok, say your Surface is smaller than dst, so it does Not have the same dimensions. There are a few options. You can choose one of the following. CurrentPixel = surfaceXYZ.GetBilinearSample(x, y); // this will place the content of surfaceXYZ at the top left of the canvas CurrentPixel = surfaceXYZ.GetBilinearSampleClamped(x, y); // this will place the content of surfaceXYZ at the top left of the canvas, and repeat the edge pixel across the reset of the canvas (good if the edges are a solid color) CurrentPixel = surfaceXYZ.GetBilinearSampleWrapped(x, y); // this will place the content of surfaceXYZ at the top left of the canvas, and then repeat it across the reset of the canvas (aka tiled) Here's an example void Render(Surface dst, Surface src, Rectangle rect) { for (int y = rect.Top; y < rect.Bottom; ++y) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; ++x) { CurrentPixel = surfaceXYZ.GetBilinearSampleWrapped(x, y) dst[x, y] = CurrentPixel; } } } ------- Another option is to resize your Surface so that it has the same dimensions as dst. In many cases, this will stretch/smash your image due to the aspect ratio changing. (It is possible write some code to calculate the aspect ratio, and clip either the height or width. It more complex than I care to demonstrate. Look at my Blur Fill plugin, if you're interested.) dst.FitSurface(ResamplingAlgorithm.Bicubic, surfaceXYZ); // Bicubic is usually used to enlarge images dst.FitSurface(ResamplingAlgorithm.Fant, surfaceXYZ); // Fant is usually used to downsize images Here's an example void Render(Surface dst, Surface src, Rectangle rect) { dst.FitSurface(ResamplingAlgorithm.Bicubic, surfaceXYZ); } Use a Surface as an inverted Alpha Mask This allows you to set the Alpha channel of one Surface based on the Alpha channel of another. I use this method in my Jigsaw Puzzle plugin, so I refer to that in my example. Spoiler The puzzle cut lines are drawn on a separate transparent Surface. Then the Alpha channel of the Source Surface is subtracted from the Alpha value of the puzzle Surface. void Render(Surface dst, Surface src, Rectangle rect) { for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { puzzlePixel = puzzleSurface.GetBilinearSample(x, y); // Grab the current pixel from the puzzle surface sourcePixel = src[x, y]; // Grab the current pixel from the Source surface sourcePixel.A = Int32Util.ClampToByte(sourcePixel.A - puzzlePixel.A); // Change the Alpha Channel on the Source pixel. ClampToByte ensures the value stays between 0 and 255. dst[x, y] = sourcePixel; } } } Unneed unsafe? The master template in CodeLab places 'unsafe' in the OnRender function, but many times it's not needed. Spoiler protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) If your plugin doesn't use any unsafe code, you can shave off 0.5KB from the file size of your compiled DLL. Delete the 'unsafe' from the OnRender function, and disable Unsafe Code in the project properties (Release and Debug). Edited March 18, 2020 by toe_head2001 1 1 Quote June 7th, 2023: Sorry about any broken images in my posts. The underlying DNS issue should be resolved soon. My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
Rick Brewster Posted December 2, 2015 Share Posted December 2, 2015 This: void Render(Surface dst, Surface src, Rectangle rect) { dst = surfaceXYZ; } doesn't actually do anything. You're assigning something that doesn't exist to one of the function's parameters? Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
toe_head2001 Posted December 2, 2015 Author Share Posted December 2, 2015 Hmm, I thought I had done that before. Thanks for pointing that out. I should have tested it right before posting. Quote June 7th, 2023: Sorry about any broken images in my posts. The underlying DNS issue should be resolved soon. My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
BoltBait Posted December 2, 2015 Share Posted December 2, 2015 That should read: dst.CopySurface(surfaceXYZ, rect.Location, rect); 2 Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
ReMake Posted December 25, 2018 Share Posted December 25, 2018 I think this tutorial can be pinned. 1 Quote Link to comment Share on other sites More sharing options...
xod Posted March 15, 2019 Share Posted March 15, 2019 (edited) Question about Dynamic values for IndirectUI sliders. After running the MoveLines plugin below on a standard 800x600 canvas and then choosing a New size canvas 2400x1800 (in the same working session), the selSize is not updated and retains the old size 800x600. Please, tell me what is wrong with my code? https://www.mediafire.com/file/3osg1n58q319efd/MoveLinesEffect.zip/file Edited March 15, 2019 by xod Quote Link to comment Share on other sites More sharing options...
toe_head2001 Posted March 15, 2019 Author Share Posted March 15, 2019 10 minutes ago, xod said: After running the MoveLines plugin below on a standard 800x600 canvas and then choosing a New size canvas 2400x1800 (in the same working session), the selSize is not updated and retains the old size 800x600. Hmm, it seems my Radius Corners plugin also has that issue. 🤔 I'll look into it, and let you know. 1 Quote June 7th, 2023: Sorry about any broken images in my posts. The underlying DNS issue should be resolved soon. My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
MJW Posted March 15, 2019 Share Posted March 15, 2019 nevermind Quote Link to comment Share on other sites More sharing options...
xod Posted March 15, 2019 Share Posted March 15, 2019 Yes, you are right, I uploaded the wrong zip file, sorry. Try again now. Quote Link to comment Share on other sites More sharing options...
toe_head2001 Posted March 15, 2019 Author Share Posted March 15, 2019 (edited) 57 minutes ago, xod said: the selSize is not updated and retains the old size 800x600. The selection size is updated correctly; that's not the issue. IndirectUI is not using the new MaxValue. That is to say, even when you use a different value for the maxValue parameter, it retains the old value from the previous plugin run. (EDIT: to clarify, the UI Control uses the new MaxValue, but the underlying property retains the old one) So when you move the slider, the value gets clamped to the old MaxValue (since it didn't actually get changed to the new MaxValue). This may or may not be intentional behavior. @Rick Brewster Edited March 15, 2019 by toe_head2001 1 Quote June 7th, 2023: Sorry about any broken images in my posts. The underlying DNS issue should be resolved soon. My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
xod Posted March 15, 2019 Share Posted March 15, 2019 Ok, I understand, thanks for the answer. Quote Link to comment Share on other sites More sharing options...
MJW Posted March 15, 2019 Share Posted March 15, 2019 A long time ago, I wrote a test program to see if I could dynamically change the max value. I just recompiled and ran it. It seems, from what I can tell so far, to work correctly. I'm not sure what's going on. protected override PropertyCollection OnCreatePropertyCollection() { List<Property> props = new List<Property>(); int w = EnvironmentParameters.SourceSurface.Width; int h = EnvironmentParameters.SourceSurface.Height; props.Add(new Int32Property(PropertyNames.Amount1, 1, 1, w)); props.Add(new Int32Property(PropertyNames.Amount2, 0, 0, h)); props.Add(new Int32Property(PropertyNames.Amount3, 0, 0, w + h)); return new PropertyCollection(props); } protected override ControlInfo OnCreateConfigUI(PropertyCollection props) { ControlInfo configUI = CreateDefaultConfigUI(props); configUI.SetPropertyControlValue(PropertyNames.Amount1, ControlInfoPropertyNames.DisplayName, "Sub-Window Width"); configUI.SetPropertyControlValue(PropertyNames.Amount2, ControlInfoPropertyNames.DisplayName, "Sub-Window Height"); configUI.SetPropertyControlValue(PropertyNames.Amount3, ControlInfoPropertyNames.DisplayName, "Sub-Window Width Plus Height"); return configUI; } Quote Link to comment Share on other sites More sharing options...
MJW Posted March 15, 2019 Share Posted March 15, 2019 BTW, I'm using PDN version 4.1.5. Are you perhaps using the beta version? Quote Link to comment Share on other sites More sharing options...
xod Posted March 15, 2019 Share Posted March 15, 2019 Yes, I'm using PDN 4.1.6 beta version. Quote Link to comment Share on other sites More sharing options...
toe_head2001 Posted March 15, 2019 Author Share Posted March 15, 2019 Here's a reduced code sample to demonstrate the issue. 1) Run the plugin (be sure to click on OK) 2) Open another image with different dimensions 3) Open the plugin again. It will fail on the Assert. Spoiler using System.Drawing; using System.Collections.Generic; using PaintDotNet; using PaintDotNet.Effects; using PaintDotNet.PropertySystem; using System.Diagnostics; namespace BrokenPropertyEffect { public class BrokenPropertyEffectPlugin : PropertyBasedEffect { int maxValue; public BrokenPropertyEffectPlugin() : base("Broken Property", null, null, new EffectOptions() { Flags = EffectFlags.Configurable }) { } private enum PropertyNames { Amount1 } protected override PropertyCollection OnCreatePropertyCollection() { this.maxValue = EnvironmentParameters.SourceSurface.Width; List<Property> props = new List<Property> { new Int32Property(PropertyNames.Amount1, 1, 0, this.maxValue), }; return new PropertyCollection(props); } protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) { int propMaxValue = newToken.GetProperty<Int32Property>(PropertyNames.Amount1).MaxValue; Debug.Assert(propMaxValue == this.maxValue); base.OnSetRenderInfo(newToken, dstArgs, srcArgs); } protected override void OnRender(Rectangle[] renderRects, int startIndex, int length) { if (length == 0) return; for (int i = startIndex; i < startIndex + length; ++i) { Render(DstArgs.Surface, SrcArgs.Surface, renderRects[i]); } } private void Render(Surface dst, Surface src, Rectangle rect) { dst.CopySurface(src, rect.Location, rect); } } } 16 minutes ago, MJW said: BTW, I'm using PDN version 4.1.5. Are you perhaps using the beta version? This is not a new issue with the beta. It has existed for quite some time. 24 minutes ago, MJW said: It seems, from what I can tell so far, to work correctly. The UI Control uses the new MaxValue, but the underlying property retains the old one. Quote June 7th, 2023: Sorry about any broken images in my posts. The underlying DNS issue should be resolved soon. My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
MJW Posted March 15, 2019 Share Posted March 15, 2019 My plugin has never crashed, but when I added render code that depends on the controls, I discovered that the control's value is sometimes clamped to the wrong max value. Presumably if I'd used the controls for other purposes, it could crash with an exception. void Render(Surface dst, Surface src, Rectangle rect) { for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { dst[x, y] = ((x < Amount1) && (y < Amount2)) ? ColorBgra.Red :ColorBgra.White; } } } Quote Link to comment Share on other sites More sharing options...
xod Posted March 16, 2019 Share Posted March 16, 2019 It seems that OptionBasedLibrary does not have this issue. My RoundedRectangle plugin runs OK on new large canvas sizes. https://forums.getpaint.net/topic/111330-unfinished-plugins/page/6/?tab=comments#comment-551743 Quote Link to comment Share on other sites More sharing options...
toe_head2001 Posted March 16, 2019 Author Share Posted March 16, 2019 42 minutes ago, xod said: It seems that OptionBasedLibrary does not have this issue. That does not surprise me. Like I said before, it's an issue with IndirectUI. Quote June 7th, 2023: Sorry about any broken images in my posts. The underlying DNS issue should be resolved soon. My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
xod Posted March 16, 2019 Share Posted March 16, 2019 (edited) Yes, you're right. Here is the new MoveLinesOB plugin made with OptionBasedLibrary which has no issue. All you need is in the Dependency folder. http://www.mediafire.com/file/ugv34d6ekgdjop3/MoveLinesEffectOB.zip/file Edited March 16, 2019 by xod Quote Link to comment Share on other sites More sharing options...
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.