12

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

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

  • { 0, 0, 21590, 27940 } 的 rclFrame(元文件的尺寸,以千分之一毫米为单位)
  • { 1440, 900 } 的 szlDevice(参考设备的尺寸,以像素为单位)
  • { 416, 260 } 的 szlMilimeters(参考设备的尺寸,单位为 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 in x 10.98 in(使用 87 dpi)而不是 8.5 in x 11 in(使用 88 dpi)。

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

感谢您的任何见解。

4

4 回答 4

16

我现在学到的关于元文件的知识比我想知道的要多。

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 } 的 a,您可能会期望得到一个Metafile{ rclFrame21590, 27940 } 的 a。毕竟,将英寸转换为毫米并不难。但你可能不会。根据您的分辨率,GDI+ 似乎会在转换英寸参数时使用截断的 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实例录制到a的DPIMetafile总是错误的。

看到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);

这不是胡说八道吗?但它似乎工作。

“舍入”错误 #2 已解决——当我说以 88 dpi 以“1 英寸”绘制某些东西时,该像素最好是 $%$^!记录为像素#88。

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

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

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

显然,Windows 无法查询远程显示器的 EDID。所以它使用古老的默认值 320 x 240 毫米,这很好,只是它恰好是 4:3 的纵横比,现在完全相同的代码正在显示器上生成一个图元文件,据说该显示器没有方形物理像素:水平 DPI 和垂直 DPI 不同,我不记得上次看到这种情况是什么时候了。

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

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

这是引发此问题的问题的主要原因。我的元文件一直都是“正确的”(好吧,在我解决了前两个问题之后是正确的),所有这些创建“高分辨率”元文件的搜索都是一个红鲱鱼。确实,在低分辨率显示设备上记录元文件时会丢失一些保真度;这是因为在元文件中指定的 GDI 命令是以像素为单位指定的。它是矢量格式并可以放大或缩小并不重要,当 GDI+ 决定将操作捕捉到哪个“像素”时,一些信息会在实际录制过程中丢失。

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

舍入错误 #3 已解决。

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

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

结论

由于我的问题是关于在设备上下文中使用 DPI 的问题,Mark's 是一个很好的答案。

于 2009-10-07T17:27:19.757 回答
3

I'm curious as to how Windows knows the physical size of your monitor. You must have changed a configuration somewhere? Perhaps you can change it to more convenient values that divide out nicely.

As implied by the name, a "Device Context" must be connected to a system device. However this does not need to be a hardware driver, it could be a device emulator such as a PDF writer print driver. I've seen at least one that lets you set an arbitrary DPI.

于 2009-10-06T20:57:55.003 回答
0

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

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

其中 preciize 值是在不四舍五入或截断的情况下计算的。

于 2011-11-22T14:36:18.103 回答
0

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

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

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

于 2010-07-21T16:07:52.023 回答