DPI и независимые от устройства пиксели
Чтобы эффективно программировать с помощью графики Windows, необходимо понимать два связанных понятия:
- Точек на дюйм (DPI)
- Независимые от устройства пиксели (DIP).
Начнем с DPI. Для этого потребуется короткий объезд в типографику. В типографии размер типа измеряется в единицах, называемых точками. Одно очко равно 1/72 дюйма.
- 1 пт = 1/72 дюйма
Примечание
Это определение точки для публикации на рабочем столе. Исторически точная мера точки менялась.
Например, шрифт из 12 точек предназначен для размещения в строке текста 1/6" (12/72). Очевидно, что это не означает, что каждый символ в шрифте точно 1/6" в высоту. На самом деле, некоторые символы могут быть выше 1/6". Например, во многих шрифтах символ Å выше номинальной высоты шрифта. Для правильного отображения шрифта требуется дополнительное пространство между текстом. Это пространство называется ведущим.
На следующем рисунке показан шрифт из 72 точек. Сплошные линии показывают ограничивающий прямоугольник высотой 1" вокруг текста. Пунктирная линия называется базовой линией. Большинство символов в шрифте покоится на базовом уровне. Высота шрифта включает часть выше базового плана ( подъем) и часть ниже базового плана ( спуск). На шрифте, показанном здесь, восхождение равно 56 пунктам, а спуск — 16 пунктов.
Однако, когда дело доходит до экрана компьютера, измерение размера текста является проблематичным, поскольку пиксели не все одинаковый размер. Размер пикселя зависит от двух факторов: разрешения дисплея и физического размера монитора. Таким образом, физические дюймы не являются полезной мерой, так как между физическими дюймами и пикселями нет фиксированной связи. Вместо этого шрифты измеряются в логических единицах. Шрифт размером 72 точки определяется как логическая высота дюйма. Затем логические дюймы преобразуются в пиксели. В течение многих лет Windows использовала следующее преобразование: один логический дюйм равен 96 пикселям. При использовании этого коэффициента масштабирования шрифт из 72 точек отображается как 96 пикселей в высоту. 12-точечный шрифт имеет высоту 16 пикселей.
- 12 точек = 12/72 логического дюйма = 1/6 логического дюйма = 96/6 пикселей = 16 пикселей
Этот коэффициент масштабирования описывается как 96 точек на дюйм (DPI). Термин точки происходит от печати, где физические точки рукописного ввода помещаются на бумагу. Для компьютерных дисплеев было бы точнее сказать 96 пикселей на логический дюйм, но термин DPI завис.
Поскольку фактические размеры пикселей различаются, текст, доступный для чтения на одном мониторе, может быть слишком мал на другом мониторе. Кроме того, у пользователей разные предпочтения— некоторые предпочитают текст большего размера. По этой причине Windows позволяет пользователю изменять параметр DPI. Например, если пользователь устанавливает для дисплея значение 144 точек на дюйм, то 72-точечное значение будет высотой 144 пикселя. Стандартные параметры DPI: 100 % (96 DPI), 125 % (120 DPI) и 150 % (144 DPI). Пользователь также может применить настраиваемый параметр. Начиная с Windows 7 параметр DPI является параметром для каждого пользователя.
Масштабирование DWM
Если в программе не учитывается значение DPI, в параметрах высокого разрешения могут быть очевидны следующие дефекты:
- Обрезанные элементы пользовательского интерфейса.
- Неправильный макет.
- Пиксельные растровые изображения и значки.
- Неправильные координаты мыши, которые могут повлиять на проверку нажатия, перетаскивание и т. д.
Чтобы обеспечить работу старых программ с высоким разрешением, DWM реализует полезный резервный вариант. Если программа не помечена как соответствующая DPI, DWM масштабируется весь пользовательский интерфейс в соответствии с параметром DPI. Например, при 144 DPI пользовательский интерфейс масштабируется на 150 %, включая текст, графику, элементы управления и размеры окон. Если программа создает окно 500 × 500, окно фактически отображается как 750 × 750 пикселей, а содержимое окна масштабируется соответствующим образом.
Это означает, что старые программы "просто работают" с параметрами с высоким разрешением. Однако масштабирование также приводит к несколько размытости, так как масштабирование применяется после рисования окна.
Приложения с поддержкой DPI
Чтобы избежать масштабирования DWM, программа может пометить себя как DPI. Это указывает DWM не выполнять автоматическое масштабирование DPI. Все новые приложения должны быть разработаны с учетом DPI, так как осведомленность о DPI улучшает внешний вид пользовательского интерфейса при более высоких параметрах DPI.
Программа объявляет себя с поддержкой DPI через манифест приложения. Манифест — это просто XML-файл, описывающий библиотеку DLL или приложение. Манифест обычно внедряется в исполняемый файл, хотя его можно предоставить как отдельный файл. Манифест содержит такие сведения, как зависимости библиотеки 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.
- В меню Проект выберите пункт Свойство.
- На левой панели разверните узел Свойства конфигурации, разверните узел Инструмент манифеста, а затем щелкните Входные и выходные данные.
- В текстовом поле Дополнительные файлы манифеста введите имя файла манифеста и нажмите кнопку ОК.
Помечая программу как поддержку DPI, вы сообщаете DWM не масштабировать окно приложения. Теперь, если вы создаете окно 500 × 500, оно будет занимать 500 × 500 пикселей, независимо от параметра DPI пользователя.
GDI и DPI
Рисунок GDI измеряется в пикселях. Это означает, что если программа помечена как поддерживающая DPI и вы просите GDI нарисовать прямоугольник 200 × 100, результирующий прямоугольник будет иметь ширину 200 пикселей и высоту 100 пикселей на экране. Однако размеры шрифтов GDI масштабируются до текущего параметра DPI. Другими словами, при создании шрифта из 72 точек размер шрифта будет составлять 96 пикселей при 96 точек на дюйм, но 144 пикселя при 144 точек на дюйм. Ниже приведен шрифт из 72 точек, отображаемый с разрешением 144 точек на дюйм с помощью GDI.
Если приложение учитывает DPI и вы используете GDI для рисования, масштабируйте все координаты документа в соответствии с DPI.
Direct2D и DPI
Direct2D автоматически выполняет масштабирование в соответствии с параметром DPI. В Direct2D координаты измеряются в единицах, называемых аппаратно-независимыми пикселями (DIP). DIP определяется как 1/96 логического дюйма. В Direct2D все операции рисования указываются в DIP, а затем масштабируются до текущего параметра DPI.
Масштаб | Размер DIP |
---|---|
96 | 1 пиксель |
120 | 1,25 пикселя |
144 | 1,5 пикселя |
Например, если параметр DPI пользователя равен 144 DPI и вы просите 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 определяется как USER_DEFAULT_SCREEN_DPI
, для которого задано значение 96. Чтобы определить коэффициент масштабирования, возьмите значение 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 PixelsToDips(T y)
{
return static_cast<float>(y) / g_DPIScale;
}
Ниже приведен альтернативный способ получения параметра DPI, если вы не используете 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);
}
Примечание
Для классического приложения рекомендуется использовать GetDpiForWindow; а для приложения универсальная платформа Windows (UWP) используйте DisplayInformation::LogicalDpi. Несмотря на то, что мы не рекомендуем это сделать, вы можете программно задать сведения о DPI по умолчанию с помощью SetProcessDpiAwarenessContext. После создания окна (HWND) в процессе изменение режима осведомленности о DPI больше не поддерживается. Если вы настраиваете режим отслеживания DPI по умолчанию для процесса программным способом, необходимо вызвать соответствующий API перед созданием каких-либо HWND. Дополнительные сведения см. в разделе Настройка сведений о 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, а не в пикселях.