C. Cristi C. Cristi - 13 days ago 5
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

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..

Comments