Sign in to follow this  
BoltBait

Level Horizon plugin with tutorial and source code v1.3 - 2015-04-21

Recommended Posts

Level Horizon Plugin v1.3
for paint.net 4.0+


Download here as part of my plugin pack: http://forums.getpaint.net/index.php?/topic/32048-v

Outdated link:
LevelHorizon.zip

Install the .dll file the normal way, then restart paint.net.

You can do everything this plugin does with the built-in Layers > Rotate / Zoom effect. However, I think you'll find this MUCH easier for leveling a horizon. That roll control is cool, but not very easy to get just the right angle for adjusting a horizon.

 

 


UPDATES



v1.3 - Added Plumb Bob functionality. Corrections of +/- 45* or less count as horizontal, greater than that count as vertical.
v1.2 - "+" indicators now red. Added IndirectUI rules. Full project now.
v1.0 - Initial release. CodeLab script.

 

 


Tutorial



Let's start with the following image:

HorizonOriginal.png

It looks great, but the horizon isn't level! Let's fix that.

Step 1:

Load your desired image and use the Effects > Photo > LevelHorizon.png Level Horizon menu.

Move the effect UI out of the way so that you can see your image to work on it.

Using the black "+" in the effect UI under "Starting position" place it on the left side of your image on the horizon.

Then, using the black "+" under "Ending position" place it on the right side of your image on the horizon.

Note: Use the + for rough placement, then use the sliders or data entry options for fine control.

You should see a dashed line on your image that follows your horizon:

HorizonStep1.png

Step 2:

Once you are happy with the location of the horizon indicator line, click the "Show guidelines" check box to turn off the guidelines and show the rotation effect.

HorizonStep2.png

You should see the image rotated so that the horizon is now flat. If you look in the corners of the image you should see some checkerboard pattern. This indicates where there is no image. We'll fix that next.

Step 3:

Adjust the "Zoom" slider until the corners of your image are filled. I usually just click the little up arrow a couple of times and that does the trick!

HorizonStep3.png

Click OK to finalize your image.

 

 

 


Source Code



Here is the CodeLab source to the effect:

 

Spoiler

 


// Title: BoltBait's Level Horizon / Plumb Bob v1.3
// Author: BoltBait
// Name: Level Horizon / Plumb Bob
// Submenu: Photo
// Desc: Rotate a photo by drawing a horizon line
// Keywords: Level|Horizon|Rotation|Rotate|Zoom
// URL: http://www.BoltBait.com/pdn
#region UICode
Pair<double, double> Amount1 = Pair.Create(-0.9, -0.33); // Starting position (Left or Top)
Pair<double, double> Amount2 = Pair.Create(0.9, -0.33); // Ending position (Right or Bottom)
bool Amount3 = true; // [0,1] Show guidelines (turn off before clicking OK)
double Amount4 = 1; // [0.06,16] Zoom
bool Amount5 = false; // [0,1] Tiling
bool Amount6 = false; // [0,1] Preserve background
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    // Copy the source surface to the destination surface
    dst.CopySurface(src, rect.Location, rect);
    if (IsCancelRequested) return;

    // Calculate rotation angle
    Rectangle selection = EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
    float X1 = (float)((Amount1.First + 1) / 2 * (selection.Right - selection.Left) + selection.Left);
    float Y1 = (float)((Amount1.Second + 1) / 2 * (selection.Bottom - selection.Top) + selection.Top);
    float X2 = (float)((Amount2.First + 1) / 2 * (selection.Right - selection.Left) + selection.Left);
    float Y2 = (float)((Amount2.Second + 1) / 2 * (selection.Bottom - selection.Top) + selection.Top);

    float deltaX = X2 - X1;
    float deltaY = Y2 - Y1;
    float angle;
    if (deltaX == 0 && deltaY == 0)
    {
        // Special case: if both +'s are on the same spot, don't rotate
        angle = 0;
    }
    else
    {
        angle = ((float)Math.Atan2(deltaX, deltaY) * 180 / (float)Math.PI) - 90;
    }

    string direction = "↔";
    if (!(((angle > -45) && (angle < 45)) || ((angle > -225) && (angle < -135))))
    {
        // switch to plumb bob
        angle += 90;
        direction = "↕";
    }

    if (Amount3)
    {
        // show grid lines
        Pen blackPen = new Pen(Color.Black, 2);
        Pen whitePen = new Pen(Color.White, 5);
        Pen redPen = new Pen(Color.Red, 2);
        blackPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
        Graphics g = new RenderArgs(dst).Graphics;
        g.Clip = new Region(rect);
        // Draw horizon line
        g.DrawLine(whitePen, X1, Y1, X2, Y2);
        g.DrawLine(blackPen, X1, Y1, X2, Y2);
        // Draw left/top +
        g.DrawLine(whitePen, X1 - 10, Y1, X1 + 10, Y1);
        g.DrawLine(whitePen, X1, Y1 - 10, X1, Y1 + 10);
        g.DrawLine(redPen, X1 - 10, Y1, X1 + 10, Y1);
        g.DrawLine(redPen, X1, Y1 - 10, X1, Y1 + 10);
        // Draw right/bottom +
        g.DrawLine(whitePen, X2 - 10, Y2, X2 + 10, Y2);
        g.DrawLine(whitePen, X2, Y2 - 10, X2, Y2 + 10);
        g.DrawLine(redPen, X2 - 10, Y2, X2 + 10, Y2);
        g.DrawLine(redPen, X2, Y2 - 10, X2, Y2 + 10);
        // Show angle calculated
        SolidBrush Brush1 = new SolidBrush(Color.Black);
        SolidBrush Brush2 = new SolidBrush(Color.White);
        Font SelectedFont = new Font("Arial", 16);
        g.DrawString(direction+" "+angle.ToString() + "°", 
            SelectedFont, Brush2, selection.Left + 1, selection.Top + 1);
        g.DrawString(direction+" "+angle.ToString() + "°", 
            SelectedFont, Brush1, selection.Left, selection.Top);
    }
    else
    {
        // do the actual rotation effect by calling the built-in rotate/zoom effect
        RotateZoomEffect rotateEffect = new RotateZoomEffect();
        PropertyCollection rProps = rotateEffect.CreatePropertyCollection();
        PropertyBasedEffectConfigToken rParams = new PropertyBasedEffectConfigToken(rProps);
        rParams.SetPropertyValue(RotateZoomEffect.PropertyNames.RollAndRotate,
            Tuple.Create<double, double, double>(angle * -1, 0.0, 0.0));
        rParams.SetPropertyValue(RotateZoomEffect.PropertyNames.Zoom, Amount4);
        rParams.SetPropertyValue(RotateZoomEffect.PropertyNames.Tiling, Amount5);
        rParams.SetPropertyValue(RotateZoomEffect.PropertyNames.PreserveBackground, Amount6);
        rotateEffect.SetRenderInfo(rParams, new RenderArgs(dst), new RenderArgs(src));
        rotateEffect.Render(new Rectangle[1] { rect }, 0, 1);
    }
}

 


The effect dll you can download above is a little more complex in the UI department, but this will show you the algorithm.

 

 

  • Upvote 5

Share this post


Link to post
Share on other sites

Really cool Plugin @BoltBait :star:  .  Thank you :beer: .

 

hXsYZKB.png

 

 

 

 

Share this post


Link to post
Share on other sites

But now the tower is tilting to the left Pixey ;-)

 

Dang - that's an Italian horizon for you ......... not simpatico smiley-ashamed001.gif 

 

She needs a plumb bob, not a spirit level.

 

Perhaps this UI for a Plumb-Bob Plugin  :lol:

 

 

edipPRZ.gif

 

:D

Edited by Pixey

Share this post


Link to post
Share on other sites

Somebody snap their fingers,, plumb bob has me hypnotized     :roll:

 

Snip.

 

And don't forget to check your lips. They may be colored red in the meantime. ;-)

Share this post


Link to post
Share on other sites

sounds like your first revision

Great idea!

I have added the Plumb Bob capability to the plugin while waiting in line at the DMV.

PlumbBob.png

Go get your fresh copy!

NOTE: I'm done updating this. User midora is working on a better version of this plugin. Go download his when it is ready.

______________

post-44727-0-73549200-1429651677_thumb.p

  • Upvote 3

Share this post


Link to post
Share on other sites

Wow!  How quick was that :lightning: .  Impressive :bmw: .

 

In line at the DMV - Ugh - that can take hours - bet you wish it'd been at McDonalds instead :P .

Share this post


Link to post
Share on other sites

Wow!  How quick was that :lightning: .  Impressive :bmw: .

Adding the plumb bob capability to the plugin only consisted in adding the following code after calculating the raw angle measurement:

 

if (!(((angle > -45) && (angle < 45)) || ((angle > -225) && (angle < -135))))
{
    angle += 90;
}
That's it.

Sometimes requested changes are easy, sometimes they are hard. This time it was the former.

  • Upvote 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this