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

Recommended Posts

Level Horizon Plugin v1.3
for 4.0+

Download here as part of my plugin pack:

Outdated link:

Install the .dll file the normal way, then restart

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.




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.




Let's start with the following image:


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:


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.


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!


Click OK to finalize your image.




Source Code

Here is the CodeLab source to the effect:




// 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:
#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

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;
        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);
        // 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);
            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: .







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:






Edited by Pixey

Share this post

Link to post
Share on other sites

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




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
On 4/20/2015 at 12:51 PM, TechnoRobbo said:

sounds like your first revision

Great idea!

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


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.



  • 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
1 hour ago, Finfin said:

This is great! Is there a way to add a hotkey?


Plugins can not register a hot key.

Share this post

Link to post
Share on other sites

Apologies that this is an older thread, but my question is related to the very helpful instructions above.


I downloaded etc, and used the Level Horizon as shown, except that I missed the bit about checking the Show Guidelines box before finishing. And then...rookie mistake.. saved the image with the guideline on it! Any idea how to easily remove the guideline?

Share this post

Link to post
Share on other sites

Hello @Fiona JB and Welcome :) 


Depending on how many lines you have, you could make a new layer above the image and then go over the lines in the exact color in the image you have saved.

Share this post

Link to post
Share on other sites

If Pixey's way doesn't yield satisfaction you could try this tutorial



... there's a link in there for Content Aware Fill which could also help.

I think you're looking at trial & error plus time.  Possibly lots of it

Share this post

Link to post
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.

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.