다음을 통해 공유


DPI 및 디바이스 독립적 픽셀

Windows 그래픽으로 효과적으로 프로그래밍하려면 다음 두 가지 관련 개념을 이해해야 합니다.

  • DPI(인치당 도트 수)
  • 디바이스 독립적 픽셀(DIP).

DPI로 시작해 보겠습니다. 이렇게 하려면 입력 체계로 잠시 우회해야 합니다. 입력 체계에서 형식의 크기는 지점이라는 단위로 측정됩니다. 1포인트는 1/72인치입니다.

1pt = 1/72인치

메모

이는 지점의 데스크톱 게시 정의입니다. 역사적으로 지점의 정확한 측정값은 다양합니다.

예를 들어 12포인트 글꼴은 1/6"(12/72) 텍스트 줄에 맞게 설계되었습니다. 글꼴의 모든 문자 높이가 정확히 1/6"인 것은 아닙니다. 실제로 일부 문자는 1/6보다 큰 것일 수 있습니다. 예를 들어 많은 글꼴에서 문자 Å는 글꼴의 명목 높이보다 큽니다. 올바르게 표시하려면 글꼴 사이에 약간의 추가 공간이 필요합니다. 이 공간을 선행.

다음 그림에서는 72포인트 글꼴을 보여 줍니다. 실선은 텍스트 주위에 1" 높이의 경계 상자를 표시합니다. 파선은 기준선이라고 합니다. 글꼴에 있는 문자의 대부분은 기준선에 있습니다. 글꼴의 높이에는 기준선 위의 부분(상승) 및 기준선 아래 부분(하강)이 포함됩니다. 여기에 표시된 글꼴에서 상승은 56포인트이고 하강은 16포인트입니다.

72포인트 글꼴을 보여 주는 그림을 .

그러나 컴퓨터 디스플레이의 경우 픽셀 크기가 모두 동일하지 않기 때문에 텍스트 크기를 측정하는 것은 문제가 됩니다. 픽셀의 크기는 디스플레이 해상도와 모니터의 실제 크기라는 두 가지 요인에 따라 달라집니다. 따라서 실제 인치와 픽셀 사이에 고정된 관계가 없으므로 물리적 인치는 유용한 측정값이 아닙니다. 대신 글꼴은 논리 단위로 측정됩니다. 72포인트 글꼴은 1개의 논리적 인치 높이로 정의됩니다. 그런 다음 논리적 인치가 픽셀로 변환됩니다. 몇 년 동안 Windows는 다음 변환을 사용했습니다. 하나의 논리적 인치는 96픽셀과 같습니다. 이 배율 인수를 사용하면 72포인트 글꼴이 96픽셀 높이로 렌더링됩니다. 12포인트 글꼴은 16픽셀 높이입니다.

12포인트 = 12/72 논리 인치 = 1/6 논리 인치 = 96/6 픽셀 = 16픽셀

이 배율 인수는 DPI(인치당 96개 점)로 설명됩니다. 점이라는 용어는 잉크의 실제 점이 종이에 놓이는 인쇄에서 파생됩니다. 컴퓨터 디스플레이의 경우 논리 인치당 96픽셀을 말하는 것이 더 정확하지만 DPI라는 용어는 중단되었습니다.

실제 픽셀 크기는 다양하기 때문에 한 모니터에서 읽을 수 있는 텍스트가 다른 모니터에서 너무 작을 수 있습니다. 또한 다른 기본 설정이 있습니다. 어떤 사람들은 더 큰 텍스트를 선호합니다. 이러한 이유로 Windows를 사용하면 사용자가 DPI 설정을 변경할 수 있습니다. 예를 들어 사용자가 디스플레이를 144DPI로 설정하는 경우 72포인트 글꼴의 높이가 144픽셀입니다. 표준 DPI 설정은 100개%(96DPI), 125개%(120DPI) 및 150개%(144DPI)입니다. 사용자는 사용자 지정 설정을 적용할 수도 있습니다. Windows 7부터 DPI는 사용자별 설정입니다.

DWM 크기 조정

프로그램이 DPI를 고려하지 않는 경우 높은 DPI 설정에서 다음과 같은 결함이 나타날 수 있습니다.

  • 잘린 UI 요소입니다.
  • 레이아웃이 잘못되었습니다.
  • 픽셀화된 비트맵 및 아이콘
  • 적중 테스트, 끌어서 놓기 등에 영향을 줄 수 있는 잘못된 마우스 좌표입니다.

이전 프로그램이 높은 DPI 설정에서 작동하도록 하기 위해 DWM은 유용한 대체를 구현합니다. 프로그램이 DPI 인식으로 표시되지 않으면 DWM은 DPI 설정과 일치하도록 전체 UI의 크기를 조정합니다. 예를 들어 144DPI에서 UI는 텍스트, 그래픽, 컨트롤 및 창 크기를 포함하여 150개%크기가 조정됩니다. 프로그램에서 500개 × 500개의 창을 만드는 경우 창은 실제로 750픽셀 × 750픽셀로 표시되고 그에 따라 창 내용의 크기가 조정됩니다.

이 동작은 이전 프로그램이 높은 DPI 설정에서 "작동"한다는 것을 의미합니다. 그러나 크기를 조정하면 창이 그려진 후에 크기 조정이 적용되므로 모양이 다소 흐리게 표시됩니다.

DPI 인식 애플리케이션

DWM 크기 조정을 방지하기 위해 프로그램은 자신을 DPI 인식으로 표시할 수 있습니다. 그러면 DWM에서 자동 DPI 크기 조정을 수행하지 않도록 지시합니다. DPI 인식은 더 높은 DPI 설정에서 UI의 모양을 향상하기 때문에 모든 새 애플리케이션은 DPI를 인식하도록 설계되어야 합니다.

프로그램은 애플리케이션 매니페스트를 통해 DPI 인식 자체를 선언합니다. 매니페스트 DLL 또는 애플리케이션을 설명하는 XML 파일일 뿐입니다. 매니페스트는 일반적으로 실행 파일에 포함되지만 별도의 파일로 제공할 수 있습니다. 매니페스트에는 DLL 종속성, 요청된 권한 수준 및 프로그램이 디자인된 Windows 버전과 같은 정보가 포함됩니다.

프로그램이 DPI 인식임을 선언하려면 매니페스트에 다음 정보를 포함합니다.

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

여기에 표시된 목록은 부분 매니페스트에 불과하지만 Visual Studio 링커는 자동으로 나머지 매니페스트를 생성합니다. 프로젝트에 부분 매니페스트를 포함하려면 Visual Studio에서 다음 단계를 수행합니다.

  1. 프로젝트 메뉴에서 속성클릭합니다.
  2. 왼쪽 창에서 구성 속성확장하고 매니페스트 도구확장한 다음 입력 및 출력클릭합니다.
  3. 추가 매니페스트 파일 텍스트 상자에 매니페스트 파일의 이름을 입력한 다음 확인클릭합니다.

프로그램을 DPI 인식으로 표시하여 DWM에 애플리케이션 창의 크기를 조정하지 말라고 합니다. 이제 500 × 500개의 창을 만들면 사용자의 DPI 설정에 관계없이 창이 500× 500픽셀을 차지합니다.

GDI 및 DPI

GDI 드로잉은 픽셀 단위로 측정됩니다. 즉, 프로그램이 DPI 인식으로 표시되고 GDI에 200개 × 100개의 사각형을 그리도록 요청하는 경우 결과 사각형은 화면에서 너비가 200픽셀, 높이가 100픽셀입니다. 그러나 GDI 글꼴 크기는 현재 DPI 설정으로 조정됩니다. 즉, 72포인트 글꼴을 만드는 경우 글꼴의 크기는 96DPI에서 96픽셀이지만 144DPI에서는 144픽셀입니다. 다음은 GDI를 사용하여 144 DPI에서 렌더링되는 72포인트 글꼴입니다.

gdi에서 dpi 글꼴 크기 조정을 보여 주는 다이어그램을 .

애플리케이션이 DPI를 인식하고 그리기에 GDI를 사용하는 경우 모든 그리기 좌표의 크기를 DPI와 일치하도록 조정합니다.

Direct2D 및 DPI

Direct2D는 DPI 설정과 일치하도록 크기 조정을 자동으로 수행합니다. Direct2D에서 좌표는 디바이스 독립적 픽셀(DIP)라는 단위로 측정됩니다. DIP는 논리적 인치의 1/96으로 정의됩니다. Direct2D에서 모든 그리기 작업은 DIP에 지정된 다음 현재 DPI 설정으로 크기가 조정됩니다.

DPI 설정 DIP 크기
96 1픽셀
120 1.25픽셀
144 1.5픽셀

예를 들어 사용자의 DPI 설정이 144DPI이고 Direct2D에 200 × 100 사각형을 그리도록 요청하는 경우 사각형은 300 × 150 물리적 픽셀이 됩니다. 또한 DirectWrite는 포인트가 아닌 DIP의 글꼴 크기를 측정합니다. 12포인트 글꼴을 만들려면 16개의 DIP(12포인트 = 1/6 논리 인치 = 96/6 DIP)를 지정합니다. 텍스트가 화면에 그려지면 Direct2D는 DIP를 실제 픽셀로 변환합니다. 이 시스템의 이점은 측정 단위가 현재 DPI 설정에 관계없이 텍스트와 그리기 모두에 대해 일관적이라는 것입니다.

주의 사항: 마우스 및 창 좌표는 여전히 DIP가 아닌 실제 픽셀로 제공됩니다. 예를 들어 WM_LBUTTONDOWN 메시지를 처리하는 경우 마우스 아래쪽 위치는 실제 픽셀로 제공됩니다. 해당 위치에 점을 그리려면 픽셀 좌표를 DIP로 변환해야 합니다.

물리적 픽셀을 DIP로 변환

DPI의 기본 값은 96으로 설정된 USER_DEFAULT_SCREEN_DPI 정의됩니다. 배율 인수를 확인하려면 DPI 값을 가져와서 USER_DEFAULT_SCREEN_DPI나눕니다.

물리적 픽셀에서 DIP로의 변환은 다음 수식을 사용합니다.

DIPs = pixels / (DPI / USER_DEFAULT_SCREEN_DPI)

DPI 설정을 얻으려면 GetDpiForWindow 함수를 호출합니다. DPI는 부동 소수점 값으로 반환됩니다. 두 축의 배율 인수를 계산합니다.

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;
}

Direct2D를 사용하지 않는 경우 DPI 설정을 가져오는 다른 방법은 다음과 같습니다.

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);
}

메모

데스크톱 앱의 경우 GetDpiForWindow ;사용하는 것이 좋습니다. UWP(유니버설 Windows 플랫폼) 앱의 경우 displayInformation::LogicalDpi 사용합니다. 권장하지는 않지만 SetProcessDpiAwarenessContext사용하여 프로그래밍 방식으로 기본 DPI 인식을 설정할 수 있습니다. 프로세스에서 창(HWND)이 만들어지면 DPI 인식 모드 변경이 더 이상 지원되지 않습니다. 프로그래밍 방식으로 프로세스 기본 DPI 인식 모드를 설정하는 경우 HWND를 만들기 전에 해당 API를 호출해야 합니다. 자세한 내용은 프로세스 대한 기본 DPI 인식 설정참조하세요.

렌더링 대상 크기 조정

창 크기가 변경되면 렌더링 대상의 크기를 일치하도록 조정해야 합니다. 대부분의 경우 레이아웃을 업데이트하고 창을 다시 그려야 합니다. 다음 코드는 이러한 단계를 보여줍니다.

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);
    }
}

GetClientRect 함수는 클라이언트 영역의 새 크기를 DIP가 아닌 물리적 픽셀로 가져옵니다. ID2D1HwndRenderTarget::Resize 메서드는 렌더링 대상의 크기도 픽셀 단위로 업데이트합니다. InvalidateRect 함수는 전체 클라이언트 영역을 창의 업데이트 영역에 추가하여 다시 칠합니다. (모듈 1에서 창 그리기 참조)

창이 커지거나 축소되면 일반적으로 그리는 개체의 위치를 다시 계산해야 합니다. 예를 들어 원 프로그램에서 반지름과 중심점을 업데이트해야 합니다.

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);
    }
}

ID2D1RenderTarget::GetSize 메서드는 레이아웃 계산에 적합한 단위인 DIP(픽셀 아님)로 렌더링 대상의 크기를 반환합니다. ID2D1RenderTarget::GetPixelSize밀접하게 관련된 메서드가 있습니다. 이 메서드는 실제 픽셀 단위로 크기를 반환합니다. HWND 렌더링 대상의 경우 이 값은 GetClientRect반환된 크기와 일치합니다. 그러나 그리기는 픽셀이 아닌 DIP에서 수행됩니다.

다음

Direct2D 색 사용