Calculate screen DPI

前端 未结 3 1617
广开言路
广开言路 2021-01-02 23:48

I am using Python to build an image-rendering app that renders numpy arrays as images. I need a way to automate the calculation screen DPI, as I am generating images of the

相关标签:
3条回答
  • 2021-01-03 00:11

    If you don't want to use Qt, you can use your last approach because in my case, there is a 45.95 dpi difference between a Qt solution and ctypes solution. I think it's always the same for all laptops. Just add (45.96 dpi) on your result every time you use ctypes.

    0 讨论(0)
  • 2021-01-03 00:12

    While I said I wanted to avoid it, there is one very simple way to pull this off using PyQt5. The more I think about it, the more I think this could be the best solution, as it is largely platform independent:

    import sys
    from PyQt5.QtWidgets import QApplication
    app = QApplication(sys.argv)
    screen = app.screens()[0]
    dpi = screen.physicalDotsPerInch()
    app.quit()
    

    Note that app.screens() returns a list of screens. In my case I only have one attached but you may have multiple, so be sure to be aware of which screen you need to get dpi from. And if you keep all this contained in a function, it won't clutter your namespace with PyQt junk.

    Also, for more on QScreen (sc is a QScreen object) see this doc page:
    https://doc.qt.io/qt-5/qscreen.html

    There's all sorts of cool stuff you can pull from it.

    0 讨论(0)
  • 2021-01-03 00:15

    While @eric's answer works, I would prefer to avoid leaving the standard library (ignoring the distributions of Python that don't include ctypes).

    This initially led me to ctypes and you can see my initial (very over-complicated) answer which can be found below. More recently I found a way to do it with just Tk (I can't remember the link to it so will link to a Stack Overflow answer which has the same content)

    import tkinter
    root = tkinter.Tk()
    dpi = root.winfo_fpixels('1i')
    

    Original answer

    The following answer offers two solutions:

    1. The first is Windows' reported DPI due to the user's display scaling

    2. The second is the monitor's true DPI calculated by finding the monitor's physical size and resolution

    These solutions assume there is only one monitor and also sets process DPI awareness (which won't be suitable for some contexts). For details on the Windows API calls made by ctypes see the documentation for SetProcessDPIAwareness, GetDPIForWindow, GetDC and GetDeviceCaps.

    Solution 1

    This solution doesn't return a proper DPI, and is instead just Window's "zoom factor" for that monitor. To get the factor, you should divide the returned DPI by 96 (meaning 96 is a scale factor of 100% while 120 means a scale factor of 125%, etc.).

    The method uses tkinter to get a valid HWND (an identifier for the window) and then ctypes to get the DPI for the new window.

    # Import the libraries
    import ctypes
    import tkinter
    
    # Set process DPI awareness
    ctypes.windll.shcore.SetProcessDpiAwareness(1)
    # Create a tkinter window
    root = tkinter.Tk()
    # Get the reported DPI from the window's HWND
    dpi = ctypes.windll.user32.GetDpiForWindow(root.winfo_id())
    # Print the DPI
    print(dpi)
    # Destroy the window
    root.destroy()
    

    Solution 2

    This method should return the monitor's true DPI, however, two things should be noted:

    1. The physical size of the monitor is (occasionally) reported incorrectly. This will therefore result in a completely incorrect DPI value, though I am not sure how to prevent this except add a check to ensure it is between sensible values (whatever that means!).

    2. The resolution Windows is using is often different to the monitor's actual resolution. This method calculates the monitor's true DPI, but if you want to calculate the number of virtual pixels per physical inch, you would replace the assignment of dw and dh with root.winfo_screenwidth() and root.winfo_screenheight() (respectively)

    This method uses tkinter to get a valid HWND (an identifier for the window) and then ctypes to get a Device Context (DC) & hardware details from this.

    # Our convertion from millimeters to inches
    MM_TO_IN = 0.0393700787
    
    # Import the libraries
    import ctypes
    import math
    import tkinter
    
    # Set process DPI awareness
    ctypes.windll.shcore.SetProcessDpiAwareness(1)
    # Create a tkinter window
    root = tkinter.Tk()
    # Get a DC from the window's HWND
    dc = ctypes.windll.user32.GetDC(root.winfo_id())
    # The the monitor phyical width
    # (returned in millimeters then converted to inches)
    mw = ctypes.windll.gdi32.GetDeviceCaps(dc, 4) * MM_TO_IN
    # The the monitor phyical height
    mh = ctypes.windll.gdi32.GetDeviceCaps(dc, 6) * MM_TO_IN
    # Get the monitor horizontal resolution
    dw = ctypes.windll.gdi32.GetDeviceCaps(dc, 8)
    # Get the monitor vertical resolution
    dh = ctypes.windll.gdi32.GetDeviceCaps(dc, 10)
    # Destroy the window
    root.destroy()
    
    # Horizontal and vertical DPIs calculated
    hdpi, vdpi = dw / mw, dh / mh
    # Diagonal DPI calculated using Pythagoras
    ddpi = math.hypot(dw, dh) / math.hypot(mw, mh)
    # Print the DPIs
    print(round(hdpi, 1), round(vdpi, 1), round(ddpi, 1))
    
    0 讨论(0)
提交回复
热议问题