이 문서에서는 물리적 픽셀과 디바이스 독립적 픽셀(DIP)의 차이점과 Win2D에서 DPI(인치당 점)를 처리하는 방법을 설명합니다.
Win2D는 낮은 DPI 디바이스와 높은 DPI 디바이스 모두에서 실행될 때 올바른 작업을 수행하는 합리적인 기본 동작을 제공하므로 많은 앱에서 이러한 구분을 무시할 수 있는 방식으로 설계되었습니다. 앱에 더 특수한 요구 사항이 있거나 "합리적인 기본값"의 의미에 대해 생각이 다르다면, 아래에서 자세한 내용을 계속 읽어보세요...
DPI란?
DPI는 "인치당 점"을 의미합니다. 컴퓨터 모니터 또는 휴대폰 화면과 같은 출력 디스플레이의 픽셀 밀도에 대한 대략적인 측정값입니다. DPI가 높을수록 더 작은 점이 디스플레이를 구성합니다.
모든 디스플레이 하드웨어를 사용하여 정확한 정보를 보고할 수 있는 것은 아니므로 DPI는 대략적인 측정값일 뿐입니다. 일부 컴퓨터 모니터는 운영 체제에 DPI를 전혀 보고하지 않거나 사용자가 실제 하드웨어와 다른 DPI를 사용하여 렌더링하도록 시스템을 구성했을 수 있습니다(예: UI 텍스트 요소의 크기 변경). 애플리케이션은 DPI를 사용하여 큰 항목을 그릴 방법을 선택할 수 있지만 디스플레이 크기의 정확한 물리적 측정값으로 의존해서는 안 됩니다.
DPI 값 96은 중립 기본값으로 간주됩니다.
픽셀이란?
픽셀은 단일 색의 점입니다. 컴퓨터 그래픽의 이미지는 2차원 그리드로 정렬된 많은 픽셀로 구성됩니다. 픽셀을 모든 이미지가 빌드되는 원자로 생각할 수 있습니다.
픽셀의 물리적 크기는 디스플레이마다 크게 다를 수 있습니다. 컴퓨터가 크지만 낮은 해상도의 모니터 또는 외부 디스플레이에 연결된 경우 픽셀은 매우 클 수 있지만 1080p 디스플레이가 있는 휴대폰에서는 몇 인치만 표시하면 픽셀이 작습니다.
Win2D에서 정수 데이터 형식(또는 정수가 포함된 BitmapSize와 같은 구조체)을 사용하여 위치 또는 크기를 지정하는 API가 표시될 때마다 API가 픽셀 단위로 작동합니다.
대부분의 Win2D API는 픽셀이 아닌 DIP에서 작동합니다.
DIP란?
DIP는 "디바이스 독립적 픽셀"을 의미합니다. 실제 픽셀과 같거나, 크거나, 작을 수 있는 가상화된 단위입니다.
픽셀과 DIP 간의 비율은 DPI에 의해 결정됩니다.
pixels = dips * dpi / 96
DPI가 96이면 픽셀과 DIP가 동일합니다. 더 높은 DPI를 사용하는 경우 단일 DIP는 둘 이상의 픽셀(또는 DPI가 96의 정확한 배수가 아닌 일반적인 경우 픽셀의 일부)에 해당할 수 있습니다.
Win2D를 비롯한 대부분의 Windows 런타임 API는 픽셀이 아닌 DIP를 사용합니다. 이렇게 하면 앱이 실행되는 디스플레이에 관계없이 그래픽을 실제 크기와 거의 동일하게 유지할 수 있다는 장점이 있습니다. 예를 들어 앱에서 단추 너비가 100 DIP임을 지정하는 경우 휴대폰 또는 4k 모니터와 같은 높은 DPI 디바이스에서 실행할 때 이 단추는 자동으로 너비가 100픽셀 이상으로 조정되므로 사용자가 클릭할 수 있는 합리적인 크기로 유지됩니다. 단추 크기를 픽셀 단위로 지정한 경우, 반면에 이러한 종류의 높은 DPI 디스플레이에서 우스꽝스럽게 작게 표시되므로 앱은 각 종류의 화면에 대해 레이아웃을 다르게 조정하기 위해 더 많은 작업을 수행해야 합니다.
Win2D에서 부동 소수점 데이터 형식(또는 부동 소수점 값이 포함된 Vector2 또는 Size와 같은 구조체)을 사용하여 위치 또는 크기를 지정하는 API가 표시될 때마다 API가 DIP에서 작동한다는 의미입니다.
DIP와 픽셀 간에 변환하려면 메서드 ConvertDipsToPixels(Single, CanvasDpiRounding) 를 ConvertPixelsToDips(Int32)사용하고 .
DPI가 있는 Win2D 리소스
비트맵 이미지를 포함하는 모든 Win2D 리소스에도 연결된 DPI 속성이 있습니다.
CanvasBitmapCanvasRenderTargetCanvasSwapChainCanvasControlCanvasVirtualControlCanvasAnimatedControlCanvasImageSource
다른 모든 리소스 종류는 DPI와 독립적입니다. 예를 들어 단일 CanvasDevice 인스턴스를 사용하여 다양한 DPI의 컨트롤 또는 렌더 대상을 그릴 수 있으므로 디바이스에는 자체 DPI가 없습니다.
마찬가지로 비트 CanvasCommandList 맵 이미지가 아닌 벡터 그리기 명령이 포함되어 있으므로 DPI가 없습니다. DPI는 명령 목록이 렌더 타깃 또는 컨트롤(둘 다 DPI를 가짐)에 그려지는 래스터화 과정에서만 적용됩니다.
DPI 제어
Win2D 컨트롤(CanvasControlCanvasVirtualControl및CanvasAnimatedControl)은 앱이 실행 중인 디스플레이와 동일한 DPI를 자동으로 사용합니다. XAML 및 기타 Windows 런타임 API에서 사용하는 좌표계와 일치합니다.
DPI가 변경되면 (예: 앱이 다른 디스플레이로 이동하는 경우) 컨트롤이 CreateResources 이벤트를 발생시키고 DpiChanged 형식의 CanvasCreateResourcesReason를 전달합니다. 앱은 컨트롤의 DPI에 종속된 리소스(예: rendertargets)를 다시 만들어 이 이벤트에 응답해야 합니다.
렌더 타깃 DPI
그릴 수 있는 대상(여기에는 CanvasRenderTarget뿐만 아니라 렌더링 대상과 유사한 형식인 CanvasSwapChain 및 CanvasImageSource도 포함됨)은 자체 DPI를 가지지만, 컨트롤과는 달리 이러한 형식은 디스플레이에 직접 연결되지 않으므로 Win2D는 DPI가 무엇이어야 하는지 자동으로 결정할 수 없습니다. 나중에 화면에 복사될 rendertarget에 그리는 경우 해당 rendertarget이 화면과 동일한 DPI를 사용하려고 하지만 다른 용도로 그리는 경우(예: 웹 사이트에 업로드할 이미지 생성) 기본 96 DPI가 더 적합합니다.
이러한 두 사용 패턴을 쉽게 만들기 위해 Win2D는 두 가지 유형의 생성자 오버로드를 제공합니다.
CanvasRenderTarget(ICanvasResourceCreator, width, height, dpi)
CanvasRenderTarget(ICanvasResourceCreatorWithDpi, width, height)
ICanvasResourceCreator 인터페이스는 CanvasDevice 및 Win2D 컨트롤에서 구현됩니다. 디바이스에는 고유한 특정 DPI가 없으므로 하나의 렌더 대상을 만들 때 DPI를 명시적으로 지정해야 합니다.
예를 들어 DIP 및 픽셀이 항상 동일한 기본 DPI rendertarget을 만들려면 다음을 수행합니다.
const float defaultDpi = 96;
var rtWithFixedDpi = new CanvasRenderTarget(canvasDevice, width, height, defaultDpi);
ICanvasResourceCreatorWithDpi 는 DPI 속성을 추가하여 확장됩니다 ICanvasResourceCreator . 이 인터페이스는 Win2D 컨트롤에 의해 구현되며, 만든 컨트롤과 동일한 DPI를 자동으로 상속하는 rendertarget을 쉽게 만들 수 있습니다.
var rtWithSameDpiAsDisplay = new CanvasRenderTarget(canvasControl, width, height);
비트맵 DPI
CanvasBitmap는 rendertarget과 달리 컨트롤에서 DPI를 자동으로 상속하지 않습니다. 비트맵을 만들고 로드하는 방법에는 DPI를 명시적으로 지정하는 오버로드가 포함되지만, 이를 제외하면 비트맵 DPI는 현재 표시 구성에 관계없이 기본적으로 96으로 설정됩니다.
비트맵이 다른 형식과 다른 이유는 비트맵이 그려지는 출력이 아니라 입력 데이터의 원본이기 때문입니다. 따라서 비트맵의 중요한 점은 해당 출력이 끝나는 DPI가 아니라 현재 디스플레이 설정과 전혀 관련이 없는 원본 이미지의 DPI입니다.
100x100 기본 DPI 비트맵을 로드한 다음 rendertarget에 그리면 비트맵은 대상 렌더 대상의 DPI에서 96DPI(100픽셀)에서 100 DIP(높은 DPI rendertarget인 경우 더 많은 수의 픽셀일 수 있음)로 크기가 조정됩니다. 결과 이미지는 항상 크기가 100 DIP이므로 불쾌한 레이아웃 놀라움은 없지만 낮은 DPI 원본 비트맵이 더 높은 DPI 대상으로 확장된 경우 약간의 흐림이 발생할 수 있습니다.
높은 DPI에서 명확성을 극대화하기 위해 일부 애플리케이션은 다양한 해상도에서 여러 비트맵 이미지 집합을 제공하고 로드 시 대상 컨트롤의 DPI와 가장 일치하는 버전을 선택할 수 있습니다. 다른 앱은 높은 DPI 비트맵만 제공하고, 낮은 DPI 디스플레이에서 실행될 때 Win2D에서 이러한 크기를 축소하도록 할 수 있습니다(스케일 다운은 확장보다 더 잘 보일 수 있음). 두 경우 모두 비트맵 DPI를 매개 변수 LoadAsync(ICanvasResourceCreator, String, Single)로 지정할 수 있습니다.
일부 비트맵 파일 형식에는 자체의 DPI 메타데이터가 포함되어 있지만 Win2D는 잘못 설정된 경우가 많기 때문에 이를 무시합니다. 대신 비트맵을 로드할 때 DPI를 명시적으로 지정해야 합니다.
CanvasDrawingSession DPI
CanvasDrawingSession는 컨트롤, 렌더 타깃, 스왑 체인 등 그려지는 대상이 무엇이든 그로부터 DPI를 상속받습니다.
기본적으로 모든 그리기 작업은 DIP에서 작동합니다. 픽셀 단위로 작업하려는 경우 속성을 통해 Units 변경할 수 있습니다.
효과 해상도(DPI)
이미지 효과 파이프라인은 효과가 그려지는 대상이 무엇이든 그 CanvasDrawingSession 로부터 DPI를 상속합니다. 내부적으로 효과 처리는 항상 픽셀 단위로 작동합니다. 크기 또는 위치와 같은 매개 변수 값은 DIP에 지정되지만 실제 이미지 조작이 수행되기 전에 이러한 단위는 픽셀로 변환됩니다.
대상 그리기 세션과 다른 DPI의 비트맵이 효과 원본 이미지로 사용되는 경우 비트맵과 효과 사이에 내부 DpiCompensationEffect 가 자동으로 삽입됩니다. 이렇게 하면 비트맵 크기가 대상 DPI에 맞게 조정되며, 보통은 이것이 원하는 방식입니다. 원하는 인스턴스가 아닌 경우 고유한 인스턴스 DpiCompensationEffect 를 삽입하여 동작을 사용자 지정할 수 있습니다.
메모
사용자 지정 효과를 구현하는 경우 기본 제공 Win2D 효과와 함께 사용할 때 일관된 동작을 보장하기 위해 해당하는 DPI 처리 체계를 적용하는 것이 좋습니다.
컴포지션 API
API는 Microsoft.Graphics.Canvas.Composition Win2D XAML 컨트롤보다 낮은 수준에서 작동하므로 사용자를 대신하여 DPI를 자동으로 처리하려고 시도하지 않습니다. 원하는 단위를 결정하고 컴퍼지션 시각적 트리의 일부로 이를 달성하는 데 필요한 변환을 설정하는 것은 사용자의 몫입니다.
컴퍼지션 API(예: CreateDrawingSurface 항상 픽셀 단위로 크기 지정). Win2D를 사용하여 컴포지션 표면에 그릴 때 CreateDrawingSession(CompositionDrawingSurface, Rect, Single)를 호출하면서 사용할 DPI 값을 원하는 대로 지정할 수 있습니다. 반환 CanvasDrawingSession 을 통해 수행되는 모든 드로잉은 그에 따라 스케일 업 또는 다운됩니다.
DPI 처리를 테스트하는 방법
앱이 디스플레이 DPI를 올바르게 처리하는지 테스트하는 가장 쉬운 방법은 Windows 10 또는 Windows 11 실행되고 앱이 실행되는 동안 표시 배율을 변경하는 것입니다.
- 바탕 화면 배경을 마우스 오른쪽 단추로 클릭하고 표시 설정을 선택합니다.
- 배율 및 레이아웃에서 배율을 변경합니다(예: 100%에서 150%로).
- 앱이 DPI 변경 알림을 수신하고 자동으로 조정되어야 합니다.
다른 DPI 설정으로 모니터를 연결하고 앱 창을 끌어 테스트할 수도 있습니다.
Windows developer