midora Posted June 1, 2013 Share Posted June 1, 2013 (edited) A simple template showing how to add properties (IndirectUi controls) to an effect dialog. The template contains all available variants of properties in Paint.NET 3.5.11. Microsoft Visual C# 2010 project: ExamplePropertyBasedEffect.zip Screenshot: In the meantime Rick released Paint.NET 4 Alpha. So here is a second project containing the additional controls available in Paint.NET 4 (DoubleVector3Property as PropertyControlType Slider and RollBallAndSliders). Remember this is an alpha version. Everything may change. This second project needs references to the libs in Paint.NET 4 and the Target Framework must be '.NET Framework 4'. Plugins using this framework will not run in Paint.NET 3.5.11. So if you do not need the additional controls then stay with the references to the libs in Paint.NET 3.5.11 (Ricks advice). Microsoft Visual C# 2010 project: ExamplePropertyBasedEffectPdn4.zip Screenshot: using System; using System.Collections.Generic; using System.Drawing; using PaintDotNet; using PaintDotNet.IndirectUI; using PaintDotNet.Effects; using PaintDotNet.PropertySystem; namespace ExamplePropertyBased { public sealed class ExamplePropertyBasedEffect : PropertyBasedEffect { // ---------------------------------------------------------------------- /// <summary> /// Defines an abstract name for the effect class /// </summary> private static Type ClassType = typeof(ExamplePropertyBasedEffect); // ---------------------------------------------------------------------- /// <summary> /// Defines a user friendly name used for menu and dialog caption /// </summary> private static string StaticName { get { return ClassType.Name; } } // ---------------------------------------------------------------------- /// <summary> /// Defines an image used for for menu and dialog caption (may be null) /// </summary> private static Bitmap StaticImage { get { return Properties.Resources.EffectIcon; } } // ---------------------------------------------------------------------- /// <summary> /// Defines the submenu name where the effect should be placed. /// Prefered is one of the SubmenuNames constants (i.e. SubmenuNames.Render) /// (may be null) /// </summary> private static string StaticSubMenuName { get { return "Development"; } } // ---------------------------------------------------------------------- /// <summary> /// Constructs an ExamplePropertyBasedEffect instance /// </summary> public ExamplePropertyBasedEffect() : base(StaticName, StaticImage, StaticSubMenuName, EffectFlags.Configurable) { } // ---------------------------------------------------------------------- /// <summary> /// Destroys the ExamplePropertyBasedEffect instance /// Remove me. Only needed for special cases. /// If resources must be freed then the prefered way is to override OnDispose() /// </summary> ~ExamplePropertyBasedEffect() { } // ---------------------------------------------------------------------- /// <summary> /// Identifiers of the properties used by the effect /// </summary> private enum PropertyNames { EnumDropDown, EnumRadioButtons, BooleanCheckBox, StringTextBox, Int32Slider, Int32IncrementButton, Int32ColorWheel, DoubleSlider, DoubleAngleChooser, DoubleVectorSlider, DoubleVectorPanAndSlider } /// <summary> /// Identifiers used in a dropdown choice property /// </summary> private enum DropDownEnum { DropDown1, DropDown2, DropDown3 } /// <summary> /// Identifiers used in a radiobutton choice property /// </summary> private enum RadioButtonEnum { RadioButton1, RadioButton2, RadioButton3 } // Settings of the properties private DropDownEnum propEnumDropDown; private RadioButtonEnum propEnumRadioButtons; private bool propBooleanCheckBox; private string propStringTextBox; private int propInt32Slider; private int propInt32IncrementButton; private int propInt32ColorWheel; private double propDoubleSlider; private double propDoubleAngleChooser; private Pair<double, double> propDoubleVectorSlider; private Pair<double, double> propDoubleVectorPanAndSlider; // ---------------------------------------------------------------------- /// <summary> /// Configure the properties of the effect. /// This just creates the properties not the controls used in the dialog. /// These properties are defining the content of the EffectToken. /// </summary> protected override PropertyCollection OnCreatePropertyCollection() { // Add properties of all types and control types (always the variant with minimal parameters) List<Property> props = new List<Property> { StaticListChoiceProperty.CreateForEnum<DropDownEnum>( // is PropertyControlType.DropDown PropertyNames.EnumDropDown, DropDownEnum.DropDown1, false), StaticListChoiceProperty.CreateForEnum<RadioButtonEnum>( // set PropertyControlType.RadioButton PropertyNames.EnumRadioButtons, RadioButtonEnum.RadioButton1, false), new BooleanProperty(PropertyNames.BooleanCheckBox), // is PropertyControlType.CheckBox new StringProperty(PropertyNames.StringTextBox), // is PropertyControlType.TextBox new Int32Property(PropertyNames.Int32Slider), // is PropertyControlType.Slider new Int32Property(PropertyNames.Int32IncrementButton), // set PropertyControlType.IncrementButton new Int32Property(PropertyNames.Int32ColorWheel, // set PropertyControlType.ColorWheel 0, 0x000000, 0xffffff), // range can not be changed! new DoubleProperty(PropertyNames.DoubleSlider, // is PropertyControlType.Slider 0.0, -1.0, 1.0), new DoubleProperty(PropertyNames.DoubleAngleChooser, // set PropertyControlType.AngleChooser 0.0, -180.0, 180.0), // only two ranges allowed [-180 180] or [0 360]! new DoubleVectorProperty(PropertyNames.DoubleVectorSlider, // set PropertyControlType.Slider Pair.Create(0.0, 0.0), Pair.Create(-1.0, -1.0), Pair.Create(1.0, 1.0)), new DoubleVectorProperty(PropertyNames.DoubleVectorPanAndSlider, // is PropertyControlType.PanAndSlider Pair.Create(0.0, 0.0), Pair.Create(-1.0, -1.0), Pair.Create(1.0, 1.0)) }; // Add rules List<PropertyCollectionRule> propRules = new List<PropertyCollectionRule>(); // no rules defined (eliminate parameter if you like) return new PropertyCollection(props, propRules); } /* OnCreatePropertyCollection */ // ---------------------------------------------------------------------- /// <summary> /// Configure the user interface of the effect. /// You may change the default control type of your properties or /// modify/suppress the default texts in the controls. /// </summary> protected override ControlInfo OnCreateConfigUI(PropertyCollection props) { ControlInfo configUI = CreateDefaultConfigUI(props); // Set control types of some properties to change them to the expected controls configUI.SetPropertyControlType(PropertyNames.EnumRadioButtons, PropertyControlType.RadioButton); configUI.SetPropertyControlType(PropertyNames.Int32IncrementButton, PropertyControlType.IncrementButton); configUI.SetPropertyControlType(PropertyNames.Int32ColorWheel, PropertyControlType.ColorWheel); configUI.SetPropertyControlType(PropertyNames.DoubleAngleChooser, PropertyControlType.AngleChooser); configUI.SetPropertyControlType(PropertyNames.DoubleVectorSlider, PropertyControlType.Slider); // Change individual properties (see ControlInfoPropertyNames) #if false configUI.SetPropertyControlValue(PropertyNames.EnumDropDown, ControlInfoPropertyNames.DisplayName, "My DisplayName); // Change DisplayName (default is the PropertyNames identifier) configUI.SetPropertyControlValue(PropertyNames.EnumDropDown, ControlInfoPropertyNames.DisplayName, String.Empty); // Suppress display of DisplayName PropertyControlInfo pci = configUI.FindControlForPropertyName(PropertyNames.EnumDropDown); pci.SetValueDisplayName(DropDownEnum.DropDown1, "My DropDown1 Text"); // #endif return configUI; } /* OnCreateConfigUI */ // ---------------------------------------------------------------------- /// <summary> /// Configure the dialog of the effect. /// </summary> protected override void OnCustomizeConfigUIWindowProperties(PropertyCollection props) { #if false props[ControlInfoPropertyNames.WindowTitle].Value = "Hello world!"; // props[ControlInfoPropertyNames.WindowWidthScale].Value = 2.0; // WindowWidthScale must be a double, range seems to be [1.0, 2.0] (Default 1.0) #endif props[ControlInfoPropertyNames.WindowIsSizable].Value = true; // don't set this property if your dialog does not contain a lot of controls base.OnCustomizeConfigUIWindowProperties(props); } /* OnCustomizeConfigUIWindowProperties */ // ---------------------------------------------------------------------- /// <summary> /// Called after the token of the effect changed. /// This method is used to read all values of the token to instance variables. /// These instance variables are then used to render the surface. /// </summary> protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken effectToken, RenderArgs dstArgs, RenderArgs srcArgs) { // Read the current settings of the properties propEnumDropDown = (DropDownEnum)effectToken.GetProperty<StaticListChoiceProperty>(PropertyNames.EnumDropDown).Value; propEnumRadioButtons = (RadioButtonEnum)effectToken.GetProperty<StaticListChoiceProperty>(PropertyNames.EnumRadioButtons).Value; propBooleanCheckBox = effectToken.GetProperty<BooleanProperty>(PropertyNames.BooleanCheckBox).Value; propStringTextBox = effectToken.GetProperty<StringProperty>(PropertyNames.StringTextBox).Value; propInt32Slider = effectToken.GetProperty<Int32Property>(PropertyNames.Int32Slider).Value; propInt32IncrementButton = effectToken.GetProperty<Int32Property>(PropertyNames.Int32IncrementButton).Value; propInt32ColorWheel = effectToken.GetProperty<Int32Property>(PropertyNames.Int32ColorWheel).Value; propDoubleSlider = effectToken.GetProperty<DoubleProperty>(PropertyNames.DoubleSlider).Value; propDoubleAngleChooser = effectToken.GetProperty<DoubleProperty>(PropertyNames.DoubleAngleChooser).Value; propDoubleVectorSlider = effectToken.GetProperty<DoubleVectorProperty>(PropertyNames.DoubleVectorSlider).Value; propDoubleVectorPanAndSlider = effectToken.GetProperty<DoubleVectorProperty>(PropertyNames.DoubleVectorPanAndSlider).Value; base.OnSetRenderInfo(effectToken, dstArgs, srcArgs); } /* OnSetRenderInfo */ // ---------------------------------------------------------------------- /// <summary> /// Render an area defined by a list of rectangles /// This function may be called multiple times to render the area of // the selection on the active layer /// </summary> protected override void OnRender(Rectangle[] rois, int startIndex, int length) { for (int i = startIndex; i < startIndex + length; ++i) { RenderRectangle(DstArgs.Surface, SrcArgs.Surface, rois[i]); } } // ---------------------------------------------------------------------- /// <summary> /// The function to render one rectangle of the surface /// </summary> private void RenderRectangle(Surface dst, Surface src, Rectangle renderRect) { // Add your render code here // Uncomment the basic example you like to see // Copy the original content of the active layer to the selection //dst.CopySurface(src,renderRect.Location,renderRect); // Fill the selection of the active layer with transparent white (0x00FFFFFF) //dst.Clear(renderRect, ColorBgra.Transparent); // Fill the selection of the active layer with the active primary color dst.Clear(renderRect, EnvironmentParameters.PrimaryColor); } } } Edited October 19, 2013 by midora 1 Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted June 1, 2013 Share Posted June 1, 2013 Great work Midora! A suggestion: Is it worth giving the alternative to a menu icon held in Resources? public static Bitmap StaticIcon { get { return new Bitmap(typeof(EffectPlugin), "EffectPluginIcon.png"); } } // Entire StaticIcon method (above) can be replaced by the StaticImage method (below) referencing a PNG icon held in Resources public static Bitmap StaticImage { get { return Properties.Resources.Icon; } } Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker Link to comment Share on other sites More sharing options...
midora Posted June 2, 2013 Author Share Posted June 2, 2013 Is it worth giving the alternative to a menu icon held in Resources? Let us collect some feedback like yours and then update the example. Makes no sense to add any variation in an example, but we may fill some more 'good to know' gaps. 1 Quote Link to comment Share on other sites More sharing options...
Red ochre Posted June 2, 2013 Share Posted June 2, 2013 Excellent! very useful. I'm a bit confused by Visual Studio though - I know the project contains Bin/Release but I can't get it show up within VS? I can find it with normal windows explorer but in VS it only shows empty files even after 'show all files' is selected. Anyway, I set the Build Events to 'copy "$(TargetPath)" "C:\Program Files\Paint.NET\Effects" /y' (as previously recommended by Null54) and it builds and copies into Pdn fine. I have also 'exported as a template' and will continue to experiment with it. I am currently exploring and comparing the Sepcot/EER template, the full code generated by codelab and examples supplied by Null54. It is very useful to have this example code too. It may be worth adding the familiar y, x loops in the RenderRectangles method? private void RenderRectangle(Surface dst, Surface src, Rectangle renderRect) { // Add your render code here /*ColorBgra cp; for (int y = rect.Top; y < rect.Bottom; y++) { for (int x = rect.Left; x < rect.Right; x++) { cp = src[x,y]; int B = cp.B;int G = cp.G;int R = cp.R;int A = cp.A; // manipulate pixel values here // re assemble cp = ColorBgra.FromBgra( Int32Util.ClampToByte(, Int32Util.ClampToByte(G), Int32Util.ClampToByte®, Int32Util.ClampToByte(A)); dst[x,y] = cp; } }*/ // Uncomment the basic example you like to see // Copy the original content of the active layer to the selection //dst.CopySurface(src,renderRect.Location,renderRect); // Fill the selection of the active layer with transparent white (0x00FFFFFF) //dst.Clear(renderRect, ColorBgra.Transparent); // Fill the selection of the active layer with the active primary color dst.Clear(renderRect, EnvironmentParameters.PrimaryColor); } Hopefully I can give you more feedback later.Many Thanks Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted June 6, 2013 Share Posted June 6, 2013 ....It may be worth adding the familiar y, x loops in the RenderRectangles method? And how about an example of how the controls are accessed within the Render loops? Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker Link to comment Share on other sites More sharing options...
midora Posted June 6, 2013 Author Share Posted June 6, 2013 It may be worth adding the familiar y, x loops in the RenderRectangles method? And how about an example of how the controls are accessed within the Render loops? I will keep the example up-to-date once a month. The access to the controls is shown in SetRenderInfo (propXXX=...). They are just not used in the RenderRect() function because the main focus of this example was to show all available controls. But something simple could be added. Quote Link to comment Share on other sites More sharing options...
midora Posted October 19, 2013 Author Share Posted October 19, 2013 Update to Paint.NET 4. Quote Link to comment Share on other sites More sharing options...
Simon Brown Posted October 20, 2013 Share Posted October 20, 2013 Edit: Never mind Quote Link to comment Share on other sites More sharing options...
guix Posted October 23, 2013 Share Posted October 23, 2013 Very useful, thanks 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.