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