Compartilhar via


Pixels independentes de dispositivo e DPI

Para programar efetivamente com elementos gráficos do Windows, você deve entender dois conceitos relacionados:

  • Pontos por polegada (DPI)
  • DIPs (pixel independente do dispositivo).

Vamos começar com o DPI. Isso exigirá um pequeno desvio na 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

Nota

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 cada caractere na fonte tenha 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 em torno do texto. A linha tracejada é chamada de 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 (o de ascensão) e a parte abaixo da linha de base (a descendente de). Na fonte mostrada aqui, a subida é de 56 pontos e a descida é de 16 pontos.

uma ilustração que mostra uma fonte de 72 pontos.

Quando se trata de uma exibição de computador, no entanto, medir o tamanho do texto é problemático, pois os pixels não são todos do 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, as polegadas físicas não são uma medida útil, pois não há nenhuma relação fixa entre polegadas físicas e pixels. Em vez disso, as fontes são medidas em unidades de 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 como 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 ponto deriva da impressão, onde os ponto físicos de tinta são colocados no papel. Para exibições de computador, seria mais preciso dizer 96 pixels por polegada lógica, mas o termo DPI ficou travado.

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 tem 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 considerar o DPI, os seguintes defeitos poderão ser aparentes nas configurações de DPI alta:

  • 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 alta DPI, o DWM implementa um fallback útil. Se um programa não estiver marcado como ciente de DPI, o DWM dimensionará toda a interface do usuário para corresponder à configuração de DPI. Por exemplo, em 144 DPI, a interface do usuário é dimensionada em 150%, incluindo texto, 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 DPI alta. 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 de 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. Normalmente, o manifesto é 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 seguintes informações 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.

  1. No menu do Projeto, clique em Propriedade.
  2. No painel esquerdo, expanda de Propriedades de Configuração, expanda de Ferramenta de Manifesto e clique em de Entrada e Saída.
  3. Na caixa de texto Arquivos de Manifesto Adicionais, digite o nome do arquivo de manifesto e clique em OK.

Marcando 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 da fonte GDI são dimensionados para a configuração de DPI atual. 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.

um diagrama que mostra o dimensionamento de fonte dpi no gdi.

Se o aplicativo estiver com reconhecimento de DPI e você usar GDI para desenho, dimensione todas as coordenadas de desenho para corresponder ao DPI.

Direct2D e DPI

O Direct2D executa o dimensionamento automaticamente para corresponder à configuração de DPI. No Direct2D, as coordenadas são medidas em unidades chamadas DIPs ( de pixels independentes do dispositivo). Um DIP é definido como 1/96 de uma polegada lógica. No 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ê pedir ao Direct2D para desenhar um retângulo de 200 × 100, o retângulo será 300 × 150 pixels físicos. Além disso, o 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, o 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 de DPI atual.

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 USER_DEFAULT_SCREEN_DPI que é definido como 96. Para determinar o fator de dimensionamento, pegue o valor de DPI e divida por USER_DEFAULT_SCREEN_DPI.

A conversão de pixels físicos para 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 o 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

Recomendamos que, para um aplicativo da área de trabalho, você use GetDpiForWindow; e para um aplicativo UWP (Plataforma Universal do Windows), use DisplayInformation::LogicalDpi. Embora não o recomendamos, é 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 há mais suporte para alterar o 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 Definir 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 reencontrar a janela. O código a seguir mostra estas 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 reexame 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, normalmente você precisará recalcular a posição dos objetos que você 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.

Próximo

Usando cor em Direct2D