Search code examples
c#visual-studiocroppictureboxdrawrectangle

C# - Forcing Rectangle to be in a fixed size/ratio


Similar to Photoshop, where you can type in a ratio or in my case a certain dimention such as 800x600, I want to be able to force a Rectangle to be a fixed ratio/size when dragging the mouse..

At the moment I have this:

Cropping an image

Which will crop an image using the Rectangle created from clicking and dragging on the PictureBox. The bounding box selects the area without any constraints. I want to be able to force the rectangle to be a certain ratio (prefereably from a set resolution) similar to the way Photoshop's cropping tool works.

My source if anyone needs more detail:

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CropResize
{
public partial class Form1 : Form
{
    private static string path;

    public Form1(string filePath)
    {
        InitializeComponent();
        path = filePath;
    }

    private Image _originalImage;
    private Image _newImage;

    private bool _selecting;
    private Rectangle _selection;


    private void Form1_Load(object sender, System.EventArgs e)
    {
        pictureBox1.Image = Image.FromFile(path);

        if (pictureBox1.Image.Height > Screen.PrimaryScreen.Bounds.Height - 50 || pictureBox1.Image.Width > Screen.PrimaryScreen.Bounds.Width - 50)
        {
            if (pictureBox1.Image.Height > Screen.PrimaryScreen.Bounds.Height - 50)
            {
                Height = Screen.PrimaryScreen.Bounds.Height - 50;
                panel1.Height = Size.Height - statusStrip1.Height - buttonSave.Height - 60;
            }
            if (pictureBox1.Image.Width > Screen.PrimaryScreen.Bounds.Width - 50)
            {
                Size = new Size(Screen.PrimaryScreen.Bounds.Width - 50, Screen.PrimaryScreen.Bounds.Height - 50);
                panel1.Width = Size.Width - statusStrip1.Height - buttonSave.Height - 60;
            }

            pictureBox1.Image = pictureBox1.Image.Fit2PictureBox(pictureBox1);

            panel1.Size = new Size(pictureBox1.Image.Width, pictureBox1.Image.Height);

        }

        Size = new Size(panel1.Size.Width + 50, panel1.Size.Height + buttonSave.Height + statusStrip1.Height + 80);

        // Create a copy of the original image for later use
        _originalImage = pictureBox1.Image.Clone() as Image;
        _newImage = pictureBox1.Image.Clone() as Image;
    }

    private void buttonOrig_Click(object sender, System.EventArgs e)
    {
        pictureBox1.Image = _originalImage.Clone() as Image;
    }

    private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
    {
        // Starting point of the selection:
        if (e.Button == MouseButtons.Left)
        {
            pictureBox1.Image.Dispose();
            pictureBox1.Image = _originalImage.Clone() as Image;
            _selecting = true;
            _selection = new Rectangle(new Point(e.X, e.Y), new Size());
        }
    }

    private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
        // Update the actual size of the selection:
        if (_selecting)
        {

            _selection.Width = (e.X - _selection.X);
            _selection.Height = (e.Y - _selection.Y);

            //int nGCD = GetGreatestCommonDivisor(1920, 1080);
            //_selection.Width = _selection.Width / nGCD;
            //_selection.Height = _selection.Height / nGCD;


            int widthRatio = 16;
            int heightRatio = 9;

            if (_selection.Height * widthRatio <= _selection.Width)
            {
                _selection.Width = _selection.Height * widthRatio;
            }
            else if (_selection.Width * heightRatio <= _selection.Width)
            {
                _selection.Height = _selection.Width * heightRatio;
            }

            // Redraw the picturebox:
            pictureBox1.Refresh();
        }
    }

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        if (_selecting && _selection.Height != 0)
        {

            // Draw a rectangle displaying the current selection
            e.Graphics.DrawRectangle(Pens.WhiteSmoke, _selection);

            //e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, Color.Gray)), 0, pictureBox1.Height - pictureBox1.Image.Height, pictureBox1.Image.Width, pictureBox1.Image.Height);
            e.Graphics.SetClip(_selection, CombineMode.Exclude);
            e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(100, Color.Black)), 0, 0, pictureBox1.Width, pictureBox1.Height);

            int nGCD = GetGreatestCommonDivisor(_selection.Width, _selection.Height);
            string str = string.Format("{0}:{1}", _selection.Width / nGCD, _selection.Height / nGCD);

            toolStripStatusLabel1.Text = "Image Size: " + _selection.Width + "x" + _selection.Height + "px.    Aspect Ratio: " + str;
        }
    }

    public static int GetGreatestCommonDivisor(int height, int width)
    {
        return width == 0 ? height : GetGreatestCommonDivisor(width, height % width);
    }


    private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left &&
            _selecting &&
            _selection.Size != new Size())
        {
            // Create cropped image:
            _newImage = pictureBox1.Image.Crop(_selection);

            _selecting = false;

            try
            {
                // Set new image to the picturebox:
                //pictureBox1.Image = _newImage.Fit2PictureBox(pictureBox1);

                pictureBox1.Image = _newImage;

                //toolStripStatusLabel1.Text = "Image Cropped.";
            }
            catch (Exception)
            { }
        }
        else
        {
            _selecting = false;
        }
    }

    private void buttonResize_Click(object sender, EventArgs e)
    {
        pictureBox1.Image = ResizeImage(pictureBox1.Image, new Size(800, 600));

        int nGCD = GetGreatestCommonDivisor(pictureBox1.Image.Width, pictureBox1.Image.Height);
        string str = string.Format("{0}:{1}", pictureBox1.Image.Width / nGCD, pictureBox1.Image.Height / nGCD);

        toolStripStatusLabel1.Text = "Image Resized to " + pictureBox1.Image.Width + "x" + pictureBox1.Image.Height + "px.    Aspect Ratio: " + str;
    }


    public static Image ResizeImage(Image image, Size size, bool preserveAspectRatio = true)
    {
        int newWidth;
        int newHeight;
        if (preserveAspectRatio)
        {
            int originalWidth = image.Width;
            int originalHeight = image.Height;
            float percentWidth = size.Width / originalWidth;
            float percentHeight = size.Height / originalHeight;
            float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
            newWidth = (int)(originalWidth * percent);
            newHeight = (int)(originalHeight * percent);
        }
        else
        {
            newWidth = size.Width;
            newHeight = size.Height;
        }
        Image newImage = new Bitmap(newWidth, newHeight);
        using (Graphics graphicsHandle = Graphics.FromImage(newImage))
        {
            graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight);
        }
        return newImage;
    }


    private void buttonSave_Click(object sender, EventArgs e)
    {
        string filename = path.Substring(path.LastIndexOf("\\") + 1);
        string newPath = path.Substring(0, path.LastIndexOf(".") - 1) + "NEW.png";

        toolStripStatusLabel1.Text = "Saving " + filename + " to " + newPath;

        pictureBox1.Image.Save(newPath, ImageFormat.Png);

        toolStripStatusLabel1.Text = filename + " saved to " + newPath;

    }
}
}

Program.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CropResize
{
static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1(args[0]));
    }


    public static Image SetImageWithinResolution(this Image image, PictureBox pictureBox)
    {
        //Bitmap bitmap = null;
        if (image.Height > Screen.PrimaryScreen.Bounds.Height)
        {
            //ActiveForm.Size = new Size(100, 100);
            Form.ActiveForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
            image = image.Fit2PictureBox(pictureBox);
            //Bitmap bitmap = new Bitmap(image, );

        }
        if (image.Width > Screen.PrimaryScreen.Bounds.Width)
        {
            //ActiveForm.Size = new Size(100, 100);
            Form.ActiveForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
            image = image.Fit2PictureBox(pictureBox);
            //Bitmap bitmap = new Bitmap();

        }
        return image;
    }


    public static Image Crop(this Image image, Rectangle selection)
    {
        Bitmap bmp = image as Bitmap;

        try
        {
            // Check if it is a bitmap:
            if (bmp == null)
                throw new ArgumentException("No valid bitmap");

            // Crop the image:
            Bitmap cropBmp = bmp.Clone(selection, bmp.PixelFormat);

            // Release the resources:
            image.Dispose();

            return cropBmp;
        }
        catch (Exception)
        {
            return bmp;
        }
    }


    public static Image Fit2PictureBox(this Image image, PictureBox picBox)
    {
        Bitmap bmp = null;
        Graphics g;

        // Scale:
        double scaleY = (double)image.Width / picBox.Width;
        double scaleX = (double)image.Height / picBox.Height;
        double scale = scaleY < scaleX ? scaleX : scaleY;

        // Create new bitmap:
        bmp = new Bitmap(
            (int)((double)image.Width / scale),
            (int)((double)image.Height / scale));

        // Set resolution of the new image:
        bmp.SetResolution(
            image.HorizontalResolution,
            image.VerticalResolution);

        // Create graphics:
        g = Graphics.FromImage(bmp);

        // Set interpolation mode:
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;

        // Draw the new image:
        g.DrawImage(
            image,
            new Rectangle(            // Destination
                0, 0,
                bmp.Width, bmp.Height),
            new Rectangle(            // Source
                0, 0,
                image.Width, image.Height),
            GraphicsUnit.Pixel);

        // Release the resources of the graphics:
        g.Dispose();

        // Release the resources of the origin image:
        image.Dispose();

        return bmp;
    }
}

Solution

  • Maybe this example will help; is shows how to restrict a drawn rectangle to a given ratio:

    float ratio = 0.33f;
    
    Rectangle setRect()
    {
        int x = Math.Min(mDown.X, currPt.X);
        int y = Math.Min(mDown.Y, currPt.Y);
    
        int w = Math.Max(mDown.X, currPt.X) - x;
        int h = Math.Max(mDown.Y, currPt.Y) - y;
    
        if (ratio > 1)  h = (int)(1f * w / ratio);
        else            w = (int)(1f * h * ratio);
    
        return new Rectangle(x, y, w, h);
    }
    

    It uses two Points, one set in the MouseDown and one updated in the MouseMove.

    It is up to you to integrate it with you programm; instead of painting all those pixels during MouseMove I would simply draw a rubberband rectangle on the surface of the control using the Paint event..

    If you are scaling things you may want to switch to using all floats.