Jump to content

Buffered Graphics


Tim!
 Share

Recommended Posts

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.

Edited by Tim!
Link to comment
Share on other sites

Actually my Increase Local Contrast plugin does something very similar to this (with the buffer).

A general comment about this kind of effect implementation:

Doing all of the processing in OnSetRenderInfo (or in the first call to Render) makes the user interface quite annoying, especially when the class is based on PropertyBasedEffect. This is because the IndirectUI triggers OnSetRenderInfo quite often which freezes the GUI for a few seconds.

With a custom GUI (for Effect class as you also use in the example) one can make a timer which doesn't trigger the update until e.g. 300ms after the latest change done by the user. This means that the update will not start (and freeze the GUI) while the user is dragging a trackbar.

Michael Vinther

Link to comment
Share on other sites

Actually my Increase Local Contrast plugin does something very similar to this (with the buffer).

A general comment about this kind of effect implementation:

Doing all of the processing in OnSetRenderInfo (or in the first call to Render) makes the user interface quite annoying, especially when the class is based on PropertyBasedEffect. This is because the IndirectUI triggers OnSetRenderInfo quite often which freezes the GUI for a few seconds.

With a custom GUI (for Effect class as you also use in the example) one can make a timer which doesn't trigger the update until e.g. 300ms after the latest change done by the user. This means that the update will not start (and freeze the GUI) while the user is dragging a trackbar.

This is actually significantly improved for Paint.NET v3.5. The UI never blocks on token updates, and instead queues them to the background controller thread and tells it to start over. The controller thread then grabs the latest token (i.o.w. if you make 10 rapid changes before it reacts, it only picks up the last one) after it is able to stop the rendering threads.

So, in other words, please use IndirectUI anyway.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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.

 Share

×
×
  • Create New...