6
votes

Calculer le DPI de l'écran

J'utilise Python pour créer une application de rendu d'image qui rend des tableaux numpy sous forme d'images. J'ai besoin d'un moyen d'automatiser l'écran de calcul DPI, car je génère des images de la même taille sur différents moniteurs (par exemple, 2x2 pouces). Je suis sous Windows 10, Python 3.7.

Je ne demande pas comment obtenir la taille d'écran du moniteur - comme savoir si votre moniteur fait 3000x2000 pixels (comme a été résolu à l'adresse: Comment obtenir la résolution du moniteur en Python ? ).

Je veux plutôt quelque chose qui calcule le DPI spécifiquement, et qui prend en compte tous les paramètres d'échelle appliqués par l'utilisateur et qui pourraient changer le DPI de son système. Par exemple, j'ai une échelle de 250%.

Suite à cette réponse sur experts-exchange ( https://www.experts-exchange.com/questions/21794611/Detecting-system-DPI-settings-in-Python.html ), j'ai essayé d'utiliser ceci: p >

from ctypes import windll

def get_ppi():
    LOGPIXELSX = 88
    LOGPIXELSY = 90
    user32 = windll.user32
    user32.SetProcessDPIAware()
    dc = user32.GetDC(0)   
    pix_per_inch = windll.gdi32.GetDeviceCaps(dc, LOGPIXELSX)
    print("Horizontal DPI is", windll.gdi32.GetDeviceCaps(dc, LOGPIXELSX))
    print("Vertical DPI is", windll.gdi32.GetDeviceCaps(dc, LOGPIXELSY))
    user32.ReleaseDC(0, dc)
    return pix_per_inch

Cela semble être la bonne approximation, mais renvoie un nombre trop faible. Il renvoie 240, alors que mon DPI réel est plus proche de 285.

Je préférerais éviter d'utiliser un framework GUI comme Qt pour cela, car je veux minimiser la surcharge, mais si c'est la seule façon, alors je volonté! Si je dois utiliser un framework, je préférerais PyQt5.


4 commentaires

Peut-être avez-vous un DPI par ensemble de moniteurs? Dans ce cas, essayez GetDpiForWindow () avec winDpi = user32.GetDpiForWindow (window)


@mnistic cela semble cool, mais cela ne semble pas fonctionner pour moi: quand je l'essaye pour window = 0 dans mon code, je viens de revenir 0. À moins que je ne le configure mal?


Passez le handle de fenêtre réel à GetDpiForWindow () , utilisez GetDpiForSystem () pour obtenir le DPI du système, c'est-à-dire sysDpi = user32.GetDpiForSystem ()


@mnistic cela semble être une excellente réponse :) Le GetDpiForSystem renvoie 240, ce que mon essai initial renvoie. Évidemment, je ne suis pas très bon avec tout ce truc windll.user32 : /


3 Réponses :


7
votes

Bien que j'aie dit que je voulais l'éviter, il existe un moyen très simple de le faire en utilisant PyQt5. Plus j'y pense, plus je pense que cela pourrait être la meilleure solution, car elle est largement indépendante de la plate-forme:

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

Notez que app.screens () renvoie une liste d'écrans. Dans mon cas, je n'en ai qu'un seul joint, mais vous pouvez en avoir plusieurs, alors assurez-vous de savoir sur quel écran vous devez obtenir des dpi. Et si vous gardez tout cela contenu dans une fonction, cela n'encombrera pas votre espace de noms avec des fichiers indésirables PyQt.

Aussi, pour en savoir plus sur QScreen ( sc est un objet QScreen) voir cette page de documentation:
https://doc.qt.io/qt-5/qscreen.html

Il y a toutes sortes de trucs sympas que vous pouvez en tirer.


0 commentaires

1
votes

Bien que la réponse de @ eric fonctionne, je préférerais éviter de quitter la bibliothèque standard (en ignorant les distributions de Python qui ne 'n'inclut pas ctypes ).

Cela m'a d'abord conduit à ctypes et vous pouvez voir ma réponse initiale (très compliquée) qui peut être trouvée ci-dessous. Plus récemment, j'ai trouvé un moyen de le faire avec juste Tk (je ne me souviens pas du lien vers celui-ci, je vais donc créer un lien vers une réponse Stack Overflow a> qui a le même contenu)

# 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))

Réponse originale

La réponse suivante propose deux solutions:

  1. Le premier est le DPI signalé par Windows en raison de la mise à l'échelle de l'affichage de l'utilisateur

  2. Le second est le DPI réel du moniteur calculé en trouvant la taille physique et la résolution du moniteur

Ces solutions supposent qu'il n'y a qu'un seul moniteur et définissent également la prise en compte du DPI du processus (qui ne convient pas à certains contextes). Pour plus de détails sur les appels d'API Windows effectués par ctypes , consultez la documentation de SetProcessDPIAwareness , GetDPIForWindow , GetDC et GetDeviceCaps .

Solution 1

Cette solution ne renvoie pas un DPI correct, et est à la place juste le "facteur de zoom" de Window pour ce moniteur. Pour obtenir le facteur, vous devez diviser le DPI retourné par 96 (ce qui signifie que 96 est un facteur d'échelle de 100% tandis que 120 signifie un facteur d'échelle de 125% , etc.).

La méthode utilise tkinter pour obtenir un HWND code> (un identifiant pour la fenêtre) puis ctypes pour obtenir le DPI de la nouvelle fenêtre.

# 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 strong>

Cette méthode devrait renvoyer le vrai DPI du moniteur, cependant, deux choses doivent être notées:

  1. La taille physique du moniteur est (parfois) signalée de manière incorrecte. Cela entraînera donc une valeur DPI complètement incorrecte, bien que je ne sois pas sûr de savoir comment éviter cela, sauf ajouter une vérification pour s'assurer qu'elle se trouve entre des valeurs raisonnables (quoi que cela signifie!).

  2. La résolution utilisée par Windows est souvent différente de la résolution réelle du moniteur. Cette méthode calcule le vrai DPI du moniteur, mais si vous souhaitez calculer le nombre de pixels virtuels par pouce physique, vous remplacez l'affectation de dw et dh par root.winfo_screenwidth () et root.winfo_screenheight () (respectivement)

Cette méthode utilise tkinter pour obtenir un HWND valide (un identifiant pour la fenêtre) puis ctypes pour obtenir un Contexte de l'appareil (DC) et détails sur le matériel.

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


5 commentaires

Je n'ai qu'un seul moniteur et je suis sous Windows10, mais vos deux solutions différentes donnent deux réponses très différentes, qui ne sont ni les 100 que j'attendais. La première solution donne 96 , la seconde solution donne 141,8 141,4 141,7


La première solution ne renvoie pas un DPI correct, mais est simplement le "facteur de zoom" de Windows pour ce moniteur ( 96 signifie que vous l'avez défini sur 100% - < code> 125% indiquerait 120 DPI). La deuxième réponse ne doit donner une lecture incorrecte que si les dimensions physiques sont incorrectement signalées ou si le 100 que vous avez indiqué est calculé en utilisant la résolution que vous conduisez plutôt que la résolution réelle de votre moniteur (vous pouvez vérifier les nombres à aide au débogage).


Remarque: si vous ne vouliez pas le nombre de pixels virtuels par pouce physique, vous remplaceriez l'affectation de dw et dh par root.winfo_screenwidth () et root.winfo_screenheight () (respectivement)


C'est pourquoi j'utilise juste Qt. Les choses se compliquent quand je ne le fais pas. Ils sont simples / pythoniques quand je le fais. :)


@ProQ Je ne sais pas s'il y a une relation ici ou si c'est une coïncidence, mais 100 x sqrt (2) == 141.4 .



0
votes

Si vous ne souhaitez pas utiliser Qt, vous pouvez utiliser votre dernière approche car dans mon cas, il y a une différence de 45,95 dpi entre une solution Qt et une solution ctypes. Je pense que c'est toujours la même chose pour tous les ordinateurs portables. Ajoutez simplement (45,96 dpi) à votre résultat chaque fois que vous utilisez des ctypes.

 capture d'écran pour la représentation


1 commentaires

Très intéressant, je n'ai pas vérifié cela mais si cela fonctionne, ce serait vraiment bien!