指定GDI设备上下文的DPI

时间:2009-10-06 20:16:33

标签: gdi rounding-error metafile

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

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

的EMF
  • {0,0,21590,27940}的rclFrame(图元文件的尺寸,以千分之一毫米为单位)
  • {1440,900}的szlDevice(参考设备的尺寸,以像素为单位)
  • {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 vertical 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吗?或者我真的必须使用打印机才能做到这一点吗?

感谢您的任何见解。

4 个答案:

答案 0 :(得分: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;
}

如果您传递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);

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

2。记录到Graphics的{​​{1}}实例上的DPI始终是错误的。

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

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

解决方案是缩小Metafile实例:

Graphics

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

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

3。 metafileGraphics = Graphics.FromImage(this.currentMetafile); metafileHeader = this.currentMetafile.GetMetafileHeader(); metafileGraphics.ScaleTransform( metafileHeader.DpiX / metafileGraphics.DpiX, metafileHeader.DpiY / metafileGraphics.DpiY); 可能变化很大;远程桌面会带来很多乐趣。

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

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

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

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

4。在查看szlMillimeters标题时,我使用的EMF到PDF工具有一个舍入错误。

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

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

舍入错误#3已解决。

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

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

结论

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

答案 1 :(得分:3)

我很好奇Windows如何知道显示器的物理尺寸。您必须在某处更改配置?也许你可以将它改成更方便的值,这些值可以很好地分开。

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

答案 2 :(得分:0)

请注意,我一直在WXP上以120 dpi运行(大字体),这意味着metafileGraphics.DpiX将返回120.

EMF文件似乎没有记录参考上下文的dpi(本例中为120,其他大多数为96)。

为了使事情变得更有趣,可以通过绘制到已将SetResolution()设置为300dpi的内存位图来创建EMF。在这种情况下,我认为缩放因子必须是300,而不是监视器(86.x)或Windows(120)可能使用的。

答案 3 :(得分:0)

似乎摘要页面上的值是错误的。它们计算如下:

Size = round(precize_size)+1
Resolution = trunc(precize_resolution)

在没有舍入或截断的情况下计算精简值。