指定GDI设备上下文的DPI

Nic*_*cki 12 gdi rounding-error metafile

我有一个生成元文件(EMF)的应用程序.它使用参考设备(也就是屏幕)来渲染这些元文件,因此元文件的DPI会根据运行代码的机器而改变.

假设我的代码打算创建一个8.5英寸x 11英寸的图元文件.使用我的开发工作站作为参考,我最终获得了一个EMF

  • 一个{0,0,21590,27940}的rclFrame(图元文件的维度,以千分之一毫米为单位)
  • {1440,900}的szlDevice(参考设备的尺寸,以像素为单位)
  • a {416,260}的szlMillimeters(参考设备的尺寸,单位mm)

好的,所以rclFrame告诉我EMF的大小应该是

  • 21590/2540 =宽8.5
  • 27940/2540 = 11英寸高

对吧.使用这些信息,我们也可以确定我的显示器的物理DPI,如果我的数学是正确的:

  • (1440*25.4)/ 416 = 87.9231水平dpi
  • (900*25.4)/ 260 = 87.9231垂直dpi

问题

凡是播放此图元文件 - 一个EMF到PDF转换,在"摘要"页上的Windows资源管理器等的EMF时,单击鼠标右键 - 似乎截断计算的DPI值,显示87的,而不是87.9231(甚至88会很好).

当播放元文件时,这会导致页面的物理大小为8.48 x 10.98 in(使用87 dpi)而不是8.5 in x 11 in(使用88 dpi).

  • 是否可以更改参考设备的DPI,以便存储在用于计算DPI的图元文件中的信息出现一个很好的整数?
  • 我可以创建自己的设备上下文并指定其DPI吗?或者我真的必须使用打印机来做到这一点?

感谢您的任何见解.

Nic*_*cki 16

我现在学到的东西比我关心的metafiles还要多.

1.某些Metafile类的构造函数重载效果不佳,并且会在截断的DPI值上运行.

考虑以下:

protected Graphics GetNextPage(SizeF pageSize)
{
    IntPtr deviceContextHandle;
    Graphics offScreenBufferGraphics;
    Graphics metafileGraphics;
    MetafileHeader metafileHeader;

    this.currentStream = new MemoryStream();
    using (offScreenBufferGraphics = Graphics.FromHwnd(IntPtr.Zero))
    {
        deviceContextHandle = offScreenBufferGraphics.GetHdc();
        this.currentMetafile = new Metafile(
            this.currentStream,
            deviceContextHandle,
            new RectangleF(0, 0, pageSize.Width, pageSize.Height),
            MetafileFrameUnit.Inch,
            EmfType.EmfOnly);

        metafileGraphics = Graphics.FromImage(this.currentMetafile);

        offScreenBufferGraphics.ReleaseHdc();
    }

    return metafileGraphics;
}
Run Code Online (Sandbox Code Playgroud)

如果您在通过SizeF的{8.5,11},你可能希望得到一个Metafile有一个rclFrame的{21590,27940}.毕竟,将英寸转换为毫米并不难.但你可能不会.根据您的分辨率,GDI +似乎会在转换inches参数时使用截断的DPI值.为了做到这一点,我必须自己完成百分之一毫米,这是GDI +刚刚通过的,因为它是如何原生存储在元文件头中的:

this.currentMetafile = new Metafile(
    this.currentStream,
    deviceContextHandle,
    new RectangleF(0, 0, pageSize.Width * 2540, pageSize.Height * 2540),
    MetafileFrameUnit.GdiCompatible,
    EmfType.EmfOnly);
Run Code Online (Sandbox Code Playgroud)

舍入错误#1已解决 - rclFrame我的元文件现在是正确的.

2. Graphics记录到a 的实例上的DPI Metafile总是错误的.

metafileGraphics通过调用Graphics.FromImage()元文件来查看我设置的变量?好吧,似乎该Graphics实例的DPI总是为96 dpi.(如果我不得不猜测,它总是设置为逻辑 DPI,而不是物理 DPI .)

您可以想象,当您Graphics在96 dpi下运行的实例上绘制并记录到Metafile其标题中"已记录"87.9231 dpi 的实例时,会出现欢闹.(我说"记录"因为它是根据其他值计算的.)元文件的"像素"(记住,存储在图元文件中的GDI命令以像素为单位指定)更大,所以你诅咒并嘀咕为什么你要画一些东西一英寸长,最终只有一英寸长.

解决方案是缩小Graphics实例:


metafileGraphics = Graphics.FromImage(this.currentMetafile);
metafileHeader = this.currentMetafile.GetMetafileHeader();
metafileGraphics.ScaleTransform(
    metafileHeader.DpiX / metafileGraphics.DpiX,
    metafileHeader.DpiY / metafileGraphics.DpiY);
Run Code Online (Sandbox Code Playgroud)

不是那个叫声吗?但似乎有效.

"舍入"错误#2解决了 - 当我说在88 dpi时画出"1英寸"的东西时,那个像素最好是$%$ ^!记录为像素#88.

3. szlMillimeters可以变化很大; 远程桌面带来很多乐趣.

因此,我们发现(根据Mark的回答),有时,Windows会查询显示器的EDID,并且实际上知道它的物理尺寸.GDI + HORZSIZE在填写szlMillimeters属性时有用地使用它(等).

现在想象一下,你回家调试这个远程桌面代码.假设您的家用电脑配备了16:9宽屏显示器.

显然,Windows无法查询远程显示器的EDID.因此它使用了320 x 240 mm的古老默认值,这很好,除了它恰好是4:3的宽高比,现在完全相同的代码在显示器上生成一个元文件,该文件应该是非方形物理像素:水平DPI和垂直DPI是不同的,我不记得上次我看到的那种情况发生了.

我现在的解决方法是:"好吧,不要在远程桌面下运行它."

4.我在使用的EMF-to-PDF工具在查看rclFrame标题时出现了舍入错误.

这是导致这个问题的问题的主要原因.我的图元文件一直是"正确的"(好吧,在修复前两个问题之后正确),并且所有这些创建"高分辨率"元文件的搜索都是一个红色的鲱鱼.确实,在低分辨率显示设备上记录图元文件时会丢失一些保真度.这是因为元文件中指定的GDI命令以像素为单位指定.它是矢量格式并且可以向上或向下缩放并不重要,当GDI +决定将操作捕捉到哪个"像素"时,某些信息在实际记录期间丢失.

我联系了供应商,他们给了我一个更正的版本.

舍入错误#3已解决.

5. Windows资源管理器中的"摘要"窗格恰好在显示计算的DPI时截断值.

恰好这个截断的值表示EMF-to-PDF工具在内部使用的相同错误值.除此之外,这个怪癖对讨论没有任何意义.

结论

由于我的问题是关于在设备环境中使用DPI进行预测,Mark's是一个很好的答案.


Mar*_*som 3

我很好奇 Windows 如何知道显示器的物理尺寸。您一定在某处更改了配置?也许您可以将其更改为更方便的值,可以很好地划分。

正如名称所暗示的,“设备上下文”必须连接到系统设备。但是,这不一定是硬件驱动程序,它可以是设备模拟器,例如 PDF 编写器打印驱动程序。我见过至少有一个可以让您设置任意 DPI。