How to accurately measure mouse movement in inches or centimetres for a mouse with a known DPI
Asked Answered
M

1

2

I have a Logitech G500 gaming mouse that is running at its full DPI of 5700.

I'm trying to write a program in C++ that accurately measures horizontal movement of the mouse in physical units, ie. centimetres or inches.

I'm using the windows API and windows raw input via the WM_INPUT message to get raw movement changes from the mouse.

I'm then assuming 1 unit of movement reported through WM_INPUT is 1/5700th of an inch, and as I'm tracking the net movement of the mouse, I thought I could perform a simple calculation to yield the net physical movement:

distance(inches) = total_movement_from_wminput / dpi; // dpi = 5700 in this case

Unfortunately, the calculation doesn't seem to be accurate. I can tell from physical measuring just on my mouse pad, that over about 6inches of mouse movement, the calculation yields a value of about 5 and a half inches (a loss of a bout 1/2 an inch).

Where am I going wrong? I have set my mouse to 5700DPI in its control panel, could its actual DPI be less than that? Is my assumption about 1 unit of change via WM_INPUT being 1/dpi inches of physical movement incorrect?

Does anyone have any ideas on how I could get this to be accurate? Thanks!

Malamud answered 16/3, 2013 at 23:21 Comment(1)
A hardware manufacturer that lied about its device capabilities. That's not news.Abessive
S
2

Marc,

It seems that the problem may be when you move the mouse faster than Windows event WM_INPUT processes it. For instance, suppose the mouse moved 2 pixels in one frame. You would have a loss of 1/5700th of an inch (in your case) because for the one WM_INPUT event processed, you would move two pixels.

To fix this, you should check how many pixels the mouse moves every time the WM_INPUT message is sent to the program. What you have to do is make a RAWINPUTDEVICE variable and set the struct so it has the information about the mouse.

The following code registers the RAWINPUTDEVICE so it can be used in WM_INPUT.

RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; 
Rid[0].dwFlags = RIDEV_INPUTSINK;   
Rid[0].hwndTarget = hWnd;
RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]);

The following code acutally uses the Rid variable two determine how many pixels the mouse has moved since the last time WM_INPUT was initiated.

case WM_INPUT: 
{
    UINT dwSize = 40;
    static BYTE lpb[40];

    GetRawInputData((HRAWINPUT)lParam, RID_INPUT, 
                    lpb, &dwSize, sizeof(RAWINPUTHEADER));

    RAWINPUT* raw = (RAWINPUT*)lpb;

    if (raw->header.dwType == RIM_TYPEMOUSE) 
    {
        int xPosRelative = raw->data.mouse.lLastX; // Could be 1, or could be more than 1
        int yPosRelative = raw->data.mouse.lLastY; // Could be 1, or could be more than 1!
    } 
    break;
}

Note that this code is the same code presented on msdn on this very topic (link below).

You could now have some type of global variable that has the x-position and y-position (in pixels) of the mouse. Then, you simply divide those variables by the DPI, and you have the amount of inches offset from whenever you set the global variables to 0.


An altogether easier method would be to process the WM_MOUSEMOVE event instead. It makes it easy to get the exact position of the mouse (in pixels, of course). Using this, you can subtract this from the pixel values of the starting location.

Example:

DPI = 5700.

Initial position = (100px, 300px).

Position after 3 seconds = (500px, 400px).

The amount of inches moved in those 3 seconds = ( (500px - 100px)/5700 inches, (400px - 300px)/5700 inches )

General rule: Amount of inches moved after S seconds = (inital_pixels_x - final_pixels_x)/DPI inches

horizontally, (initial_pixels_y - final_pixels_y)/DPI inches vertically

Here, final_pixels_x is the x-position of the mouse after s seconds, and final_pixels y is the y-position after s seconds.


To summarize what you did wrong, you incorrectly assumed that each WM_INPUT event means that 1 pixel was travelled by the mouse.

If I for some reason misunderstood the question and you know that you are already getting the correct amount of pixels moved, please leave a comment and I will do my best to try and fix my answer. However, I would still recommend using WM_MOUSEMOVE instead of WM_INPUT as it is specifically for the mouse and it applies "pointer acceleration" which you can read about on the link at the very bottom.

Thank you for asking your question, tcs08

Msdn code and explanation for mouse input with WM_INPUT

Msdn code and explanation for mouse input with WM_MOUSEMOVE

Sori answered 3/7, 2013 at 23:45 Comment(2)
Thanks for that outrageously good answer. You've covered all the bases to achieve what I was trying to achieve, and I indeed got it working in the end (accurate to the millimetre). Thanks!Malamud
DPI is not PPI. The mouse cursor is not moving 5,100 pixels on screen per inch of movement of the mouse itself on a surface. So, you can't divide the X and Y pixel deltas of cursor movement by DPI to get what the asker is asking for. The DPI value just means that the CCD in the mouse takes into account the equivalent of, in this case, 5,100 points of a light per inch ("dots per inch") into generating a mickey count (yes, that's the term) of movement units to send to the OS, which then translates it to a relative amount of pixels to displace mouse cursor displayed on the screen.Dorie

© 2022 - 2024 — McMap. All rights reserved.