关于GDI/GDI+坐标兼容性的问题?

18

我在使用 GDI 和 GDI+ 进行绘图时遇到问题。其中页面变换,特别是缩放,在两者之间似乎有些微小差别。除了 SetViewportExtSetWindowExt,GDI 上下文中哪些属性会影响输出的缩放?

代码几乎全部使用 GDI 进行绘制,但在需要半透明效果时会使用 GDI+。代码中使用了 SetViewportExtSetWindowExtSetViewportOrg 来实现缩放和滚动。

当需要使用 GDI+ 时,我会在 HDC 周围构建一个 Gdiplus::Graphics 对象并进行绘制。我认为这使得图形上下文包装设备上下文,并将其渲染到设备上下文中。如果我提取 GDI+ 图形上下文的转换矩阵,我看到它是单位矩阵,因此缩放是在其他地方完成的(我猜测是在设备上下文中)。

我设计了一个简单的测试,在 GDI 和 GDI+ 中绘制相同的矩形数组,以确保在两种情况下所有转换都相同。以下是代码片段:

CRect rect = ...;

// Draw the rectangle using GDI
CPen cpen(PS_DASH, 0, RGB(0,0,255));
pDC->SelectObject(&cpen);
pDC->Rectangle(rect);

{
    // Draw the rectangle using GDI+
    Gdiplus::Graphics graphics(pDC->m_hDC);

    Gdiplus::Pen pen(Gdiplus::Color(180,180,180));
    graphics.DrawRectangle(
        &pen,
        Gdiplus::Rect(rect.left, rect.top, rect.Width(), rect.Height()));
}

结果如下所示:(蓝色虚线由GDI绘制,灰色由GDI+绘制)

代码绘制的结果

我可以清楚地看到两个坐标系是不同的。我期望会有一些舍入误差,但没有像这里看到的比例误差。此外,当我改变缩放因子时,GDI+会在两个方向上跳动±4像素。这也在截图中突出显示,因为与GDI矩形相比,GDI+矩形在X轴上具有正偏移量,在Y轴上具有负偏移量。

  • 有人知道这里发生了什么吗?

  • 我该如何进行调查和调试?这发生在Windows的内部,所以我无法对其进行调试。

供参考,这是我的视口/窗口org/ext的样子:

Window Ext: (134000, 80500)
Window Org: (0, 0)
Viewport Ext: (1452 872)
Viewport Org: (35 35)

更新:

问题已经解决,但并不美观。基本方法如下:

  1. 在屏幕空间中采用两个坐标(原点和适当的第二个点),并使用 GDI(DPtoLP 函数)将它们转换为逻辑坐标。

  2. 将 GDI 转换重置为 MM_TEXT

  3. 使用转换后的点构建一个 GDI+ 的变换矩阵,该矩阵表示相同的变换。

  4. 最后,使用此矩阵构造一个具有正确变换的 GDI+ 上下文。

这有点像 hack,但它确实有效。尽管我仍然不知道为什么两者之间存在差异。但至少可以说明,GDI+ 上下文能够模仿 GDI 转换。

4个回答

7
简短回答:调用 graphics.SetPageUnit(Gdiplus::UnitPixel) 我遇到了与https://dev59.com/x3E95IYBdhLWcg3wb9db#4894969相同的问题:在打印时,GDI+(Gdiplus::Graphics)的坐标与GDI(HDC)的坐标不匹配。 graphics.GetPageUnit() 返回 UnitDisplay。文档中对UnitDisplay的说明是:

指定显示单位。例如,如果显示设备是监视器,则单位为1像素。

我错误地认为对于打印机,UnitDisplay将使用打印机点。经过长时间的挣扎,我终于发现它实际上是使用1/100英寸的,原因未知。如果我使用Gdiplus::UnitPixel,那么GDI+坐标就与GDI坐标相同。

2
我们曾经遇到过相同的问题。
(背景:GDI在几乎所有方面都表现良好,并且对于我们需要呈现的数千个单元格文本的电子表格样式显示来说,似乎要快得多。但是,我们需要使用GDI+来显示.jpg文件。)
当在屏幕上显示内容时,GDI+缩放看起来是正确的。我们有一个打印预览功能,它使用坐标变换让应用程序使用打印机坐标进行渲染,但在屏幕上显示。一切正常,直到我们将其发送到实际打印机(或PDF编写器)时,缩放就会出现问题。
经过一周的工作(并从您的解决方案中获得提示),我们了解到:
GDI+存在一个bug,即当您调用:“Graphics(HDC)”(从GDI设备上下文创建Graphics对象)时,HDC来自打印机或软件打印机,例如6000 x 4000像素分辨率,然后GDI+忽略了HDC正在使用此大分辨率的事实,而是应用了自己的分辨率,约为1000 x 800像素。
因此,您的解决方法可能是这个问题的正确和最佳解决方法。
我们的解决方案类似但有所不同,因为我们实际上不需要任何坐标转换:
        graphics.GetVisibleClipBounds(&rect);
        double deltaY = (double)GetPrinterH()/(double)rect.Height;
        double deltaX = (double)GetPrinterW()/(double)rect.Width;
        x1=x1/deltaX;
        x2=x2/deltaX;
        y1=y1/deltaY;
        y2=y2/deltaY;
        graphics.DrawImage(jpeg->image, x1,y1,x2-x1,y2-y1);

许多打印机驱动程序中的这些缩放因子似乎非常接近于“6”。


0

我找到了一个解决打印问题的方法。请注意,在示例中,图形对象没有设置任何世界空间变换,所以绘图直接在页面空间进行。

将页面单位设置为英寸,然后将坐标转换为英寸似乎可以解决绘图问题,而且不需要太多额外的工作。在不同 DPI(从 72 到 4000)下使用显示器和打印机的 DC 进行了测试。

Gdiplus::Graphics graphics(..);
Gdiplus::RectF    rect(0.0f, 0.0f, 1.0f, 1.0f);
Gdiplus::REAL     dpiX = graphics.getDpiX();
Gdiplus::REAL     dpiY = graphics.getDpiY();

/* Logical coordinates to inches. In this example, the window extents are
   equal to the DC's DPI. You will have to convert to inches based on your
   specific configuration. */
rect.X      /= dpiX;
rect.Y      /= dpiY;
rect.Width  /= dpiX;
rect.Height /= dpiY;

graphics.SetPageUnit(Gdiplus::UnitInch);
graphics.FillRectangle(.., rect);

-1
需要记住的一件事是,大多数GDI通常在硬件上运行(即GDI函数映射到实现硅上某些功能的显示驱动程序)。 GDI+应该获得硬件加速,但它仍然是一个单独的软件渲染器。
尝试通过GDI+和GDI手动设置几个像素,并查看它们是否不同。
也许你特定的图形卡转换坐标的方式与GDI+中发生的方式有所偏差。

我已经收集了很多信息,但我仍然不明白为什么从GDI到GDI+会有这么大的差异。(除非硬件加速是由显卡驱动程序实现的,并且它有一个错误的实现,但我认为这种情况不太可能发生。) - Yngve Hammersland
我想尝试实际的代码,你能发布一个最简单的完整示例,可以编译吗? - rep_movsd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接