Introduction
When using the default PictureBox
in a Windows.Forms
, it is often helpful to know what relative pixel of the image the mouse is currently over. Since the position returned in mouse events for the control are relative to the control's base, the position over the image can be vastly different, depending on how the SizeMode
property is set. This class helps resolve these differences.
Background
In one of my applications, I needed to be able to find out the location of the mouse relative to the image, but my SizeMode
was set to Zoom
. In this mode, the image constantly rescales itself to maintain the proper aspect ratio that will fit into the area of the control. As such, I needed to be able to safely translate the point.
Determining the Position
There are five different size modes that the PictureBox
control can be set to:
Normal
AutoSize
CenterImage
StretchImage
Zoom
In the Normal
mode, the image is located at the top left corner and is not scaled or skewed, so we can simply return the point relative to the control.
In the AutoSize
mode, the control itself is resized to fit the image (if possible), and the resulting image is not scaled or skewed, so we can simply return the point relative to the control again.
In the CenterImage
mode, the image is displayed in the center of the control. If the image is smaller than the control, it is padded equally to center it. If the image is larger than the control, the image is cropped equally. The following shows how to determine the location relative to the image:
protected Point TranslateCenterImageMousePosition(Point coordinates)
{
if(Image==null) return coordinates;
int diffWidth = Width - Image.Width;
int diffHeight = Height - Image.Height;
diffWidth /= 2;
diffHeight /= 2;
coordinates.X -= diffWidth;
coordinates.Y -= diffHeight;
return coordinates;
}
In the StretchImage
mode, the image is stretched or compressed, independently across axis, to take up the entire area of the control. To accommodate this, we must determine the ratio of the skew and apply it to the point:
protected Point TranslateStretchImageMousePosition(Point coordinates)
{
if (Image == null) return coordinates;
if (Width == 0 || Height == 0) return coordinates;
float ratioWidth = (float)Image.Width/Width;
float ratioHeight = (float)Image.Height / Height;
float newX = coordinates.X;
float newY = coordinates.Y;
newX *= ratioWidth;
newY *= ratioHeight;
return new Point((int)newX, (int)newY);
}
In the last mode, Zoom
, the image is scaled evenly across axis in such a way that the entire image is displayed in the control, centered when appropriate. To manage this method, we first need to determine what our limiting dimension is (height
or width
). After that, we need to determine the padding and the scaling to be applied to the point:
protected Point TranslateZoomMousePosition(Point coordinates)
{
if (Image == null) return coordinates;
if (Width == 0 || Height == 0|| Image.Width==0||Image.Height==0) return coordinates;
float imageAspect = (float)Image.Width / Image.Height;
float controlAspect = (float)Width / Height;
float newX = coordinates.X;
float newY = coordinates.Y;
if(imageAspect>controlAspect)
{
float ratioWidth = (float)Image.Width / Width;
newX *= ratioWidth;
float scale = (float)Width / Image.Width;
float displayHeight = scale * Image.Height;
float diffHeight = Height - displayHeight;
diffHeight /= 2;
newY -= diffHeight;
newY /= scale;
}
else
{
float ratioHeight = (float)Image.Height / Height;
newY *= ratioHeight;
float scale = (float)Height / Image.Height;
float displayWidth = scale * Image.Width;
float diffWidth = Width - displayWidth;
diffWidth /= 2;
newX -= diffWidth;
newX /= scale;
}
return new Point((int)newX, (int)newY);
}
Using the Code
The attached class utilizes these methods to determine the location of the point within the image. Additionally, the class supplies a couple methods and properties to obtain the Point regardless of the display mode (by checking the mode and calling the appropriate method) and an event that is called if the mouse is moved over the image.