Jump to content

Tim!

Members
  • Posts

    37
  • Joined

  • Last visited

Posts posted by Tim!

  1. Great to see that the file types have been updated.

     

    I've just been trying to compile the code with VS2017 targetting .NET 4.8. I have a couple of issues with SvgFileType.cs.

     

    Firstly, the obsolete constructor. Have I fixed it correctly?

     

        public class SvgFileType : FileType
        {
            //
            // Obsolete.
            //
            //public SvgFileType()
            //    : base(
            //        "Scalable Vector Graphics",       // name
            //        FileTypeFlags.SupportsLoading,    // flags
            //        new[] {".svg", ".svgz"})          // extensions
            //{
            //}
    
            //
            // New and improved.
            //
            public SvgFileType()
                : base
                (
                    "Scalable Vector Graphics",         // name
                    new FileTypeOptions                 // options
                    {
                        LoadExtensions = new string[] { ".svg", ".svgx" }
                    }
                )
            {
                //this.Options.LoadExtensions = new List<string> { ".svg", ".svgx" };
                //this.Options.LoadExtensions = new string[] { ".svg", ".svgx" };
                //FileTypeOptions fto = new FileTypeOptions
                //{
                //    LoadExtensions = new List<string> { ".svg", ".svgx" }
                //};
            }

     

     

    Secondly, I get 4 compile errors with SvgVisualElement.Visible being read only, e.g.:

     

    Quote

    Error    CS0200    Property or indexer 'SvgVisualElement.Visible' cannot be assigned to -- it is read only    SvgFileTypePlugin    C:\Users\me\Dowloads\Scalable-Vector-Graphics-Plugin-for-Paint.NET-0.3-alpha\Scalable-Vector-Graphics-Plugin-for-Paint.NET-0.3-alpha\SvgFileType\SvgFileType.cs

     

    Would it be OK to comment these out. or is there a new equivalent?

  2. If you want your effect to be used several times on distinct regions, you'll need a "reset" or "run now" button in the parameters dialog that, when it is pressed will simply rest this "mylock.averageComputed" flag to false, because static objects remain in their current state in memory, as long as the effect DLL is loaded in PaintDotNet.

    Hi verdy_p,

    You can reset the flag in OnSetRenderInfo()

  3. I've tried the following plug-in experiment. It gives inconsistent results, but Loop 2 is usually a bit faster than Loop 1 (the JIT compiler has done its job well). The Windows Forms experiment gave a consistent and marked speed difference. So why the difference?

    Hidden Content:
    using System.Diagnostics;
    using System.Drawing;
    using System.Windows.Forms;
    using PaintDotNet;
    using PaintDotNet.Effects;
    
    namespace EffectsPluginTemplate1
    {
       public class EffectPlugin
           : PaintDotNet.Effects.Effect
       {
           public static string StaticName
           {
               get
               {
                   return "Effect Plugin";
               }
           }
    
           public static Bitmap StaticIcon
           {
               get
               {
                   return new Bitmap(typeof(EffectPlugin), "EffectPluginIcon.png");
               }
           }
    
           public static string StaticSubMenuName
           {
               get
               {
                   //return null; // Use for no submenu
    #if DEBUG
                   return "_test"; // Use for custom submenu
    #else
                   return SubmenuNames.Render;
    #endif
               }
           }
    
           public EffectPlugin()
               : base(EffectPlugin.StaticName, EffectPlugin.StaticIcon, EffectPlugin.StaticSubMenuName, EffectFlags.Configurable | EffectFlags.SingleThreaded)
           {
               show_msgbox = false;
           }
    
           ~EffectPlugin()
           {
               if (show_msgbox)
               {
                   string str = "";
                   str += "Loop 1: " + loop1_ticks + "\r\n";
                   str += "Loop 2: " + loop2_ticks + "\r\n";
                   str += "Loop 1 / Loop 2: " + loop1_ticks / loop2_ticks + "\r\n";
                   str += "Ticks per second: " + Stopwatch.Frequency;
                   MessageBox.Show(str);
               }
           }
    
           public override EffectConfigDialog CreateConfigDialog()
           {
               return new EffectPluginConfigDialog();
           }
    
           protected override void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs)
           {
               loop1_ticks = 0;
               loop2_ticks = 0;
               show_msgbox = true;
               base.OnSetRenderInfo(parameters, dstArgs, srcArgs);
           }
    
           private Stopwatch sw = new Stopwatch();
           private float loop1_ticks;
           private float loop2_ticks;
           private bool show_msgbox;
    
           public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length)
           {
               PdnRegion selectionRegion = EnvironmentParameters.GetSelection(srcArgs.Bounds);
    
               sw.Reset();
               sw.Start();
    
               //
               // Loop 1
               //
               for (int i = startIndex; i < startIndex + length; ++i)
               {
                   Rectangle rect = rois[i];
    
                   for (int y = rect.Top; y < rect.Bottom; ++y)
                   {
                       for (int x = rect.Left; x < rect.Right; ++x)
                       {
                           // Render Code Here
                           dstArgs.Surface[x, y] = srcArgs.Surface[rect.Right - x - 1, y];
                       }
                   }
               }
    
               sw.Stop();
               loop1_ticks += sw.ElapsedTicks;
    
               sw.Reset();
               sw.Start();
    
               //
               // Loop 2
               //
               int r = startIndex + length;
               for (int i = startIndex; i < r; i++)
               {
                   Rectangle rect = rois[i];
                   int left = rect.Left;
                   int right = rect.Right;
                   int top = rect.Top;
                   int bottom = rect.Bottom;
                   for (int y = top; y < bottom; y++)
                   {
                       for (int x = left; x < right; x++)
                       {
                           dstArgs.Surface[x, y] = srcArgs.Surface[right - x - 1, y];
                       }
                   }
               }
    
               sw.Stop();
               loop2_ticks += sw.ElapsedTicks;
           }
       }
    }

  4. Just use csc with the optimize code option... or optimize code in visual studio project properties. It does all this.

    Also, I believe the Effects API in Paint.NET does some refactoring.

    I checked optimise code in project properties (Release). Loop 2 runs 3.64 times faster than Loop 1. Here's my ILDASM where Loop 1 uses call instance and Loop 2 uses ldloc.s:

    Hidden Content:
    .method private hidebysig instance void buttonLoops_Click(object sender, class [mscorlib]System.EventArgs e) cil managed
    {
       .maxstack 5
       .locals init (
           [0] class [system]System.Diagnostics.Stopwatch sw,
           [1] valuetype [system.Drawing]System.Drawing.Rectangle rect,
           [2] string str,
           [3] int32 y,
           [4] int32 x,
           [5] int32 left,
           [6] int32 right,
           [7] int32 top,
           [8] int32 bottom,
           [9] int32 V_9,
           [10] int32 V_10,
           [11] object CS$0$0000,
           [12] object[] CS$0$0001,
           [13] object CS$0$0002,
           [14] object[] CS$0$0003)
       L_0000: call class [system.Windows.Forms]System.Windows.Forms.Cursor [system.Windows.Forms]System.Windows.Forms.Cursors::get_WaitCursor()
       L_0005: call void [system.Windows.Forms]System.Windows.Forms.Cursor::set_Current(class [system.Windows.Forms]System.Windows.Forms.Cursor)
       L_000a: newobj instance void [system]System.Diagnostics.Stopwatch::.ctor()
       L_000f: stloc.0 
       L_0010: ldloca.s rect
       L_0012: ldc.i4.0 
       L_0013: ldc.i4.0 
       L_0014: ldc.i4 0x2710
       L_0019: ldc.i4 0x2710
       L_001e: call instance void [system.Drawing]System.Drawing.Rectangle::.ctor(int32, int32, int32, int32)
       L_0023: ldstr ""
       L_0028: stloc.2 
       L_0029: ldloc.0 
       L_002a: callvirt instance void [system]System.Diagnostics.Stopwatch::Start()
       L_002f: ldloca.s rect
       L_0031: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Top()
       L_0036: stloc.3 
       L_0037: br.s L_0059
       L_0039: ldloca.s rect
       L_003b: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Left()
       L_0040: stloc.s x
       L_0042: br.s L_004a
       L_0044: ldloc.s x
       L_0046: ldc.i4.1 
       L_0047: add.ovf 
       L_0048: stloc.s x
       L_004a: ldloc.s x
       L_004c: ldloca.s rect
       L_004e: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Right()
       L_0053: blt.s L_0044
       L_0055: ldloc.3 
       L_0056: ldc.i4.1 
       L_0057: add.ovf 
       L_0058: stloc.3 
       L_0059: ldloc.3 
       L_005a: ldloca.s rect
       L_005c: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Bottom()
       L_0061: blt.s L_0039
       L_0063: ldloc.0 
       L_0064: callvirt instance void [system]System.Diagnostics.Stopwatch::Stop()
       L_0069: ldloc.2 
       L_006a: stloc.s CS$0$0000
       L_006c: ldc.i4.4 
       L_006d: newarr object
       L_0072: stloc.s CS$0$0001
       L_0074: ldloc.s CS$0$0001
       L_0076: ldc.i4.0 
       L_0077: ldloc.s CS$0$0000
       L_0079: stelem.ref 
       L_007a: ldloc.s CS$0$0001
       L_007c: ldc.i4.1 
       L_007d: ldstr "Loop 1: "
       L_0082: stelem.ref 
       L_0083: ldloc.s CS$0$0001
       L_0085: ldc.i4.2 
       L_0086: ldloc.0 
       L_0087: callvirt instance int64 [system]System.Diagnostics.Stopwatch::get_ElapsedTicks()
       L_008c: box int64
       L_0091: stelem.ref 
       L_0092: ldloc.s CS$0$0001
       L_0094: ldc.i4.3 
       L_0095: ldstr "\r\n"
       L_009a: stelem.ref 
       L_009b: ldloc.s CS$0$0001
       L_009d: call string [mscorlib]System.String::Concat(object[])
       L_00a2: stloc.2 
       L_00a3: ldloc.0 
       L_00a4: callvirt instance void [system]System.Diagnostics.Stopwatch::Reset()
       L_00a9: ldloc.0 
       L_00aa: callvirt instance void [system]System.Diagnostics.Stopwatch::Start()
       L_00af: ldloca.s rect
       L_00b1: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Left()
       L_00b6: stloc.s left
       L_00b8: ldloca.s rect
       L_00ba: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Right()
       L_00bf: stloc.s right
       L_00c1: ldloca.s rect
       L_00c3: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Top()
       L_00c8: stloc.s top
       L_00ca: ldloca.s rect
       L_00cc: call instance int32 [system.Drawing]System.Drawing.Rectangle::get_Bottom()
       L_00d1: stloc.s bottom
       L_00d3: ldloc.s top
       L_00d5: stloc.s V_9
       L_00d7: br.s L_00f1
       L_00d9: ldloc.s left
       L_00db: stloc.s V_10
       L_00dd: br.s L_00e5
       L_00df: ldloc.s V_10
       L_00e1: ldc.i4.1 
       L_00e2: add.ovf 
       L_00e3: stloc.s V_10
       L_00e5: ldloc.s V_10
       L_00e7: ldloc.s right
       L_00e9: blt.s L_00df
       L_00eb: ldloc.s V_9
       L_00ed: ldc.i4.1 
       L_00ee: add.ovf 
       L_00ef: stloc.s V_9
       L_00f1: ldloc.s V_9
       L_00f3: ldloc.s bottom
       L_00f5: blt.s L_00d9
       L_00f7: ldloc.0 
       L_00f8: callvirt instance void [system]System.Diagnostics.Stopwatch::Stop()
       L_00fd: ldloc.2 
       L_00fe: stloc.s CS$0$0002
       L_0100: ldc.i4.4 
       L_0101: newarr object
       L_0106: stloc.s CS$0$0003
       L_0108: ldloc.s CS$0$0003
       L_010a: ldc.i4.0 
       L_010b: ldloc.s CS$0$0002
       L_010d: stelem.ref 
       L_010e: ldloc.s CS$0$0003
       L_0110: ldc.i4.1 
       L_0111: ldstr "Loop 2: "
       L_0116: stelem.ref 
       L_0117: ldloc.s CS$0$0003
       L_0119: ldc.i4.2 
       L_011a: ldloc.0 
       L_011b: callvirt instance int64 [system]System.Diagnostics.Stopwatch::get_ElapsedTicks()
       L_0120: box int64
       L_0125: stelem.ref 
       L_0126: ldloc.s CS$0$0003
       L_0128: ldc.i4.3 
       L_0129: ldstr "\r\n"
       L_012e: stelem.ref 
       L_012f: ldloc.s CS$0$0003
       L_0131: call string [mscorlib]System.String::Concat(object[])
       L_0136: stloc.2 
       L_0137: ldloc.2 
       L_0138: ldstr "Ticks per second: "
       L_013d: ldsfld int64 [system]System.Diagnostics.Stopwatch::Frequency
       L_0142: box int64
       L_0147: call string [mscorlib]System.String::Concat(object, object, object)
       L_014c: stloc.2 
       L_014d: ldloc.2 
       L_014e: call valuetype [system.Windows.Forms]System.Windows.Forms.DialogResult [system.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
       L_0153: pop 
       L_0154: call class [system.Windows.Forms]System.Windows.Forms.Cursor [system.Windows.Forms]System.Windows.Forms.Cursors::get_Default()
       L_0159: call void [system.Windows.Forms]System.Windows.Forms.Cursor::set_Current(class [system.Windows.Forms]System.Windows.Forms.Cursor)
       L_015e: ret 
    }
    
    
    
    
    

  5. A common rendering inefficiency amongst many code samples is caused by the repetitive accessing of properties of the Rectangle class in for-loops in the Render method.

    Consider the following code sample. The second style is faster because it uses local variables to access the rectangle data. The first style makes repeated subroutine calls to access the rectangle data:

    Hidden Content:
    using System.Diagnostics;
    using System.Drawing;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
       public partial class Form1 : Form
       {
           public Form1()
           {
               InitializeComponent();
           }
    
           private void buttonLoops_Click(object sender, System.EventArgs e)
           {
               Cursor.Current = Cursors.WaitCursor;
    
               Stopwatch sw = new Stopwatch();
    
               Rectangle rect = new Rectangle(0, 0, 10000, 10000);
               string str = "";
    
               sw.Start();
    
               for (int y = rect.Top; y < rect.Bottom; y++)
               {
                   for (int x = rect.Left; x < rect.Right; x++)
                   {
                       int result = 0;
                   }
               }
    
               sw.Stop();
               str += "Loop 1: " + sw.ElapsedTicks + "\r\n";
    
               sw.Reset();
               sw.Start();
    
               int right = rect.Right;
               int left = rect.Left;
               int top = rect.Top;
               int bottom = rect.Bottom;
               for (int y = top; y < bottom; y++)
               {
                   for (int x = left; x < right; x++)
                   {
                       int result = 0;
                   }
               }
    
               sw.Stop();
               str += "Loop 2: " + sw.ElapsedTicks + "\r\n";
    
               str += "Ticks per second: " + Stopwatch.Frequency;
    
               MessageBox.Show(str);
    
               Cursor.Current = Cursors.Default;
           }
       }
    }
    

    Another common problem is unecessary recalculation of for-loop limits every time the loop is executed. Limits should be calculated before the for-loops. In Example 2 below, for an image of 800x600px, it saves 480598 computations (600 + 800 x 600 - 2) in comparison to Example 1:

    Hidden Content:
               //
               // Example 1
               //
               int a = ComputeA();
               int b = ComputeB();
               int left = rect.Left;
               int right = rect.Right;
               int top = rect.Top;
               int bottom = rect.Bottom;
               for (int y = top; y < bottom - a; y++)
               {
                   for (int x = left; x < right - b; x++)
                   {
                       int result = 0;
                   }
               }
    
               //
               // Example 2
               //
               int a = ComputeA();
               int b = ComputeB();
               int left = rect.Left;
               int right = rect.Right - b;
               int top = rect.Top;
               int bottom = rect.Bottom - a;
               for (int y = top; y < bottom; y++)
               {
                   for (int x = left; x < right; x++)
                   {
                       int result = 0;
                   }
               }
    

  6. This is great but sometimes, dragging the Tesserae Width slider, or launching the plugin makes a popup saying something like a NullReferenceException (it talks about an object and its missing reference...searching this in google returns it's a System.NullReferenceException) appear. . .
    I've just had another look at that problem. I should have put a lock statement in OnSetRenderInfo(). Thanks for highlighting it.
  7. Is it possible to change the tile size?
    Yes. Tile height and width of the tessarae can be changed independently using sliders ranging from 2px to 100px. I'll update my original post with a better description.
    Also, is it possible to use a fully transparent colour as the secondary colour?
    No, but I could add this capability to a future release. Currently the tesserae are drawn on opaque background (it's the Secondary colour with Alpha set to fully opaque 255).
  8. Thanks for your suggestions. I've now published it here (and here)

    Rounded Corners: Hmmm. That involves calculating eight tangents and four x-y offsets and drawing an arc about each of the four x-y offsets between pairs of tangents. Easy enough to say, but tricky to implement. I'll have a think...

    EDIT 1: Updated links for new forum.

  9. Hi All,

     

    Here's my Rounded Rectangle effect. (A full list of my effects is here.)

     

    Rounded Rectangle - Version 1.5.0.0 - 27 February 2017

    ©Copyright Tim Mathias 2017. All rights reserved.

    USE AT YOUR OWN RISK.

     

    Menu: Effects -> Render -> Rounded Rectangle...

    LavEnt.Effects.RoundedRectangle.1.5.0.0.zip

     

    Example 1

    Sample - Rounded Rectangle 1.png

     

    Example 2

    Sample - Rounded Rectangle 3.jpg

     

    The Rounded Rectanlge effect uses the Primary and Secondary colours and the current brush width to draw a rectangle with rounded corners into the current selection. The roundness of the corners, the border outline and fill type are user-selectable.

     

    You can have multiple selections (Ctrl key). If there's no selection, it uses the entire canvas. The selection can be any shape (rectangle, lasso, ellipse or magic wand).

     

    After drawing the first shape, you can change the colours and brush width for subsequent shapes without having to re-open the config dialog. Just change them in Paint.NET's toolbars and then press Ctrl+F to paint into the selection with the new colours and brush width.

     

    Note: If you choose corner radii whose sum is greater than the side length, the radii will be scaled to fit the side length. The scaling is indicated by a green bar.

     

    Version 1.5.0.0 - 27 February 2017:

    • Recompiled for .NET 4.6.1.

    Version 1.3.0.0 - 14 May 2009:

    • Added a lock statement to prevent multiple simultaneous renders.
    • Removed cross-threaded calls.
    • Removed UI bug where independent radii were overwritten when typing a new top-left x radius.

    Version 1.2.0.0 - 30 Mar 2009 (789 downloads):

    • Added a supplementary brush width slider to the config dialog. Note: It reverts to the brush width set in the Paint.NET toolbar every time the dialog opens and when re-applying the effect using Ctrl+F.
    • Added a fill overlap slider. It controls by how much the fill colour overlaps the outline colour. It's useful for removing gaps that sometimes appear due to antialiasing. The overlap ranges from zero to half the brush width. To see it in action use semi-transparent colours for the outline and fill, then move the Fill Overlap slider.
    • Added a Reset Radii button.
    • Added a Show Original checkbox.
    • Improved slider performance.
    • Improved radii UI logic.

    Version 1.1.0.0 - 16 Mar 2009 (266 downloads):

    • Added support for the number keypad.
    • Improved outline-fill algorithm so that semi-transparent colours don't overlap.
    • Removed UI glitch when moving radii sliders.

    Version 1.0.0.0 (168 downloads):

    • Original.

     

  10. Hi All,

     

    Here's my Mosaic effect. (A full list of my effects is here.)

     

    Mosaic - Version 1.3.0.0 - 27 February 2017

    ©Copyright Tim Mathias 2017. All rights reserved.

    USE AT YOUR OWN RISK.

     

    Menu: Effects -> Artistic -> Mosaic...

    LavEnt.Effects.Mosaic.1.3.0.0.zip

     

    Before

    Mosaic 1 - Before.png

     

    After

    Mosaic 1 - After.png

     

    The Mosaic effect converts a drawing into a mosaic pattern and uses the Secondary colour as the adhesive or grout between the tesserae.

    Tessera Width: 2px to 100px

    Tessera Height: 2px to 100px

    Tessera Colour: Centre spot sample or average.

    Tessera Shape Irregularity: 0% (square) to 50% of tessera side length.

    Adhesive Thickness: 0px to 50px.

    Antialias: On or off.

     

    Possible enhancements I might add in future:

    1. Mathematical formulae to generate the mosaic pattern; at the moment it just uses a random irregularity based on a square shape.
    2. Fully transparent Secondary colour so that the underlying image shows through the gaps between tessarae.

     

    Tip: Try using a dark semi-transparent Secondary colour for the adhesive.

     

    Version 1.3.0.0 - 27 February 2017:

    • Recompiled for .NET 4.6.1.

    Version 1.1.0.0 - 14 May 2009:

    • Added a lock statement to prevent multiple simultaneous renders.

    Version 1.0.0.0 - 30 March 2009 (813 downloads):

    • Original.
  11. bit too tile like.

    I value your comments but I don't know mean by that one. Mosaics are made of (usually) square tiles. One of the intrinsic properties of a mosaic is that it comprises versatile tesselating tesserae. ;-)

    Before

    post-54726-130480062643_thumb.png

    After

    post-54726-130480065799_thumb.png

    EDIT 1: Updated links for new forum.

  12. Tim actually those changes (#2 above) sound like what I was hoping for. I just didn't know how to word it. Not being a programmer the reasoning and wording is what gets most of us end users when asking for improvements.

    I've re-done the UI logic. All those multiple inter-dependencies is quite tricky. I've also added a few other things (see my original post).

  13. There are issues with using System.Windows.Forms.Timer with TrackBar. I use System.Threading.Timer instead. Add this example code to your ConfigDialog and replace all your calls to FinishTokenUpdate() with calls to FinishTokenUpdateDelayed().

    Hidden Content:
           #region FinishTokenUpdateDelayed
    
           private System.Threading.Timer timer1 = null;
           private System.Threading.TimerCallback timer1_callback = null;
           private const int timer1_due_time = 100; // ms
    
           private void CreateTimer1()
           {
               timer1_callback = new System.Threading.TimerCallback(timer1_Callback);
               timer1 = new System.Threading.Timer(timer1_callback, null, timer1_due_time, System.Threading.Timeout.Infinite);
           }
    
           /// 
           /// Remember to call DisposeTimer1() when dialog closes, e.g. in Form1_FormClosing()
           /// 
           private void DisposeTimer1()
           {
               if (timer1 != null)
               {
                   timer1.Dispose();
                   timer1 = null;
               }
           }
    
           private void ChangeTimer1()
           {
               if (timer1 == null)
               {
                   CreateTimer1();
               }
               else
               {
                   timer1.Change(timer1_due_time, System.Threading.Timeout.Infinite);
               }
           }
    
           /// 
           /// Call FinishTokenUpdateDelayed() instead of FinishTokenUpdate()
           /// 
           private void FinishTokenUpdateDelayed()
           {
               ChangeTimer1();
           }
    
           private void timer1_Callback(Object obj)
           {
               DisposeTimer1();
               FinishTokenUpdate();
           }
    
           #endregion
    

  14. Hi All,

    I've just written another plugin called Mosaic. It converts a drawing into mosaic pattern and uses the Secondary colour as the adhesive or grout between the tesserae. I was wondering what would be the best submenu for it: Distort, Render or Stylize? (This BETA version is in the _test submenu for now.)

    A possible enhancement I might add in future would be mathematical formulae to generate the mosaic pattern; at the moment it just uses a random irregularity based on a square shape.

    EDIT 1: Updated the effect by adding a tesserae colour computation option and removed the annoying message box.

    EDIT 2: Removed beta effect zip file because I've published it here (and here). Thanks for your suggestions.

    EDIT 3: Updated hyperlinks for new forum.

  15. Ok, thanks for the hint about the dialog. The example plugin I based mine on just had an empty form so I assumed that I had to make the GUI from scratch.

    About multithreading, it is not that simple. My algorithm is a multi-pass IIR filter so I cannot just compute any pixel independant of the others. Actually everything is computed the first time Render() is called with a new parameter token in a lock() { } section so any following calls (in the same thread or others) will just return pixels. This is why the progress bar appears to stop for a long time at 0%.

    Hi Michael,

    This could be worth a read: Buffered Graphics

  16. I decided to write this topic after reading this about a multi-pass IIR filter.

    About multithreading, it is not that simple. My algorithm is a multi-pass IIR filter so I cannot just compute any pixel independant of the others. Actually everything is computed the first time Render() is called with a new parameter token in a lock() { } section so any following calls (in the same thread or others) will just return pixels. This is why the progress bar appears to stop for a long time at 0%.

    I hope this bit of sample code helps. Option 1 is preferrable to Option 2 if possible. I've also shown how to implement "Show Original Image".

    Hidden Content:
    #define BUFFER
    
    public class MyEffect : PaintDotNet.Effects.Effect
    {
       private MyEffectConfigToken parameters = new MyEffectConfigToken();
    
       private RenderArgs buffer_render_args;
    
    #if BUFFER
       ~MyEffect()
       {
           DisposeAll();
       }
    
       private void DisposeAll()
       {
           try
           {
               if (buffer_render_args != null)
               {
                   buffer_render_args.Dispose();
               }
           }
           catch { }
       }
    #endif
    
       protected override void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs)
       {
           //MessageBox.Show("OnSetRenderInfo");
           if (parameters is MyEffectConfigToken)
           {
               GenerateSurface((MyEffectConfigToken)parameters, dstArgs, srcArgs);
           }
           base.OnSetRenderInfo(parameters, dstArgs, srcArgs);
       }
    
       public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length)
       {
           if (this.parameters.view_original)  // Set via a control in ConfigDialog. Set it to false when closing ConfigDialog so that Ctrl+F doesn't do nothing.
           {
               dstArgs.Surface.CopySurface(srcArgs.Surface, rois, startIndex, length);
           }
           else
           {
               //ComputeIIROnRois(buffer_render_args.Surface, rois, startIndex, length);  // Option 1
    #if BUFFER
               dstArgs.Surface.CopySurface(buffer_render_args.Surface, rois, startIndex, length);
    #endif
           }
       }
    
       private void GenerateSurface(MyEffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs)
       {
    #if BUFFER
           this.parameters.view_original = parameters.view_original;  // Ignore view state
    #endif
    
           if (buffer_render_args != null && this.parameters == parameters)  // Override == operator of MyEffectConfigToken (and associated operators)
           {
               return;
           }
    
           this.parameters = parameters;
    
           try
           {
    #if BUFFER
               DisposeAll();
               buffer_render_args = new RenderArgs(new Surface(srcArgs.Size));
    #else
               buffer_render_args = dstArgs;
    #endif
               buffer_render_args.Surface.CopySurface(srcArgs.Surface);
    
               //ComputeIIROnEntireBuffer(buffer_render_args);  // Option 2. (You can also access the GDI+ Graphics and Bitmap as required.)
           }
           catch (Exception ex)
           {
               MessageBox.Show("An error occurred while generating surface:nn" + ex.Message, "My Effect");
           }
       }
    }
    

    EDIT 1:

    • Option 2 could be made more efficient by doing something like:
      //ComputeIIROnRoi(buffer_render_args, EnvironmentParameters.GetSelection(srcArgs.Bounds));
    • I put the code block in a hidden block.

    EDIT 2:

    OnSetRenderInfo() should have lock statement something like this:

    Hidden Content:
            private Object the_lock = new Object();
    
           protected override void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs)
           {
               if (parameters is MyEffectConfigToken)
               {
                   lock (the_lock)
                   {
                       GenerateSurface((MyEffectConfigToken)parameters, dstArgs, srcArgs);
                   }
               }
               base.OnSetRenderInfo(parameters, dstArgs, srcArgs);
           }

    EDIT 3: Updated hyperlink for new forum.

  17. OOI, are you looking to use this in a canvas plugin or one with a simple dialog?

    I'm new to Paint.NET, so correct me if I'm wrong. Does a canvas plugin mean an Effect plugin without a config dialog.

    For effect plugins without a config dialog, the EnvironmentParametersEx would only need to be read-only; for effect plugins with config dialogs, the EvironmentParametersEx could both readable and writeable.

    As an example, my Rounded Rectangle config dialog could have a brush width selector initialised to the value in EnvironmentParametersEx.BrushWidth when the dialog opens, the user then changes it and then the new value is written to EnvironmentParametersEx.BrushWidth when the dialog closes.

    What do you think?

  18. > Not sure if this is just me but I can not use the Numberpad on the side of my keyboard when I try in input numbers. I had tried both with the Numlock on and off.

    Thanks for spotting that. That was an oversight. I've fixed it for version 1.1.

    > This could be an OCD thing but I think that if you have the Independent Radii checkbox checked, make changes to the corners and sliders and then uncheck the Independent Radii, then the values should be set to the top left enabled textfield and should also reset the sliders back to the defaults.

    I've thought about various implementations in an OCD way but I was too lazy actually implement any of them ;-) . One of my ideas was to have two sets of radii values; one for independent radii and another for non-independent radii. And perhaps a separate Reset button and an Apply Same or Apply to All button for the radii. I haven't received any complaints on my effects thread yet, so I'll leave it be for now.

  19. Hi All,

     

    Here are my effects:

    1. Mosaic - v1.3.0.0 - 27 February 2017
    2. Rounded Rectangle - v1.3.0.0 - 14 May 2009

     

     

    Mosaic - Version 1.3.0.0 - 27 February 2017

     

    Example

    Mosaic 1 - After.png

     

    Menu: Effects -> Artistic -> Mosaic...

     

    The Mosaic effect converts a drawing into a mosaic pattern and uses the Secondary colour as the adhesive or grout between the tesserae.

     

     

    Rounded Rectangle - Version 1.3.0.0 - 14 May 2009

     

    Example

    Sample - Rounded Rectangle 1.png

     

    Menu: Effects -> Render -> Rounded Rectangle...

     

    The Rounded Rectanlge effect uses the Primary and Secondary colours and the current brush width to draw a rectangle with rounded corners into the current selection. The roundness of the corners, the border outline and fill type are user-selectable.

  20. Tim check which one you've attached.

    this one comes up test in the effects menu

    do you not need this to be in a render

    also I'm unable to move any of the sliders at all unless I clik that independant radius box Is that the way you intend it to work?

    "_test": I'll change it when I finally publish it.

    Disabled sliders until Independent Radii checked: That's by design, but I was wondering whether it would be best to keep the sliders enabled and then automatically uncheck check (EDIT 1) Independent Radii when the sliders are scrolled, or hide the sliders when Independent Radii is not checked.

×
×
  • Create New...