Search code examples
c#winformsgdi+pixel

How to read the Color of other Monitor Pixel


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

enter image description here

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)


Solution

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