Sebastian17 Posted August 30, 2022 Share Posted August 30, 2022 (edited) Hello, inspired by two other plugins (Equirectangular Viewer Plugin / Paneling Effect Plugin) I like to use Codelab for horizontal picture shifting (adjusting the forward view of a 360°-panorama). So far I have done the GUI and the Shift operation: Quote // Name: // Submenu: // Author: // Title: // Version: // Desc:// Keywords: // URL: // Help: #region UICode IntSliderControl xRot = 0; // [-180,180] Rotation Shift [°] #endregion void Render(Surface dst, Surface src, Rectangle rect) { if (IsCancelRequested) return; // Delete any of these lines you don't need Rectangle selection = EnvironmentParameters.SelectionBounds; int deltaX = (selection.Right - selection.Left)-1; int centerX = (deltaX / 2) + selection.Left; int centerY = ((selection.Bottom - selection.Top) / 2) + selection.Top; int xSource = 0; if (xRot == 180){ xRot = -180; } int xOffset = (int) ((float)xRot / 360.0 * (float)deltaX); ColorBgra currentPixel; for (int y = rect.Top; y < rect.Bottom; y++) { for (int x = rect.Left; x < rect.Right; x++) { if (xOffset > 0){ xOffset -= deltaX; } xSource = x + xOffset; if (xSource < selection.Left){ xSource += selection.Right; } currentPixel = src[xSource,y]; dst[x,y] = currentPixel; } } } How do I create a vertical line (aiming help) in the middle of the picture that is only shown during the use of the GUI and disappears on pressing "OK" or "CANCEL"? If I understand it correctly, there is a way to check wheter "CANCEL" is pressed: Quote if (IsCancelRequested) ... Is there a similar way to check if "OK" is pressed? Greetings, Sebastian Edited August 30, 2022 by Sebastian17 Quote Link to comment Share on other sites More sharing options...
MJW Posted August 30, 2022 Share Posted August 30, 2022 4 hours ago, Sebastian17 said: Is there a similar way to check if "OK" is pressed? What I'm sure you're looking for is a way to distinguish the final rendering pass from the interactive rendering done in response to control changes. Unfortunately, there's no way to do that with IndirectUI plugins; though BoltBait, I, and others have long lobbied for that feature. The only current options are to add a checkbox control to specify the final rendering should be done (most inelegant and inconvenient for the user) or write a non-IndirectUI plugin (more difficult). 1 Quote Link to comment Share on other sites More sharing options...
Sebastian17 Posted August 30, 2022 Author Share Posted August 30, 2022 If I transfer the code to Visual c#, is there a specific event procedure that is called after pressing "OK"? Within that procedure can I set a global variable like "IsReturnPressed = true;" and is the render procedure called a final time afterwards? Or could I trigger it once more (and check the global variable within to determine what to do)? As I have no idea of the event chain I need some help with it. Greetings, Sebastian Quote Link to comment Share on other sites More sharing options...
BoltBait Posted August 30, 2022 Share Posted August 30, 2022 What @MJW said. If you convert your plugin to use vs instead of CodeLab, you can do what you want as long as you stop using IndirectUI. You’ll need to code everything yourself. Look around, there is a template to get you started. Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
MJW Posted August 30, 2022 Share Posted August 30, 2022 2 hours ago, Sebastian17 said: If I transfer the code to Visual c#, is there a specific event procedure that is called after pressing "OK"? Within that procedure can I set a global variable like "IsReturnPressed = true;" Sadly, it's considerably more than just transferring to VS. You must implement all the controls yourself, rather than relying on IndirectUI. When you do so, the OK button will trigger an event handler, so you can do whatever you want when Cancel is pressed. Some time ago I listed the main routines that need to be implemented to work in PDN. Though it's been quite a while, I think it's probably still accurate. The thread also has links to some example code. I don't think writing a non-IndirectUI plugin is all that difficult: it's mostly just a Windows Forms application with some hooks into PDN. However, maintaining the look-and-feel of a standard PDN plugin would be, I expect, quite a challenge. Quote Link to comment Share on other sites More sharing options...
toe_head2001 Posted August 30, 2022 Share Posted August 30, 2022 I made some templates a few years ago, but they are out-of-date now. Fixing them hasn't been a priority for me (or anyone else apparently). Quote My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
Sebastian17 Posted August 31, 2022 Author Share Posted August 31, 2022 Is there a way to trigger the OnRender procdure from within the OnDispose procedure? Quote Link to comment Share on other sites More sharing options...
BoltBait Posted August 31, 2022 Share Posted August 31, 2022 1 hour ago, Sebastian17 said: Is there a way to trigger the OnRender procdure from within the OnDispose procedure? Don’t do that. OnDispose is called by the .NET Garbage Collector, not by Paint.NET itself. 1 Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
Rick Brewster Posted August 31, 2022 Share Posted August 31, 2022 Dispose / OnDispose is strictly for cleanup, loosely akin to a destructor in C++. If you don't understand IDisposable, it's best to stay away from Dispose() / OnDispose(). It can be tricky to get right. And never put any other kind of logic in that method. 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...
Sebastian17 Posted September 1, 2022 Author Share Posted September 1, 2022 Hi, I understand that OnDispose() is a system routine and OnRender is part of PDN, ok. I will avoid OnDispose(). Now I would create own control dialogs. How do I call OnRender() and Render() from my own control event procedure? To be more precise, how do I obtain (get) the variables (Rectangle[] rois, int startIndex, int length) and (Surface dst, Surface src, Rectangle rect) witin my control procedures? (As I have to pass them in order to invoke rendering.) Greetings, Sebastian Quote Link to comment Share on other sites More sharing options...
TrevorOutlaw Posted September 5, 2022 Share Posted September 5, 2022 (edited) I have been following this thread because it was intriguing, but I cannot help wondering if the end result is planning to be similar to @TechnoRobboPanoPDN Panoramic Viewer: Edited September 5, 2022 by TrevorOutlaw Quote Paint.NET Gallery | Remove Foreground Object Tutorial | Dispersion Effect Tutorial Link to comment Share on other sites More sharing options...
Sebastian17 Posted September 21, 2022 Author Share Posted September 21, 2022 (edited) I implemented my Code in analogy to the project FurBlur from Red ochre (John Robbins) helped by Null54. I had to fix references and outdated code but it worked 🙂. Unfortunately I couldn't use the other examples and templates I found because the didn't work together with VS 2015 (I can't change...😢) My code works well so far, I couldn't find a problem yet. But I am sure that the prcedures are not optimal for my problem (they were optimized for FurBlur). For example the render() procedure is called twice after pressing OK. Could you give me a hint how to change the details concerning the token handling and the workflow? The C# part for using the dialog looks like: Spoiler namespace PanoramaShifter { public partial class PanoramaShifterConfigDialog: EffectConfigDialog { private int suppressTokenUpdateCounter; private static readonly PanoramaShifterDefaultSettings DefaultSettings = new PanoramaShifterDefaultSettings(PanoramaShifterToken.CreateDefaultToken()); public PanoramaShifterConfigDialog() { InitializeComponent(); this.suppressTokenUpdateCounter = 0; } private void PushSuppressTokenUpdate() { this.suppressTokenUpdateCounter++; } private void PopSuppressTokenUpdate() { this.suppressTokenUpdateCounter--; } protected override void InitDialogFromToken(EffectConfigToken effectTokenCopy) { PanoramaShifterToken token = (PanoramaShifterToken)effectTokenCopy; PushSuppressTokenUpdate(); // Suppress the FinishTokenUpdate() call when the dialog is being set. // always start with a rotation of 0°, do not use old values at the start // this.tbar_Rotation.Value = token.rotation; this.tbar_Rotation.Value = 0; this.lbl_Status.Text = "ready"; PopSuppressTokenUpdate(); } protected override void InitTokenFromDialog() { PanoramaShifterToken token = (PanoramaShifterToken)base.theEffectToken; token.rotation = this.tbar_Rotation.Value; token.status = this.lbl_Status.Text; } protected override void InitialInitToken() { base.theEffectToken = PanoramaShifterToken.CreateDefaultToken(); } private void UpdateConfigToken() { if(suppressTokenUpdateCounter == 0) { FinishTokenUpdate(); } } private void btn_OK_Click(object sender, EventArgs e) { // set the status to finish in order for the render procedure to avoid the helping objects lbl_Status.Text = "finished"; base.FinishTokenUpdate(); base.DialogResult = System.Windows.Forms.DialogResult.OK; this.Close(); } private void btn_CANCEL_Click(object sender, EventArgs e) { base.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.Close(); } private void btn_Reset_Click(object sender, EventArgs e) { tbar_Rotation.Value = DefaultSettings.rotation; } private void tb_Rotation_ValueChanged(object sender, EventArgs e) { tbar_Rotation.Value = (int)tb_Rotation.Value; } private void tbar_Rotation_ValueChanged(object sender, EventArgs e) { decimal value = tbar_Rotation.Value; if(tb_Rotation.Value != value) { tb_Rotation.Value = value; } UpdateConfigToken(); } } } And the corresponding render procedures are: Spoiler public PanoramaShifter_Plugin() : base(StaticName, StaticIcon, SubmenuName, new EffectOptions() { Flags = EffectFlags.Configurable}) { } public override EffectConfigDialog CreateConfigDialog() { return new PanoramaShifterConfigDialog(); } protected override void OnSetRenderInfo(EffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) { PanoramaShifterToken configToken = (PanoramaShifterToken)newToken; this.xRot = configToken.rotation; this.dlgStatus = configToken.status; Rectangle selection = EnvironmentParameters.SelectionBounds; Render(dstArgs.Surface , srcArgs.Surface, selection); base.OnSetRenderInfo(newToken, dstArgs, srcArgs); } public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) { if(length == 0) return; } #region User Entered Code // Name: PanoramaShifter // Submenu: PanoramaShifter // Author: Sebastian Mainusch // Title: // Version: 1.0 // Desc: Shifts 360° panorama picuters horizontally in order to redefine the FWD view // Keywords: panorama, shift, roll // URL: // Help: #region UICode int xRot = 0; string dlgStatus = "ready"; //IntSliderControl xRot = 0; // [-180,180] Rotation Shift [°] #endregion void Render(Surface dst, Surface src, Rectangle rect) { if (IsCancelRequested) return; // Delete any of these lines you don't need Rectangle selection = EnvironmentParameters.SelectionBounds; int deltaX = (selection.Right - selection.Left)-1; double centerX = deltaX / 2.0 + (double)selection.Left; int centerY = ((selection.Bottom - selection.Top) / 2) + selection.Top; int xSource = 0; if (xRot == 180){ xRot = -180; } int xOffset = (int) ((float)xRot / 360.0 * (float)deltaX); if(xOffset > 0) { xOffset -= deltaX; } ColorBgra currentPixel; for(int y = rect.Top; y < rect.Bottom; y++) { if(IsCancelRequested) return; for(int x = rect.Left; x < rect.Right; x++) { xSource = x + xOffset; if(xSource < selection.Left) { xSource += selection.Right; } currentPixel = src[xSource, y]; dst[x, y] = currentPixel; } } if(dlgStatus == "finished") { dlgStatus = "ready"; } else { using(RenderArgs ra = new RenderArgs(dst)) { Graphics graf = ra.Graphics; graf.Clip = new Region(rect); graf.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; Pen colpen = new Pen(ColorBgra.White); if(Math.Ceiling(centerX) - Math.Floor(centerX) > 0) { colpen.Width = 2; } else { colpen.Width = 1; } colpen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid; graf.DrawLine(colpen, new Point((int)centerX, rect.Top), new Point((int)centerX, rect.Bottom)); colpen.Color = ColorBgra.Blue; colpen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; graf.DrawLine(colpen, new Point((int)centerX, rect.Top), new Point((int)centerX, rect.Bottom)); } } } #endregion I have also created a token class and so on as in FlurBlur. Here some questions: 1) Is the code still using parallel rendering? The pure Codelab version is ways faster... 2) Can I optimize the sequence? I like a instant preview (with helping center line) while using the UI and a fast rendering if the dialog is finished by pressing OK. 3) Is there a simpler approach for the tokens? 4) Is the cancelation of rendering: Quote if (IsCancelRequested) return; better placed in the beginning of the render procedure or due to parallel processing in the loop? 5) Is "using" necessary in the context: Quote using(RenderArgs ra = new RenderArgs(dst)) { Graphics graf = ra.Graphics; graf.Clip = new Region(rect); ... graf.DrawLine(colpen, new Point((int)centerX, rect.Top), new Point((int)centerX, rect.Bottom)); } or is it overdone? I would really appreciate a little help. Greetings, Sebastian Edited September 23, 2022 by Sebastian17 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.