您的位置:首页 > 其它

Windows桌面应用程序(1-2-4-7th) DPI和设备无关的像素

2018-01-12 21:53 211 查看
要使用Windows图形进行有效编程,您必须了解两个相关的概念:

每英寸点数(DPI)

设备无关像素(DIP)。

我们从DPI开始。这将需要短暂的绕行排版。在印刷术中,类型的大小以称为点的单位来测量。一点等于1/72英寸。

1点=1/72英寸1点=1/72英寸

注意 这是桌面出版的定义。历史上,一个点的具体措施已经变化。

例如,12点字体被设计成适合1/6”(12/72)行的文本。显然,这并不意味着字体中的每个字符恰好是1/6”高。实际上,某些字符可能会比1/6”更高,例如,在许多字体中,字符Å比字体标称高度要高,为了正确显示,字体之间需要一些额外的空间,这个空间是称为领先。

下图显示了一个72磅字体。实线在文本周围显示1”高的边界框,虚线称为基线,字体中的大部分字符位于基线上,字体的高度包括基线上方的部分(上升)和基线以下的部分(下降)。在这里显示的字体中,上升56点,下降16点。



显示72点字体的插图。

但是,当涉及到计算机显示器时,测量文字大小是有问题的,因为像素的大小不尽相同。像素的大小取决于两个因素:显示器分辨率和显示器的物理尺寸。因此,物理英寸不是一个有用的度量,因为物理英寸和像素之间没有固定的关系。相反,字体是以逻辑单位来衡量的。72点字体被定义为一个逻辑英寸高。逻辑英寸然后被转换为像素。多年来,Windows使用以下转换:一个逻辑英寸等于96像素。使用这个缩放因子,72点字体被渲染成96像素高。12点字体是16像素高。

12点=12/72逻辑英寸=1/6逻辑英寸=96/6像素=16像素12点=12/72逻辑英寸=1/6逻辑英寸=96/6像素=16像素

这个比例因子被描述为每英寸96点(DPI)。术语圆点来源于印刷,其中油墨的物理点放在纸上。对于计算机显示器来说,每逻辑英寸说96像素更准确,但术语DPI已经停滞。

由于实际像素大小不同,在一台监视器上可读的文本在其他监视器上可能太小。另外,人们有不同的喜好——有些人喜欢较大的文字。因此,Windows使用户可以更改DPI设置。例如,如果用户将显示设置为144 DPI,则72点字体的高度为144像素。标准DPI设置为100%(96 DPI),125%(120 DPI)和150%(144 DPI)。用户也可以应用自定义设置。从Windows 7开始,DPI是按用户设置的。

DWM缩放

如果一个程序没有考虑DPI,在高DPI设置下可能会出现以下缺陷:

剪裁的UI元素。

不正确的布局。

像素化的位图和图标。

不正确的鼠标坐标,这可能影响命中测试,拖放等等。

为了确保较旧的程序在高DPI设置下工作,DWM实现了有用的回退。如果某个程序未标记为DPI,则DWM将缩放整个UI以匹配DPI设置。例如,在144 DPI时,UI缩放了150%,包括文本,图形,控件和窗口大小。如果程序创建一个500×500窗口,窗口实际上显示为750×750像素,窗口内容将相应地缩放。

这种行为意味着较旧的程序在高DPI设置下“正常工作”。但是,缩放也会导致模糊的外观,因为缩放是在窗口绘制之后应用的。

DPI感知应用程序

为了避免DWM缩放,程序可以将自己标记为DPI感知。这告诉DWM不要执行任何自动DPI缩放。所有新的应用程序都应设计为DPI感知型,因为DPI感知能够在更高的DPI设置下改善UI的外观。

一个程序通过其应用程序清单声明自己的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中执行以下步骤。

项目菜单上,单击属性

在左侧窗格中,展开配置属性,展开清单工具,然后单击输入和输出

其他清单文件文本框中,键入清单文件的名称,然后单击确定

通过将您的程序标记为支持DPI,您告诉DWM不要缩放您的应用程序窗口。现在,如果您创建500×500的窗口,无论用户的DPI设置如何,窗口都将占用500×500像素。

GDI和DPI

GDI绘图以像素为单位进行测量。这意味着如果您的程序被标记为支持DPI,并且您要求GDI绘制一个200×100的矩形,则得到的矩形将在屏幕上宽200像素,高100像素。但是,GDI字体大小被缩放到当前的DPI设置。换句话说,如果创建72磅字体,则96 DPI时的字体大小为96像素,144 DPI时为144像素。这是使用GDI以144 DPI呈现的72点字体。



在GDI中显示DPI字体缩放的图表。

如果您的应用程序支持DPI并使用GDI进行绘制,则缩放所有绘图坐标以匹配DPI。

Direct2D和DPI

Direct2D自动执行缩放以匹配DPI设置。在Direct2D中,坐标是以独立于设备的像素(DIP)为单位测量的。DIP被定义为逻辑英寸的1/96。在Direct2D中,所有绘图操作都在DIP中指定,然后按比例缩放到当前的DPI设置。

DPI设置DIP尺寸
961 像素
1201.25 像素
1441.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

从物理像素到DIP的转换使用以下公式。

DIPs=像素/(DPI/96.0)DIPs=像素/(DPI/96.0)

要获得DPI设置,请调用ID2D1Factory::GetDesktopDpi方法。DPI返回为两个浮点值,一个用于x轴,另一个用于y轴。从理论上讲,这些价值可能不同。为每个轴计算一个单独的比例因子。

float g_DPIScaleX=1.0f;
float g_DPIScaleY=1.0f;
void InitializeDPIScale(ID2D1Factory *pFactory){
FLOAT dpiX,dpiY;
pFactory->GetDesktopDpi(&dpiX,&dpiY);
g_DPIScaleX=dpiX/96.0f;
g_DPIScaleY=dpiY/96.0f;
}
template<typename T>
float PixelsToDipsX(T x){
return static_cast<float>(x)/g_DPIScaleX;
}
template<typename T>
float PixelsToDipsY(T y){
return static_cast<float>(y)/g_DPIScaleY;
}


如果您不使用Direct2D,则可以使用另一种方法获取DPI设置:

void InitializeDPIScale(HWND hwnd){
HDC hdc=GetDC(hwnd);
g_DPIScaleX=GetDeviceCaps(hdc,LOGPIXELSX)/96.0f;
g_DPIScaleY=GetDeviceCaps(hdc,LOGPIXELSY)/96.0f;
ReleaseDC(hwnd,hdc);
}


调整渲染目标的大小

如果窗口大小发生变化,则必须调整渲染目标的大小以匹配。在大多数情况下,您还需要更新布局并重新绘制窗口。以下代码显示了这些步骤。

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中使用颜色

原文链接:DPI and Device-Independent Pixels
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐