How to draw on a zoomed image?

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 GetImageScaleData(PictureBox pbox)
{
    SizeF sp = pbox.ClientSize;
    SizeF si = pbox.Image.Size;
    float rp = 1f * sp.Width / sp.Height;   // calculate the ratios of
    float ri = 1f * 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)
{
    GetImageScaleData(pictureBox1);
}

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 transform 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());

Leave a Comment