anonymous anonymous - 3 months ago 34
C# Question

How to draw on a zoomed image?

I have a picturebox of

Size
400X400 in my application. The
SizeMode
of the picturebox is set to
Zoomed
. I have loaded a png image of 700X446 in the picturebox.

enter image description here

I have following issue,

Even though I am drawing a black straight line along the red path, actually it is being shown along the loaded image out of perspective.

How can I solve the issue?

P.S. I want to draw only on the image, not the entire picturebox.

Source Code:

public partial class MainForm : Form
{
Bitmap _inputImage = null;
//Graphics _imageGraphics = null;

#region ctor
public MainForm()
{
InitializeComponent();

_inputImage = Bitmap.FromFile(@"E:\cracked.png") as Bitmap;

this.inputImagePictureBox.Image = _inputImage;
}
#endregion

#region Mouse Up and Down
Point _startPoint = Point.Empty;

private void left_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
_startPoint = e.Location;

Circle tempCircle = new Circle(_startPoint, 10);

Bitmap tempImage = (Bitmap)_inputImage.Clone();

Graphics g = Graphics.FromImage(tempImage);

tempCircle.Draw(g);

inputImagePictureBox.Image = tempImage;
}
}

private void pressed_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (_startPoint != e.Location)
{
Line tempLine = new Line(_startPoint, e.Location);

Bitmap tempImage = (Bitmap)_inputImage.Clone();

Graphics g = Graphics.FromImage(tempImage);

tempLine.Draw(g);

inputImagePictureBox.Image = tempImage;
}
}
}

Bitmap _savedImage;

private void left__MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (_startPoint != e.Location)
{
Line tempLine = new Line(_startPoint, e.Location);

Bitmap tempImage = (Bitmap)_inputImage.Clone();

Graphics g = Graphics.FromImage(tempImage);

tempLine.Draw(g);

_savedImage = tempImage;

inputImagePictureBox.Image = tempImage;
}
else
{
Bitmap tempImage = (Bitmap)_inputImage.Clone();
Graphics g = Graphics.FromImage(tempImage);

inputImagePictureBox.Image = tempImage;
}
}
}
}

TaW TaW
Answer

You need to address two issues:

  • Clip the Graphics area to the actual Image instead of the whole PictureBox.ClientArea

  • Scale the coordinates of the mouse events to the actual image when receiving and recording them and back again when you use them to draw in the Paint event.

For both we need to know the zoom factor of the Image; for the clipping we also need to know the ImageArea and for drawing I simply store two mouse locations.

Here are the class level variable I use:

PointF mDown = Point.Empty;
PointF mLast = Point.Empty;
float zoom = 1f;
RectangleF ImgArea = RectangleF.Empty;

Note that I use floats for all, since we will need to do some dividing..

First we'll calculate the zoom and the ImageArea:

void SetImageScale()
{
    SizeF sp = pictureBox1.ClientSize;
    SizeF si = pictureBox1.Image.Size;
    float rp = sp.Width / sp.Height;   // calculate the ratios of
    float ri = si.Width / si.Height;   // pbox and image

    if (rp > ri)
    {
        zoom = sp.Height / si.Height;
        float width = si.Width * zoom;
        float left = (sp.Width - width) / 2;
        ImgArea = new RectangleF(left, 0, width, sp.Height);
    }
    else
    {
        zoom = sp.Width / si.Width;
        float height = si.Height * zoom;
        float top = (sp.Height - height) / 2;
        ImgArea = new RectangleF(0, top, sp.Width, height);
    }
}

This routine should be called each time a new Image is loaded and also upon any Resizing of the PictureBox:

private void pictureBox1_Resize(object sender, EventArgs e)
{
    SetImageScale();
}

Now ne need store the mouse locations. Since they must be reusable after a resize we need to tranfsorm them to image coordinates. This routine can do that and also back again:

PointF scalePoint(PointF pt, bool scale)
{
    return scale ? new PointF( (pt.X - ImgArea.X) / zoom, (pt.Y - ImgArea.Y) / zoom)
                 : new PointF( pt.X * zoom + ImgArea.X, pt.Y * zoom + ImgArea.Y);
}

Finally we can code the Paint event

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    using (Pen pen = new Pen(Color.Fuchsia, 2.5f) { DashStyle = DashStyle.Dot})
        e.Graphics.DrawRectangle(pen, Rectangle.Round(ImgArea));

    e.Graphics.SetClip(ImgArea);
    e.Graphics.DrawLine(Pens.Red, scalePoint(mDown, false), scalePoint(mLast, false));
}

.. and the mouse events:

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    mDown = scalePoint(e.Location, true);
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        mLast = scalePoint(e.Location, true);
        pictureBox1.Invalidate();
    }
}

enter image description here

For more complex drawing you would store the coordinates in List<PointF> and tranform them back, pretty much like above..:

List<PointF> points = new List<PointF>();

and then:

e.Graphics.DrawCurve(Pens.Orange, points.Select(x => scalePoint(x, false)).ToArray());
Comments