11

I need the precise position of my mouse pointer over a PictureBox.

I use the MouseMove event of the PictureBox.

On this PictureBox, I use the "zoom" property to show an image.

What is the correct way for getting the position of the mouse on the original (unzoomed) image?

Is there a way to find the scale factor and use it?

I think need to use imageOriginalSize/imageShowedSize to retrieve this scale factor.

I use this function:

float scaleFactorX = mypic.ClientSize.Width / mypic.Image.Size.Width;
float scaleFactorY = mypic.ClientSize.Height / mypic.Image.Size.Height;

Is possible to use this value to get the correct position of the cursor over the image?

mdb
  • 52,000
  • 11
  • 64
  • 62
devilkkw
  • 418
  • 2
  • 6
  • 17
  • I suppose you can try: Point cursorPosition = mypic.PointToClient(Cursor.Position); Point positionOverImage = new Point(cursorPosition.X / scaleFactorX, cursorPosition.Y / scaleFactorY);. However, I suppose there can be some issues with accuracy when you count zooming factor using this method. I would suggest you to scale the image on your own. – Lukasz M May 06 '12 at 19:55
  • thank,but doesn't work.position is incorrect,i get same value when using pointtoclient and eventmouseargs position. – devilkkw May 06 '12 at 20:17
  • Please post the code snippet calculating the coordinates that you are currently using. – Lukasz M May 06 '12 at 20:48
  • Rectangle sourceRec = new Rectangle((int)(e.X / currentScale), (int)(e.Y / currentScale), 1, 1); – devilkkw May 06 '12 at 21:12
  • When you say you need your mouse coordinates with respect to the unstretched image, what is the anchor you expect your unstretched image to have? Is it anchored at the top left? Anchored at the center? – Satyajit May 06 '12 at 22:23
  • A proper conversion needs to pay attention to the box' SizeMode property. The code you use now is only correct for Stretch, an unusual choice. – Hans Passant May 07 '12 at 12:38

3 Answers3

16

I had to solve this same problem today. I wanted it to work for images of any width:height ratio.

Here's my method to find the point 'unscaled_p' on the original full-sized image.

            Point p = pictureBox1.PointToClient(Cursor.Position);
            Point unscaled_p = new Point();

            // image and container dimensions
            int w_i = pictureBox1.Image.Width; 
            int h_i = pictureBox1.Image.Height;
            int w_c = pictureBox1.Width;
            int h_c = pictureBox1.Height;

The first trick is to determine if the image is a horizontally or vertically larger relative to the container, so you'll know which image dimension fills the container completely.

            float imageRatio = w_i / (float)h_i; // image W:H ratio
            float containerRatio = w_c / (float)h_c; // container W:H ratio

            if (imageRatio >= containerRatio)
            {
                // horizontal image
                float scaleFactor = w_c / (float)w_i;
                float scaledHeight = h_i * scaleFactor;
                // calculate gap between top of container and top of image
                float filler = Math.Abs(h_c - scaledHeight) / 2;  
                unscaled_p.X = (int)(p.X / scaleFactor);
                unscaled_p.Y = (int)((p.Y - filler) / scaleFactor);
            }
            else
            {
                // vertical image
                float scaleFactor = h_c / (float)h_i;
                float scaledWidth = w_i * scaleFactor;
                float filler = Math.Abs(w_c - scaledWidth) / 2;
                unscaled_p.X = (int)((p.X - filler) / scaleFactor);
                unscaled_p.Y = (int)(p.Y / scaleFactor);
            }

            return unscaled_p;

Note that because Zoom centers the image, the 'filler' length has to be factored in to determine the dimension that is not filled by the image. The result, 'unscaled_p', is the point on the unscaled image that 'p' correlates to.

Hope that helps!

dan
  • 176
  • 3
  • 2
    Perfect upon one small error: for the ratio you should not use width and height of the complete control, but the width and height of the Control.ClientSize, so without borders. Quite often the difference is not noticeable, until you show a border and do some calculations. You'll see that some pixels are missing – Harald Coppoolse Nov 28 '14 at 15:10
2

If I have understood you correctly I believe you would want to do something of this nature:

Assumption: the PictureBox fits to the image width/height, there is no space between the border of the PictureBox and the actual image.

ratioX = e.X / pictureBox.ClientSize.Width;
ratioY = e.Y / pictureBox.ClientSize.Height;

imageX = image.Width * ratioX;
imageY = image.Height * ratioY;

this should give you the points ot the pixel in the original image.

Mark Hall
  • 53,938
  • 9
  • 94
  • 111
Dene B
  • 486
  • 4
  • 9
  • I missed out the `(float)` for the ratio, do that, at the moment if you say had `var ratioX`, then it will likely be an `int`, meaning `ratioX/Y` will always be zero. – Dene B May 07 '12 at 20:30
0

Here is a simple function to solve this:

private Point RemapCursorPosOnZoomedImage(PictureBox pictureBox, int x, int y, out bool isInImage)
{
  // original size of image in pixel
  float imgSizeX = pictureBox.Image.Width;
  float imgSizeY = pictureBox.Image.Height;

  // current size of picturebox (without border)
  float cSizeX = pictureBox.ClientSize.Width;
  float cSizeY = pictureBox.ClientSize.Height;

  // calculate scale factor for both sides
  float facX = (cSizeX / imgSizeX);
  float facY = (cSizeY / imgSizeY);

  // use smaller one to fit picturebox zoom layout
  float factor = Math.Min(facX, facY);

  // calculate current size of the displayed image
  float rSizeX = imgSizeX * factor;
  float rSizeY = imgSizeY * factor;

  // calculate offsets because image is centered
  float startPosX = (cSizeX - rSizeX) / 2;
  float startPosY = (cSizeY - rSizeY) / 2;

  float endPosX = startPosX + rSizeX;
  float endPosY = startPosY + rSizeY;

  // check if cursor hovers image
  isInImage = true;
  if (x < startPosX || x > endPosX) isInImage = false;
  if (y < startPosY || y > endPosY) isInImage = false;

  // remap cursor position
  float cPosX = ((float)x - startPosX) / factor;
  float cPosY = ((float)y - startPosY) / factor;

  // create new point with mapped coords
  return new Point((int)cPosX, (int)cPosY);
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
NonoGG
  • 21
  • 2
  • Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Aug 01 '22 at 09:01
  • The method solves the problem devilkkw described. But I agree with you. I probably should have talked a bit more about the input parameters. First you specify from which picture box the position should be extracted. Then you specify the position of the mouse (x, y), which will be given at the MouseMove event. The out boolean returns at the end, whether the mouse is in the picture at all, which may not be the case with a picture box with "zoomed". The return of the method is a point, which then specifies the coordinates of the "unzoomed" picture. – NonoGG Aug 03 '22 at 07:01