WPF中如何确定RenderTargetBitmap的DPI值以正确渲染位图?
问题
我正尝试在WPF应用中将800×800的位图绘制到Image控件中。具体操作是创建DrawingVisual,通过RenderOpen()生成DrawingContext,在其中绘制红蓝色交替的98×98矩形,接着创建指定尺寸和手动输入DPI的RenderTargetBitmap,调用Render()后将其设为Image控件的源。
问题在于,若不明确正确的DPI值,位图无法渲染到正确尺寸,目前发现实际DPI约为91。当前代码中我将DPI硬编码为96:
DrawingVisual D = new DrawingVisual(); DrawingContext DC = D.RenderOpen(); DC.DrawRectangle(Brushes.White, null, new Rect(0, 0, 800, 800)); int R, C; for (R = 0; R < 8; R++) { for (C = 0; C < 8; C++) { Brush TheColor = Brushes.Red; if ((R + C) % 2 == 0) { TheColor = Brushes.Blue; } DC.DrawRectangle(TheColor, null, new Rect(C * 100 + 1, R * 100 + 1, 98, 98)); } } DC.Close(); double DPI_X = 96.0; double DPI_Y = 96.0; RenderTargetBitmap B = new RenderTargetBitmap(800, 800, DPI_X, DPI_Y, PixelFormats.Pbgra32); B.Render(D); theImage.Source = B;
Image控件的XAML代码如下:
<Image x:Name="theImage" HorizontalAlignment="Left" Height="800" Width="800" Margin="0,0,0,0" VerticalAlignment="Top" />
主要疑问:
- 我的实现方式是否正确?当前位图显示正常但尺寸不符。
- 若此方式可行,如何获取屏幕DPI?网上找到的方法要么返回null要么返回96。
解决方案
一、实现方式的正确性
核心实现思路是对的,尺寸不符的根源在于WPF的设备无关单位(1/96英寸)与实际像素的映射依赖真实屏幕DPI,硬编码96DPI会在非标准DPI屏幕上出现缩放偏差。
需要注意:
- DrawingVisual中的绘制坐标是设备无关单位,而RenderTargetBitmap的尺寸参数是实际像素数。如果目标是让绘制内容刚好填满800x800像素的Image控件,必须保证RenderTargetBitmap的DPI与屏幕实际DPI匹配。
- 你当前绘制的800x800设备无关单位,在96DPI下对应800x800像素,但如果屏幕实际DPI是91,800设备无关单位对应的像素数为
800 * (91/96) ≈ 758,这就导致显示尺寸不符。
二、获取正确的屏幕DPI
以下是WPF中可靠的屏幕DPI获取方式,避开旧方法的96DPI偏差问题:
方法1:通过Visual的DpiProperty获取(推荐,.NET 4.6.2+支持)
直接读取Image控件所在屏幕的真实DPI:
var dpi = VisualTreeHelper.GetDpi(theImage); double dpiX = dpi.DpiScaleX * 96; double dpiY = dpi.DpiScaleY * 96;
方法2:通过Window的CompositionTarget获取
如果Image在Window中,可从窗口的设备转换矩阵计算DPI:
var dpiScale = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice; double dpiX = 96 * dpiScale.M11; double dpiY = 96 * dpiScale.M22;
方法3:启用高DPI感知(必要配置)
在项目的app.manifest中添加以下配置,避免系统自动缩放导致的DPI偏差:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness> </windowsSettings> </application>
三、修正后的代码示例
替换硬编码DPI为真实屏幕DPI,同时用using自动释放DrawingContext:
DrawingVisual D = new DrawingVisual(); using (DrawingContext DC = D.RenderOpen()) { DC.DrawRectangle(Brushes.White, null, new Rect(0, 0, 800, 800)); for (int R = 0; R < 8; R++) { for (int C = 0; C < 8; C++) { Brush TheColor = (R + C) % 2 == 0 ? Brushes.Blue : Brushes.Red; DC.DrawRectangle(TheColor, null, new Rect(C * 100 + 1, R * 100 + 1, 98, 98)); } } } // 获取真实DPI var dpi = VisualTreeHelper.GetDpi(theImage); double DPI_X = dpi.DpiScaleX * 96; double DPI_Y = dpi.DpiScaleY * 96; RenderTargetBitmap B = new RenderTargetBitmap(800, 800, DPI_X, DPI_Y, PixelFormats.Pbgra32); B.Render(D); theImage.Source = B;
内容的提问来源于stack exchange,提问作者Don Del Grande
相关产品推荐
相关产品推荐

