DPI e pixel indipendenti dal dispositivo
Per programmare in modo efficace con la grafica Windows, è necessario comprendere due concetti correlati:
- Punti per pollice (DPI)
- Pixel indipendenti dal dispositivo (DIP).
Si inizierà con DPI. Questo richiederà una breve deviazione nella tipografia. Nella tipografia, la dimensione del tipo viene misurata in unità denominate punti. Un punto è uguale a 1/72 di un pollice.
- 1 pt = 1/72 pollici
Nota
Si tratta della definizione di punto di pubblicazione desktop. Storicamente, la misura esatta di un punto è variata.
Ad esempio, un tipo di carattere a 12 punti è progettato per adattarsi a una riga di testo di 1/6" (12/72). Ovviamente, questo non significa che ogni carattere nel tipo di carattere è esattamente alto 1/6". Infatti, alcuni caratteri potrebbero essere più alti di 1/6". Ad esempio, in molti tipi di carattere il carattere Å è più alto dell'altezza nominale del tipo di carattere. Per la visualizzazione corretta, il tipo di carattere necessita di spazio aggiuntivo tra il testo. Questo spazio è denominato iniziale.
La figura seguente mostra un tipo di carattere a 72 punti. Le linee solide mostrano un rettangolo delimitatore alto 1" intorno al testo. La linea tratteggiata è detta linea di base. La maggior parte dei caratteri di un tipo di carattere si trova nella linea di base. L'altezza del tipo di carattere include la parte sopra la linea di base (salita ) e la parte sotto la linea di base ( discesa). Nel tipo di carattere mostrato qui, la salita è di 56 punti e la discesa è di 16 punti.
Quando si tratta di una visualizzazione del computer, tuttavia, la misurazione delle dimensioni del testo è problematica, perché i pixel non sono tutte le stesse dimensioni. Le dimensioni di un pixel dipendono da due fattori: la risoluzione dello schermo e le dimensioni fisiche del monitor. Pertanto, i pollici fisici non sono una misura utile, perché non esiste alcuna relazione fissa tra pollici fisici e pixel. I tipi di carattere vengono invece misurati in unità logiche . Un tipo di carattere a 72 punti è definito come un pollice logico alto. I pollici logici vengono quindi convertiti in pixel. Per molti anni, Windows ha usato la conversione seguente: un pollice logico è uguale a 96 pixel. Usando questo fattore di ridimensionamento, viene eseguito il rendering di un tipo di carattere a 72 punti come alto 96 pixel. Un tipo di carattere a 12 punti è alto 16 pixel.
- 12 punti = 12/72 pollici logici = 1/6 pollice logico = 96/6 pixel = 16 pixel
Questo fattore di ridimensionamento è descritto come 96 punti per pollice (DPI). Il termine punti deriva dalla stampa, dove i punti fisici dell'inchiostro vengono inseriti su carta. Per i display del computer, sarebbe più accurato dire 96 pixel per pollice logico, ma il termine DPI è bloccato.
Poiché le dimensioni effettive dei pixel variano, il testo leggibile in un monitor potrebbe essere troppo piccolo in un altro monitor. Inoltre, le persone hanno preferenze diverse: alcune persone preferiscono testo più grande. Per questo motivo, Windows consente all'utente di modificare l'impostazione DPI. Ad esempio, se l'utente imposta la visualizzazione su 144 DPI, un tipo di carattere a 72 punti è alto 144 pixel. Le impostazioni DPI standard sono 100% (96 DPI), 125% (120 DPI) e 150% (144 DPI). L'utente può anche applicare un'impostazione personalizzata. A partire da Windows 7, DPI è un'impostazione per utente.
Ridimensionamento DWM
Se un programma non tiene conto della dpi, i difetti seguenti potrebbero essere evidenti nelle impostazioni DPI elevate:
- Elementi dell'interfaccia utente ritagliati.
- Layout non corretto.
- Bitmap e icone pixelate.
- Coordinate del mouse non corrette, che possono influire sull'hit testing, trascinare e rilasciare e così via.
Per garantire che i programmi meno recenti funzionino con impostazioni con valori DPI elevati, DWM implementa un fallback utile. Se un programma non è contrassegnato come compatibile con DPI, il DWM ridimensiona l'intera interfaccia utente in modo che corrisponda all'impostazione DPI. Ad esempio, a 144 DPI, l'interfaccia utente viene ridimensionata del 150%, inclusi testo, grafica, controlli e dimensioni delle finestre. Se il programma crea una finestra di 500 × 500, la finestra viene effettivamente visualizzata come 750 × 750 pixel e il contenuto della finestra viene ridimensionato di conseguenza.
Questo comportamento significa che i programmi meno recenti "funzionano" con impostazioni DPI elevate. Tuttavia, il ridimensionamento comporta anche un aspetto leggermente sfocato, perché il ridimensionamento viene applicato dopo che la finestra viene disegnata.
Applicazioni compatibili con DPI
Per evitare il ridimensionamento DWM, un programma può contrassegnarsi come compatibile con DPI. Questo indica al DWM di non eseguire alcuna scalabilità DPI automatica. Tutte le nuove applicazioni devono essere progettate per essere compatibili con DPI, perché la consapevolezza dpi migliora l'aspetto dell'interfaccia utente con impostazioni DPI più elevate.
Un programma dichiara se stesso compatibile con DPI tramite il manifesto dell'applicazione. Un manifesto è semplicemente un file XML che descrive una DLL o un'applicazione. Il manifesto è in genere incorporato nel file eseguibile, anche se può essere fornito come file separato. Un manifesto contiene informazioni quali le dipendenze DLL, il livello dei privilegi richiesti e la versione di Windows per cui è stato progettato il programma.
Per dichiarare che il programma è compatibile con DPI, includere le informazioni seguenti nel manifesto.
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
L'elenco illustrato di seguito è solo un manifesto parziale, ma il linker di Visual Studio genera automaticamente il resto del manifesto. Per includere un manifesto parziale nel progetto, seguire questa procedura in Visual Studio.
- Scegliere Proprietà dal menu Progetto.
- Nel riquadro sinistro espandere Proprietà di configurazione, espandere Strumento manifesto e quindi fare clic su Input e output.
- Nella casella di testo File manifesto aggiuntivi digitare il nome del file manifesto e quindi fare clic su OK.
Contrassegnando il programma come compatibile con DPI, si indica al DWM di non ridimensionare la finestra dell'applicazione. Ora, se si crea una finestra di 500 × 500, la finestra occupa 500 × 500 pixel, indipendentemente dall'impostazione DPI dell'utente.
GDI e DPI
Il disegno GDI viene misurato in pixel. Ciò significa che se il programma è contrassegnato come compatibile con DPI e si chiede a GDI di disegnare un rettangolo di 200 × 100, il rettangolo risultante sarà largo 200 pixel e 100 pixel di altezza sullo schermo. Tuttavia, le dimensioni dei caratteri GDI vengono ridimensionate in base all'impostazione DPI corrente. In altre parole, se si crea un tipo di carattere a 72 punti, la dimensione del tipo di carattere sarà di 96 pixel a 96 DPI, ma 144 pixel a 144 DPI. Ecco un tipo di carattere da 72 punti di cui viene eseguito il rendering a 144 DPI usando GDI.
Se l'applicazione è compatibile con DPI e si usa GDI per disegnare, ridimensionare tutte le coordinate di disegno in modo che corrispondano al valore DPI.
Direct2D e DPI
Direct2D esegue automaticamente il ridimensionamento in modo che corrisponda all'impostazione DPI. In Direct2D le coordinate vengono misurate in unità denominate pixel indipendenti dal dispositivo (DIP). Un DIP viene definito come 1/96 di un pollice logico . In Direct2D tutte le operazioni di disegno vengono specificate in DIP e quindi ridimensionate all'impostazione DPI corrente.
Impostazione DPI | Dimensioni DIP |
---|---|
96 | 1 pixel |
120 | 1,25 pixel |
144 | 1,5 pixel |
Ad esempio, se l'impostazione DPI dell'utente è 144 DPI e si chiede a Direct2D di disegnare un rettangolo di 200 × 100, il rettangolo sarà 300 × 150 pixel fisici. Inoltre, DirectWrite misura le dimensioni dei tipi di carattere in DIP, anziché i punti. Per creare un tipo di carattere a 12 punti, specificare 16 DIP (12 punti = 1/6 pollice logico = 96/6 DIP). Quando il testo viene disegnato sullo schermo, Direct2D converte i DIP in pixel fisici. Il vantaggio di questo sistema è che le unità di misura sono coerenti sia per il testo che per il disegno, indipendentemente dall'impostazione DPI corrente.
Una parola di cautela: le coordinate del mouse e della finestra vengono ancora fornite in pixel fisici, non DIP. Ad esempio, se si elabora il messaggio WM_LBUTTONDOWN , la posizione del mouse verso il basso viene specificata in pixel fisici. Per disegnare un punto in tale posizione, è necessario convertire le coordinate pixel in DIP.
Conversione di pixel fisici in DIP
Il valore di base di DPI viene definito come USER_DEFAULT_SCREEN_DPI
impostato su 96. Per determinare il fattore di ridimensionamento, prendere il valore DPI e dividere per USER_DEFAULT_SCREEN_DPI
.
La conversione da pixel fisici a DIP usa la formula seguente.
DIPs = pixels / (DPI / USER_DEFAULT_SCREEN_DPI)
Per ottenere l'impostazione DPI, chiamare la funzione GetDpiForWindow . Il valore DPI viene restituito come valore a virgola mobile. Calcolare il fattore di ridimensionamento per entrambi gli assi.
float g_DPIScale = 1.0f;
void InitializeDPIScale(HWND hwnd)
{
float dpi = GetDpiForWindow(hwnd);
g_DPIScale = dpi / USER_DEFAULT_SCREEN_DPI;
}
template <typename T>
float PixelsToDipsX(T x)
{
return static_cast<float>(x) / g_DPIScale;
}
template <typename T>
float PixelsToDips(T y)
{
return static_cast<float>(y) / g_DPIScale;
}
Ecco un modo alternativo per ottenere l'impostazione DPI se non si usa Direct2D:
void InitializeDPIScale(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
g_DPIScaleX = (float)GetDeviceCaps(hdc, LOGPIXELSX) / USER_DEFAULT_SCREEN_DPI;
g_DPIScaleY = (float)GetDeviceCaps(hdc, LOGPIXELSY) / USER_DEFAULT_SCREEN_DPI;
ReleaseDC(hwnd, hdc);
}
Nota
È consigliabile che per un'app desktop si usi GetDpiForWindow; e per un'app piattaforma UWP (Universal Windows Platform) (UWP), usa DisplayInformation::LogicalDpi. Anche se non è consigliabile, è possibile impostare la consapevolezza dpi predefinita a livello di codice usando SetProcessDpiAwarenessContext. Dopo aver creato una finestra (HWND) nel processo, la modifica della modalità di riconoscimento DPI non è più supportata. Se si imposta la modalità di riconoscimento DPI predefinita del processo a livello di codice, è necessario chiamare l'API corrispondente prima della creazione di eventuali HWND. Per altre info, vedi Impostazione del riconoscimento DPI predefinito per un processo.
Ridimensionamento della destinazione di rendering
Se le dimensioni della finestra cambiano, è necessario ridimensionare la destinazione di rendering in modo che corrisponda. Nella maggior parte dei casi, sarà anche necessario aggiornare il layout e aggiornare la finestra. Il codice seguente illustra questi passaggi.
void MainWindow::Resize()
{
if (pRenderTarget != NULL)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
pRenderTarget->Resize(size);
CalculateLayout();
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
La funzione GetClientRect ottiene le nuove dimensioni dell'area client, in pixel fisici (non DIP). Il metodo ID2D1HwndRenderTarget::Resize aggiorna le dimensioni della destinazione di rendering, specificate anche in pixel. La funzione InvalidateRect forza un aggiornamento aggiungendo l'intera area client all'area di aggiornamento della finestra. Vedere Disegnare la finestra, nel modulo 1.
Man mano che la finestra cresce o si riduce, è in genere necessario ricalcolare la posizione degli oggetti di cui si disegna. Ad esempio, nel programma circle, il raggio e il punto centrale devono essere aggiornati:
void MainWindow::CalculateLayout()
{
if (pRenderTarget != NULL)
{
D2D1_SIZE_F size = pRenderTarget->GetSize();
const float x = size.width / 2;
const float y = size.height / 2;
const float radius = min(x, y);
ellipse = D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius);
}
}
Il metodo ID2D1RenderTarget::GetSize restituisce le dimensioni della destinazione di rendering in DIPs (non pixel), ovvero l'unità appropriata per il calcolo del layout. Esiste un metodo strettamente correlato, ID2D1RenderTarget::GetPixelSize, che restituisce le dimensioni in pixel fisici. Per una destinazione di rendering HWND , questo valore corrisponde alle dimensioni restituite da GetClientRect. Tuttavia, tenere presente che il disegno viene eseguito in DIP, non in pixel.