I followed this answer and it work great with one monitor (despite it was 11 years old :D). But when I attached my 2nd monitor and use it, it doesn't work anymore. Instead, it always give weird color like all black when x > 2500
Edit: I discovered the problem, it was due to different text scale on both monitor (1st is 125% and 2nd is 100%), I change setting both to 125% and work like a charm. Now the problem is, how can I automatic detect font scaling on other monitor (currently I use this for first monitor)
So the problem is different scales in each monitor
First, I detect index of which monitor the input point
is in with this answer :
private int ConvertMousePointToScreenIndex(Point mousePoint)
{
//first get all the screens
System.Drawing.Rectangle ret;
for (int i = 1; i <= Screen.AllScreens.Count(); i++)
{
ret = Screen.AllScreens[i - 1].Bounds;
if (ret.Contains(mousePoint))
return i - 1;
}
return 0;
}
Second, I just need to get real width / virtual width
of that indexed monitor based on this answer
const int ENUM_CURRENT_SETTINGS = -1;
public double GetScale(Point point)
{
Screen screen = Screen.AllScreens[ConvertMousePointToScreenIndex(point)];
DEVMODE dm = new DEVMODE();
dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));
EnumDisplaySettings(screen.DeviceName, ENUM_CURRENT_SETTINGS, ref dm);
double RealWidth = dm.dmPelsWidth;
double VirtualWidth = screen.Bounds.Width;
return RealWidth / VirtualWidth;
}
[DllImport("user32.dll")]
public static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE
{
private const int CCHDEVICENAME = 0x20;
private const int CCHFORMNAME = 0x20;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public int dmPositionX;
public int dmPositionY;
public ScreenOrientation dmDisplayOrientation;
public int dmDisplayFixedOutput;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
}
And finally, apply it to GetColorAt :
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
Bitmap screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
public Color GetColorAt(Point location)
{
double scale = GetScale(location);
using (Graphics gdest = Graphics.FromImage(screenPixel))
{
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, (int)Math.Round(point.X * scale), (int)Math.Round(point.Y * scale), (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
}
return screenPixel.GetPixel(0, 0);
}