Tim! Posted March 18, 2009 Share Posted March 18, 2009 (edited) 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 May 7, 2011 by Tim! Quote Link to comment Share on other sites More sharing options...
MichaelVinther Posted March 19, 2009 Share Posted March 19, 2009 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. Quote Michael Vinther Link to comment Share on other sites More sharing options...
Rick Brewster Posted March 20, 2009 Share Posted March 20, 2009 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. 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...
MichaelVinther Posted March 20, 2009 Share Posted March 20, 2009 That sounds good. I will make a new version using IndirectUI as soon as v3.5 is released then - until then it is too annoying with the freezing :wink: Quote Michael Vinther Link to comment Share on other sites More sharing options...
Tim! Posted March 25, 2009 Author Share Posted March 25, 2009 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 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.