C. Cristi C. Cristi - 11 months ago 41
C# Question

free-form crop selection doesn't work properly

I made a program that let's you do a random selection then the selected area to save into a new image, but I got a problem it doesn't work how it's supposed to.. here is how i actually selected the crop line. And here is how it really crops. I will post my code here so you can have a look:

private List<Point> Points = null;
private bool Selecting = false;

private Bitmap SelectedArea = null;

private void pictureBox5_MouseDown(object sender, MouseEventArgs e)
{
Points = new List<Point>();
Selecting = true;
}

private void pictureBox5_MouseMove(object sender, MouseEventArgs e)
{
if (!Selecting) return;
Points.Add(new Point(e.X, e.Y));
pictureBox5.Invalidate();
}

private void pictureBox5_MouseUp(object sender, MouseEventArgs e)
{
Selecting = false;

// Copy the selected area.
SelectedArea = GetSelectedArea(pictureBox5.Image, Color.Transparent, Points);

SelectedArea.Save(@"C:\Users\User\Desktop\Gallery\image" + NumberOfClick.ToString() + "cropped.jpeg", ImageFormat.Jpeg);

string filename = @"C:\Users\User\Desktop\Gallery\image" + NumberOfClick.ToString() + "cropped.jpeg";

if(File.Exists(filename))
{
button1.Visible = true;
pictureBox5.Visible = false;
}
}



private void pictureBox5_Paint(object sender, PaintEventArgs e)
{
if ((Points != null) && (Points.Count > 1))
{
using (Pen dashed_pen = new Pen(Color.Black))
{
dashed_pen.DashPattern = new float[] { 5, 5 };
e.Graphics.DrawLines(Pens.White, Points.ToArray());
e.Graphics.DrawLines(dashed_pen, Points.ToArray());
}
}
}

private Bitmap GetSelectedArea(Image source, Color bg_color, List<Point> points)
{
// Make a new bitmap that has the background
// color except in the selected area.
Bitmap big_bm = new Bitmap(source);
using (Graphics gr = Graphics.FromImage(big_bm))
{
// Set the background color.
gr.Clear(bg_color);

// Make a brush out of the original image.
using (Brush br = new TextureBrush(source))
{
// Fill the selected area with the brush.
gr.FillPolygon(br, points.ToArray());

// Find the bounds of the selected area.
Rectangle source_rect = GetPointListBounds(points);

// Make a bitmap that only holds the selected area.
Bitmap result = new Bitmap(
source_rect.Width, source_rect.Height);

// Copy the selected area to the result bitmap.
using (Graphics result_gr = Graphics.FromImage(result))
{
Rectangle dest_rect = new Rectangle(0, 0,
source_rect.Width, source_rect.Height);
result_gr.DrawImage(big_bm, dest_rect,
source_rect, GraphicsUnit.Pixel);
}

// Return the result.
return result;
}
}
}

private Rectangle GetPointListBounds(List<Point> points)
{
int xmin = points[0].X;
int xmax = xmin;
int ymin = points[0].Y;
int ymax = ymin;

for (int i = 1; i < points.Count; i++)
{
if (xmin > points[i].X) xmin = points[i].X;
if (xmax < points[i].X) xmax = points[i].X;
if (ymin > points[i].Y) ymin = points[i].Y;
if (ymax < points[i].Y) ymax = points[i].Y;
}

return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
}


This is how I am doing and saving the cropped images.

And also this is how I am uploading the pictures:

OpenFileDialog f = new OpenFileDialog();
f.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png) | *.jpg; *.jpeg; *.jpe; *.jfif; *.png";

if (f.ShowDialog() == DialogResult.OK)
{
currentImage = Image.FromFile(f.FileName);
pictureBox1.Image = currentImage;
}

pictureBox1.Image.Save(@"C:\Users\User\Desktop\Gallery\image1.jpeg", ImageFormat.Jpeg);

DialogResult result = MessageBox.Show("Crop your image", "Information", MessageBoxButtons.OK);

if(result == DialogResult.OK)
{
pictureBox5.Visible = true;
button1.Visible = false;
pictureBox5.Image = pictureBox1.Image;
}


In pictureBox5 I am selecting and cropping the picture.

TaW TaW
Answer Source

You need to calculate the zoom and the offset of the image when it is zoomed.

Here is how to do that; this assumes the PictureBox is indeed in Zoom mode, not in Stretch mode. If you stretch it you need to calculate the zooms for x and y separately..

float zoom = 1f * pictureBox.ClientSize.Width / pictureBox.Image.Width ;

float offx = (pictureBox.ClientSize.Width - pictureBox.Image.Width * zoom) / 2;
float offy = (pictureBox.ClientSize.Height - pictureBox.Image.Height * zoom) / 2;
Point offset = Point.Round(new PointF(offx, offy));

You calculate this after setting the Image and after resizing the PictureBox..

Now you can transform each drawn point into a zoomed or an unzoomed coordinate:

    PointF zoomed(Point p1, float zoom, Point offset)
    {
        return (new PointF(p1.X * zoom + offset.X, p1.Y * zoom + offset.Y));
    }

    PointF unZoomed(Point p1, float zoom, Point offset)
    {
        return (new PointF((p1.X - offset.X) / zoom, (p1.Y - offset.Y) / zoom));
    }

Here is a demo the draws on to either a normal (left) or a zoomed (right) image: enter image description here

In your GetSelectedArea method use this point list instead:

    private Bitmap GetSelectedArea(Image source, Color bg_color, List<Point> points)
    {
        var unzoomedPoints = 
            points.Select(x => Point.Round((unZoomed(Point.Round(x), zoom, offset))))
                  .ToList();
        // Make a new bitmap that has the background

After this replace each reference to points in the method by one to unzoomedPoints. Actually there are just two of them..