简体   繁体   中英

Resize a box with the mouse while maintaining the aspect ratio?

I'm trying to create a resizable image overlay (for cropping purposes). It seems pretty easy to resize the overlay if I ignore the aspect ratio, but I can't figure out how to perform a constrained resize that respects the AR. I figure that I obviously can't obey the overlay's "grip" positions (or even borders) unless I force the mouse to follow it, but that seems unnatural, so I'll just have to rely on the mouse gesture (which I don't mind doing).

I can also easily resize the overlay and then force it into the proper dimensions afterwards (like every other question about this topic on this site is about), but it's not very intuitive when using a mouse.

This is sort of what I'm going for: http://deepliquid.com/projects/Jcrop/demos.php?demo=live_crop

I've written an application like this before but it was browser-based so I used a javascript library. This is a desktop application and I haven't found a suitable library for this.

I've left a lot of details out of this code snippet and simplified some conditions with booleans.

private void pbImage_Paint(object sender, PaintEventArgs e)
{
    //Overlay
    e.Graphics.FillRectangle(brushRect, overlayRect);

    // Grips
    e.Graphics.FillRectangle(gripRect, leftTopGrip);
    e.Graphics.FillRectangle(gripRect, rightTopGrip);
    e.Graphics.FillRectangle(gripRect, leftBottomGrip);
    e.Graphics.FillRectangle(gripRect, rightBottomGrip);

    AdjustGrips();

    base.OnPaint(e);
}

public void AdjustGrips()
{
    // The next section only causes the grips to partly obey
    // the AR - the rest of the overlay ignores it
    if (overlayRect.Height * arWidth <= overlayRect.Width)
        overlayRect.Width = overlayRect.Height * arWidth;
    else if (overlayRect.Width * arHeight <= overlayRect.Height)
        overlayRect.Height = overlayRect.Width * arHeight;

    leftTopGrip.X = overlayRect.Left;
    leftTopGrip.Y = overlayRect.Top;

    rightTopGrip.X = overlayRect.Right - rightTopGrip.Width;
    rightTopGrip.Y = overlayRect.Top;

    leftBottomGrip.Y = overlayRect.Bottom - leftBottomGrip.Height;
    leftBottomGrip.X = overlayRect.Left;

    rightBottomGrip.X = overlayRect.Right - rightBottomGrip.Width;
    rightBottomGrip.Y = overlayRect.Bottom - rightBottomGrip.Height;

}


private void pbImage_MouseMove(object sender, MouseEventArgs e)
{
    Point pt = new Point(e.X, e.Y);

    // Details elided


    if (e.Button == MouseButtons.Left && mouseinGrip)
    {
        if (bottomRightIsGripped)
        {
            newOverlayRect.X = overlayRect.X;
            newOverlayRect.Y = overlayRect.Y;
            newOverlayRect.Width = pt.X - newOverlayRect.Left;
            newOverlayRect.Height = pt.Y - newOverlayRect.Top;

            if (newOverlayRect.X > newOverlayRect.Right)
            {
                newOverlayRect.Offset(-width, 0);
                if (newOverlayRect.X < 0)
                    newOverlayRect.X = 0;
            }

            if (newOverlayRect.Y > newOverlayRect.Bottom)
            {
                newOverlayRect.Offset(0, -height);
                if (newOverlayRect.Y < 0)
                    newOverlayRect.Y = 0;
            }

            pbImage.Invalidate();
            oldOverlayRect = overlayRect = newOverlayRect;
            Cursor = Cursors.SizeNWSE;
        }

        // Code for other grips elided
    }   

    AdjustGrips();
    pbImage.Update();
    base.OnMouseMove(e);
}

// Mouse up and down elided

You have complete control over the new size for the overlay as it drags.

The example link that you've given, is simply selecting a starting point based on the click down, then selecting Max(Abs(pt.x - start.x), Abs(pt.y - start.y)) , and basing the crop square off of that.

To use a non square ratio, normalize the distances first.

// given known data 
// 
// Point start; 
// The starting location of the mouse down for the drag, 
// or the top left / bottom right of the crop based on if the mouse is 
// left/above the starting point
// 
// Size ratio;
// The ratio of the result crop
//

// pt = (20)x(-20)
// start = (0),(0)
// ratio = (1)x(2)
var dist = new Point(pt.X - start.X, pt.Y - start.Y);

// "normalize" the vector from the ratio
// normalized vector is the distances with respect to the ratio
// ratio is (1)x(2). A (20)x(-20) is normalized as (20),(-10)
var normalized = new Point(dist.X / ratio.Width, dist.Y / ratio.Height);

// In our (20),(-10) example, we choose the ratio's height 20 as the larger normal.
// we will base our new size on the height
var largestNormal = (Math.Abs(normalized.X) > Math.Abs(normalized.Y)
                        ? Math.Abs(normalized.X) : Math.Abs(normalized.Y);

// The calcedX will be 20, calcedY will be 40
var calcedOffset = (largestNormal * ratio.Width, largestNormal * ratio.Height);

// reflect the calculation back to the correct quarter
// final size is (20)x(-40)
if (distX < 0) calcedOffset.X *= -1;
if (distY < 0) calcedOffset.Y *= -1;

var newPt = new Point(start.X + calcedOffset.X, start.Y + calcedOffset.Y);

Notice that one of the lengths can grow greater than the mouse location, but it will never be less. This will have the effect of the mouse traveling along the edge of the new crop box, and the box maintaining ratio.

I've figured out what was causing the original problems in my code. Unlike a static image resize, the aspect ratio code depends on which grip you're "holding", so putting it in a common location for all cases (eg. when the grip positions are set) will not work. You can easily calculate the size of the what the rect should be on the next update, but the position should be set depending on which grip is being held.

If, for example, you're resizing by holding the top left grip, then the bottom and right sides of the cropping rectangle should remain stationary. If you leave the code the same, then the rectangle resizes correctly, but it moves around the canvas and/or the grips go out of sync with the corners of the rect. There is probably a better way to do this but here's some crude code that works. I've only included code for the bottom right and top left grips to illustrate the differences. Extraneous things like setting the mouse pointer and error checking omitted.

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    Point mousePosition = new Point(e.X, e.Y);

    if (e.Button == MouseButtons.Left)
    {
        // This resizeMode, moveMode and other booleans
        // are set in the MouseUp event

        if (resizeBottomLeft)
        {
            // Top and Right should remain static!
            newCropRect.X = mousePosition.X;
            newCropRect.Y = currentCropRect.Y;
            newCropRect.Width = currentCropRect.Right - mousePosition.X;
            newCropRect.Height = mousePosition.Y - newCropRect.Top;

            if (newCropRect.X > newCropRect.Right)
            {
                newCropRect.Offset(cropBoxWidth, 0);
                if (newCropRect.Right > ClientRectangle.Width)
                    newCropRect.Width = ClientRectangle.Width - newCropRect.X;
            }

            if (newCropRect.Y > newCropRect.Bottom)
            {
                newCropRect.Offset(0, -cropBoxHeight);
                if (newCropRect.Y < 0)
                    newCropRect.Y = 0;
            }

            // Aspect Ratio + Positioning
            if (newCropRect.Width > newCropRect.Height)
            {
                newCropRect.Height = (int)(newCropRect.Width / ASPECT_RATIO);
            }
            else
            {
                int newWidth = (int)(newCropRect.Height * ASPECT_RATIO);
                newCropRect.X = newCropRect.Right - newWidth;
                newCropRect.Width = newWidth;
            }
        }
        else if (resizeTopRight)
        {
            // Bottom and Left should remain static!
            newCropRect.X = oldCropRect.X;
            newCropRect.Y = mousePosition.Y;
            newCropRect.Width = mousePosition.X - newCropRect.Left;
            newCropRect.Height = oldCropRect.Bottom - mousePosition.Y;

            if (newCropRect.X > newCropRect.Right)
            {
                newCropRect.Offset(-cropBoxWidth, 0);
                if (newCropRect.X < 0)
                    newCropRect.X = 0;
            }
            if (newCropRect.Y > newCropRect.Bottom)
            {
                newCropRect.Offset(0, cropBoxHeight);
                if (newCropRect.Bottom > ClientRectangle.Height)
                    newCropRect.Y = ClientRectangle.Height - newCropRect.Height;
            }

            // Aspect Ratio + Positioning
            if (newCropRect.Width > newCropRect.Height)
            {
                int newHeight = (int)(newCropRect.Width / ASPECT_RATIO);
                newCropRect.Y = newCropRect.Bottom - newHeight;
                newCropRect.Height = newHeight;
            }
            else
            {
                int newWidth = (int)(newCropRect.Height * ASPECT_RATIO);
                newCropRect.Width = newWidth;
            }
        }
        else if (moveMode) //Moving the rectangle
        {
            newMousePosition = mousePosition;
            int dx = newMousePosition.X - oldMousePosition.X;
            int dy = newMousePosition.Y - oldMousePosition.Y;
            currentCropRect.Offset(dx, dy);
            newCropRect = currentCropRect;
            oldMousePosition = newMousePosition;
        }

        if (resizeMode || moveMode)
        {
            oldCropRect = currentCropRect = newCropRect;

            // Set the new position of the grips
            AdjustGrips();
            pictureBox1.Invalidate();
            pictureBox1.Update();
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM