DPI e pixels independentes de dispositivo
Para programar efetivamente com elementos gráficos do Windows, você deve entender dois conceitos relacionados:
- Pontos por polegada (DPI)
- DIPs (pixel independente de dispositivo).
Vamos começar com o DPI. Isso exigirá um pequeno desvio para tipografia. Na tipografia, o tamanho do tipo é medido em unidades chamadas pontos. Um ponto é igual a 1/72 de polegada.
- 1 pt = 1/72 polegada
Observação
Essa é a definição de ponto de publicação da área de trabalho. Historicamente, a medida exata de um ponto variou.
Por exemplo, uma fonte de 12 pontos foi projetada para caber dentro de uma linha de texto de 1/6" (12/72). Obviamente, isso não significa que todos os caracteres na fonte tenham exatamente 1/6" de altura. Na verdade, alguns caracteres podem ser mais altos que 1/6". Por exemplo, em muitas fontes, o caractere Å é mais alto que a altura nominal da fonte. Para exibir corretamente, a fonte precisa de algum espaço adicional entre o texto. Esse espaço é chamado de à esquerda.
A ilustração a seguir mostra uma fonte de 72 pontos. As linhas sólidas mostram uma caixa delimitadora de 1" de altura ao redor do texto. A linha tracejada é chamada de linha de base. A maioria dos caracteres em uma fonte está na linha de base. A altura da fonte inclui a parte acima da linha de base (a ascensão) e a parte abaixo da linha de base (a descida). Na fonte mostrada aqui, a ascensão é de 56 pontos e a descida é de 16 pontos.
No entanto, quando se trata de uma exibição de computador, medir o tamanho do texto é problemático, pois os pixels não têm o mesmo tamanho. O tamanho de um pixel depende de dois fatores: a resolução de exibição e o tamanho físico do monitor. Portanto, polegadas físicas não são uma medida útil, porque não há nenhuma relação fixa entre polegadas físicas e pixels. Em vez disso, as fontes são medidas em unidades lógicas . Uma fonte de 72 pontos é definida como uma polegada lógica de altura. As polegadas lógicas são convertidas em pixels. Por muitos anos, o Windows usou a seguinte conversão: uma polegada lógica é igual a 96 pixels. Usando esse fator de dimensionamento, uma fonte de 72 pontos é renderizada com 96 pixels de altura. Uma fonte de 12 pontos tem 16 pixels de altura.
- 12 pontos = 12/72 polegada lógica = 1/6 polegada lógica = 96/6 pixels = 16 pixels
Esse fator de dimensionamento é descrito como DPI (96 pontos por polegada). O termo dots deriva da impressão, onde os ponto físicos de tinta são colocados no papel. Para telas de computador, seria mais preciso dizer 96 pixels por polegada lógica, mas o termo DPI ficou preso.
Como os tamanhos reais de pixel variam, o texto legível em um monitor pode ser muito pequeno em outro monitor. Além disso, as pessoas têm preferências diferentes— algumas pessoas preferem texto maior. Por esse motivo, o Windows permite que o usuário altere a configuração de DPI. Por exemplo, se o usuário definir a exibição como 144 DPI, uma fonte de 72 pontos será de 144 pixels de altura. As configurações de DPI padrão são 100% (96 DPI), 125% (120 DPI) e 150% (144 DPI). O usuário também pode aplicar uma configuração personalizada. A partir do Windows 7, o DPI é uma configuração por usuário.
Dimensionamento de DWM
Se um programa não levar em conta o DPI, os seguintes defeitos poderão ser aparentes nas configurações de alto DPI:
- Elementos de interface do usuário recortados.
- Layout incorreto.
- Bitmaps e ícones pixelados.
- Coordenadas incorretas do mouse, que podem afetar o teste de clique, arrastar e soltar e assim por diante.
Para garantir que os programas mais antigos funcionem em configurações de alto DPI, o DWM implementa um fallback útil. Se um programa não estiver marcado como com reconhecimento de DPI, o DWM dimensionará toda a interface do usuário para corresponder à configuração do DPI. Por exemplo, em 144 DPI, a interface do usuário é dimensionada em 150%, incluindo texto, elementos gráficos, controles e tamanhos de janela. Se o programa criar uma janela de 500 × 500, a janela será exibida como 750 × 750 pixels e o conteúdo da janela será dimensionado adequadamente.
Esse comportamento significa que os programas mais antigos "apenas funcionam" em configurações de alto DPI. No entanto, o dimensionamento também resulta em uma aparência um pouco desfocada, pois o dimensionamento é aplicado depois que a janela é desenhada.
Aplicativos com reconhecimento de DPI
Para evitar o dimensionamento do DWM, um programa pode se marcar como com reconhecimento de DPI. Isso informa ao DWM para não executar nenhum dimensionamento automático de DPI. Todos os novos aplicativos devem ser projetados para serem com reconhecimento de DPI, pois o reconhecimento de DPI melhora a aparência da interface do usuário em configurações de DPI mais altas.
Um programa se declara com reconhecimento de DPI por meio do manifesto do aplicativo. Um manifesto é simplesmente um arquivo XML que descreve uma DLL ou um aplicativo. O manifesto normalmente é inserido no arquivo executável, embora possa ser fornecido como um arquivo separado. Um manifesto contém informações como dependências de DLL, o nível de privilégio solicitado e para qual versão do Windows o programa foi projetado.
Para declarar que seu programa tem reconhecimento de DPI, inclua as informações a seguir no 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>
A listagem mostrada aqui é apenas um manifesto parcial, mas o vinculador do Visual Studio gera o restante do manifesto para você automaticamente. Para incluir um manifesto parcial em seu projeto, execute as etapas a seguir no Visual Studio.
- No menu Projeto , clique em Propriedade.
- No painel esquerdo, expanda Propriedades de Configuração, expanda Ferramenta de Manifesto e clique em Entrada e Saída.
- Na caixa de texto Arquivos de Manifesto Adicionais , digite o nome do arquivo de manifesto e clique em OK.
Ao marcar seu programa como com reconhecimento de DPI, você está dizendo ao DWM para não dimensionar a janela do aplicativo. Agora, se você criar uma janela de 500 × 500, a janela ocupará 500 × 500 pixels, independentemente da configuração de DPI do usuário.
GDI e DPI
O desenho GDI é medido em pixels. Isso significa que, se o programa estiver marcado como com reconhecimento de DPI e você pedir à GDI para desenhar um retângulo de 200 × 100, o retângulo resultante terá 200 pixels de largura e 100 pixels de altura na tela. No entanto, os tamanhos de fonte GDI são dimensionados para a configuração atual do DPI. Em outras palavras, se você criar uma fonte de 72 pontos, o tamanho da fonte será de 96 pixels a 96 DPI, mas 144 pixels a 144 DPI. Aqui está uma fonte de 72 pontos renderizada em 144 DPI usando GDI.
Se o aplicativo tiver reconhecimento de DPI e você usar a GDI para desenhar, dimensione todas as coordenadas de desenho para corresponder ao DPI.
Direct2D e DPI
Direct2D executa o dimensionamento automaticamente para corresponder à configuração de DPI. Em Direct2D, as coordenadas são medidas em unidades chamadas DIPs (pixels independentes de dispositivo). Um DIP é definido como 1/96 de polegada lógica . Em Direct2D, todas as operações de desenho são especificadas em DIPs e, em seguida, dimensionadas para a configuração de DPI atual.
Configuração de DPI | Tamanho do DIP |
---|---|
96 | 1 pixel |
120 | 1,25 pixels |
144 | 1,5 pixels |
Por exemplo, se a configuração de DPI do usuário for 144 DPI e você solicitar que Direct2D desenhe um retângulo de 200 × 100, o retângulo terá 300 × 150 pixels físicos. Além disso, DirectWrite mede tamanhos de fonte em DIPs, em vez de pontos. Para criar uma fonte de 12 pontos, especifique 16 DIPs (12 pontos = 1/6 polegada lógica = 96/6 DIPs). Quando o texto é desenhado na tela, Direct2D converte os DIPs em pixels físicos. O benefício desse sistema é que as unidades de medida são consistentes para texto e desenho, independentemente da configuração atual do DPI.
Uma palavra de cuidado: as coordenadas do mouse e da janela ainda são fornecidas em pixels físicos, não em DIPs. Por exemplo, se você processar a mensagem WM_LBUTTONDOWN , a posição do mouse para baixo será fornecida em pixels físicos. Para desenhar um ponto nessa posição, você deve converter as coordenadas de pixel em DIPs.
Convertendo pixels físicos em DIPs
O valor base do DPI é definido como , que é definido como USER_DEFAULT_SCREEN_DPI
96. Para determinar o fator de dimensionamento, use o valor de DPI e divida por USER_DEFAULT_SCREEN_DPI
.
A conversão de pixels físicos em DIPs usa a fórmula a seguir.
DIPs = pixels / (DPI / USER_DEFAULT_SCREEN_DPI)
Para obter a configuração de DPI, chame a função GetDpiForWindow . O DPI é retornado como um valor de ponto flutuante. Calcule o fator de dimensionamento para ambos os eixos.
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 PixelsToDipsY(T y)
{
return static_cast<float>(y) / g_DPIScale;
}
Aqui está uma maneira alternativa de obter a configuração de DPI se você não estiver usando 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);
}
Observação
Recomendamos que, para um aplicativo da área de trabalho, você use GetDpiForWindow; e para um aplicativo Plataforma Universal do Windows (UWP), use DisplayInformation::LogicalDpi. Embora não o recomendemos, é possível definir o reconhecimento de DPI padrão programaticamente usando SetProcessDpiAwarenessContext. Depois que uma janela (um HWND) tiver sido criada em seu processo, não haverá mais suporte para a alteração do modo de reconhecimento de DPI. Se você estiver definindo o modo de reconhecimento de DPI padrão do processo programaticamente, deverá chamar a API correspondente antes que os HWNDs tenham sido criados. Para obter mais informações, consulte Configurando o reconhecimento de DPI padrão para um processo.
Redimensionando o destino de renderização
Se o tamanho da janela for alterado, você deverá redimensionar o destino de renderização para corresponder. Na maioria dos casos, você também precisará atualizar o layout e repintar a janela. O código a seguir mostra essas etapas.
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);
}
}
A função GetClientRect obtém o novo tamanho da área do cliente, em pixels físicos (não em DIPs). O método ID2D1HwndRenderTarget::Resize atualiza o tamanho do destino de renderização, também especificado em pixels. A função InvalidateRect força uma repinta adicionando toda a área do cliente à região de atualização da janela. (Consulte Pintando a janela, no Módulo 1.)
À medida que a janela cresce ou encolhe, você normalmente precisará recalcular a posição dos objetos que desenha. Por exemplo, no programa de círculo, o raio e o ponto central devem ser atualizados:
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);
}
}
O método ID2D1RenderTarget::GetSize retorna o tamanho do destino de renderização em DIPs (não pixels), que é a unidade apropriada para calcular o layout. Há um método intimamente relacionado, ID2D1RenderTarget::GetPixelSize, que retorna o tamanho em pixels físicos. Para um destino de renderização HWND , esse valor corresponde ao tamanho retornado por GetClientRect. Mas lembre-se de que o desenho é executado em DIPs, não em pixels.