화면 해상도(DPI) 인식
개요
GUI 프로그램을 개발할 때는 다양한 디스플레이 환경을 고려해야 한다. 예를 들어, 동일한 1920 x 1080 해상도에서도 디스플레이 배율에 따라 프로그램의 크기와 배치가 달라질 수 있다. 따라서 디스플레이 해상도와 배율을 적절히 반영하는 것이 중요하다. 이전에 C# WinForm에서는 이를 다뤘지만, 이번에는 파이썬을 사용해 디스플레이 환경을 고려하는 방법을 살펴보도록 하겠다.
본문
ctypes
ctypes는 파이썬에서는 외부 C 라이브러리에 쉽게 접근할 수 있게 해주는 모듈이다. C와 파이썬은 서로 다른 데이터 타입을 사용하지만, ctypes는 파이썬 타입을 C 데이터 타입에 매핑하는 기능을 제공한다. 일반적으로 플랫폼 의존적인 기능에 대한 직접적인 접근과 정확성을 이유로, 운영 체제의 네이티브 API에 접근하기 위해 자주 사용한다. Windows의 경우 user32.dll에서 디스플레이에 대한 정보를 제공하기 때문에 이를 이용해 보도록 하겠다.
import ctypes
def get_screen_resolution():
user32 = ctypes.windll.user32
user32.SetProcessDPIAware()
screen_width = user32.GetSystemMetrics(0)
screen_height = user32.GetSystemMetrics(1)
return screen_width, screen_height
def get_display_ratio():
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
ctypes.windll.shcore.SetProcessDpiAwareness(2)
hdc = user32.GetDC(0)
dpi_x = gdi32.GetDeviceCaps(hdc, 88)
user32.ReleaseDC(0, hdc)
scaling_factor = dpi_x / 96.0
return scaling_factor
width, height = get_screen_resolution()
ratio = get_display_ratio()
print(f"현재 디스플레이 해상도: {width}x{height}\n배율: {ratio}")
get_screen_resolution
ctypes.windll.user32는 Windows의 user32.dll 라이브러리에 접근하는 객체다. 이 객체의 SetProcessDPIAware() 함수를 통해 현재 파이썬 프로세스가 DPI-Aware(DPI 인식) 모드로 설정된다. 이 함수를 호출하면 파이썬 프로그램이 Windows의 DPI 스케일링 설정을 무시하고 실제 해상도(픽셀 단위)를 정확하게 인식하게 된다. 또한, GetSystemMetrics() 함수를 사용하면 다양한 시스템 정보를 가져올 수 있는데, 인수에 따라 반환되는 정보가 달라진다. 예를 들어, 0은 화면의 가로 해상도, 1은 화면의 세로 해상도를 의미한다. 그러나 DPI 배율을 직접적으로 반환하지는 않으며, 해상도를 기반으로 간접적으로 계산할 수 있다.
get_display_ratio
ctypes.windll.gdi32는 그래픽 장치와 관련된 작업을 처리하는 Windows의 gdi32.dll 라이브러리에 접근하는 객체다. GetDC() 함수는 화면의 디바이스 컨텍스트(Device Context) 핸들을 가져오는데, 인수로 0을 전달하면 전체 화면에 대한 핸들을 가져온다. 디바이스 컨텍스트는 그래픽 장치의 속성이나 상태를 가져오고 설정할 수 있도록 해주는 개념이다. 디바이스 컨텍스트를 사용한 후에는 시스템 자원을 해제하기 위해 ReleaseDC()를 호출하여 자원을 반환해야 한다. gdi32 객체의 GetDeviceCaps() 함수를 사용하면 디바이스 컨텍스트에서 가로 방향 DPI 값을 가져올 수 있다. 인수 88은 가로 DPI를 의미하는 상수다. 마지막으로, 현재 DPI 값을 Windows의 기본 DPI인 96으로 나누면 현재 화면의 배율을 계산할 수 있다.
예시
다음과 같이 1500x1000 크기의 GUI가 있다고 가정해 보자. 이 GUI를 1920x1080 해상도를 사용하는 동일한 디스플레이에서 배율을 달리하면 다음과 같은 결과를 볼 수 있다.
만약 배율이 120%로 설정된 경우, 화면 내에서 UI 요소들이 20% 더 크게 표시된다. 이렇게 되면 실제 해상도는 그대로지만, 마치 1600x900 해상도에서 동작하는 것처럼 UI 요소가 커지면서 일부 내용이 화면에서 잘리거나 보이지 않을 수 있다. 이처럼 고해상도 디스플레이에서는 배율 설정에 따른 영향을 고려해야 한다. 이러한 문제를 해결하기 위해, 앞서 구현한 get_display_ratio 함수를 활용할 수 있다. 이 함수는 화면의 DPI 배율을 계산하여, UI의 크기 조정에 활용할 수 있다.
import ctypes
import tkinter as tk
def get_display_ratio():
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
ctypes.windll.shcore.SetProcessDpiAwareness(2)
hdc = user32.GetDC(0)
dpi_x = gdi32.GetDeviceCaps(hdc, 88)
user32.ReleaseDC(0, hdc)
scaling_factor = dpi_x / 96.0
return scaling_factor
ratio = get_display_ratio()
width, height = int(1500/ratio), int(1000/ratio)
root = tk.Tk()
root.geometry(f"{width}x{height}")
[tk.Label(root, text=f"{i}번 째 라벨", font=("Helvetica", 50)).pack() for i in range(5)]
root.mainloop()
위 코드에서는 get_display_ratio() 함수를 통해 현재 화면의 배율을 계산하고, 1500x1000 크기의 GUI를 배율에 맞춰 조정한 크기로 설정한다. 배율을 고려함으로써 UI가 너무 작거나 잘리는 문제를 방지할 수 있다.
필자의 경우, 위치와 크기 관련 코드를 작성할 때는 항상 배율을 고려하여 변수로 처리하고, 이를 DPI 배율로 나눠 크기를 조정하는 방식을 사용하고 있다. 이를 통해 다양한 배율 환경에서도 일관된 UI를 유지할 수 있다.
요약
반응형 GUI
1. ctypes 라이브러리를 통해 user32.dll, gdi32.dll 등의 OS Native API 호출
2. DPI 값을 이용해 크기를 조정
'Language > Python' 카테고리의 다른 글
[파이썬] 에러 로깅 클래스 (0) | 2024.09.28 |
---|---|
[파이썬] GUI(tkinter) 멀티스레딩 with CAN Message (1) | 2024.09.27 |
[파이썬] tkinter (2) - PDF 페이지 추출 프로그램 (0) | 2024.06.29 |
[파이썬] tkinter (1) (0) | 2024.06.24 |
[Python] PDF 텍스트 추출 (0) | 2023.10.31 |