Jump to content

PropertyBasedEffect template using IndirectUI


Recommended Posts

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: post-79572-0-11923800-1382210448_thumb.j

 

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: post-79572-0-37043500-1382210163_thumb.j

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 by midora
  • Upvote 1
midora%20signature.gif
Link to post
Share on other sites

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; } }
Link to post
Share on other sites

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.

  • Upvote 1
midora%20signature.gif
Link to post
Share on other sites

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

 

Red ochre Plugin pack............................................................... Diabolical Drawings

 

PdnForumSig2.jpg

Link to post
Share on other sites

....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?

Link to post
Share on other sites

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.

midora%20signature.gif
Link to post
Share on other sites
  • 4 months later...

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...