Jump to content

Plugin (Codelab) to shift panorama pictures horizontally (temporary GUI/picture objects?)


Recommended Posts

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 by Sebastian17
Link to comment
Share on other sites

  • Sebastian17 changed the title to Plugin (Codelab) to shift panorama pictures horizontally (temporary GUI/picture objects?)
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).

  • Upvote 1
Link to comment
Share on other sites

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 

Link to comment
Share on other sites

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. 

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

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

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

 

Link to comment
Share on other sites

  • 3 weeks later...

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

×
×
  • Create New...