关于GDI / GDI +坐标兼容性?

时间:2010-02-22 08:51:59

标签: c++ windows gdi+ gdi

我在使用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 +绘制)

Result drawn by code

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

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

  • 我将如何调查/调试此事?这发生在窗户的内部,所以我很遗憾无法调试它。

作为参考,这是我的视口/窗口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 +上下文。

  5. 这有点像黑客,但它确实有效。我仍然不知道为什么两者之间存在差异。至少它表明,可能让GDI +上下文模仿GDI转换。

4 个答案:

答案 0 :(得分:5)

简短回答:致电graphics.SetPageUnit(Gdiplus::UnitPixel)

我遇到与https://stackoverflow.com/a/4894969/700027相同的问题:打印时,GDI +(Gdiplus :: Graphics)的坐标与GDI(HDC)的坐标不匹配。

graphics.GetPageUnit()正在返回UnitDisplayUnitDisplay的文档是:

  

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

我错误地认为对于打印机,UnitDisplay会使用打印机点。经过多次努力,我终于发现它实际上使用了1/100英寸的原因。如果我使用Gdiplus :: UnitPixel,则GDI +坐标与GDI坐标相同。

答案 1 :(得分:2)

我们遇到了同样的问题。

(背景:GDI适用于几乎所有东西,对于我们的电子表格样式显示来说似乎要快得多,需要渲染1000个文本单元格。但是我们需要GDI +来显示.jpg。)

在屏幕上显示内容时,GDI +缩放似乎是正确的。我们有一个打印预览功能,它使用坐标转换让应用程序使用打印机坐标渲染,但显示在屏幕上。一切正常,直到我们将它发送到实际的打印机(或PDF编写器),当缩放被填满时。

经过一个人工作周的工作(并用你的解决方案得到你的暗示),这是我们的理解:

GDI +中存在一个错误,当您调用:“图形(HDC)”(从GDI设备上下文创建图形对象)时,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'。

答案 2 :(得分: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);

答案 3 :(得分:-1)

要记住的一件事是大多数GDI通常在硬件上运行(即GDI函数映射到在硅上实现某些功能的显示驱动程序) GDI +应该得到硬件加速,但它仍然是一个独立的软件渲染器。

尝试通过GDI +和GDI手动设置几个像素,看看它们是否不同。

也许您的特定图形卡转换坐标的方式与GDI +中发生的方式不同