C++ – About GDI/GDI+ coordinate compatibility

cgdi+windows

I have a problem while drawing with both GDI and GDI+ interchangeably. The page transformation—in particular scaling—seems to be a little bit off between the two. Which properties of the GDI context affects the scaling of the output other than SetViewportExt and SetWindowExt?

The code uses almost exclusively GDI for its drawing, but uses GDI+ in a few cases where its features (semitransparency) are needed. It uses SetViewportExt, SetWindowExt and SetViewportOrg to enable zooming and scrolling.

When GDI+ is needed I construct a Gdiplus::Graphics object around the HDC and do the drawing. I assume this makes the graphics context wrap the device context and relay its rendering to the device context. If I extract the transformation matrix of the GDI+ graphics context, I see that it is the identity matrix, so the scaling is done elsewhere (in the device context I guess).

I devised a simple test where I draw the same array of rectangles with GDI and GDI+ to be sure that all the transformations are the same in both cases. The code snippet follows:

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()));
}

And the result is here: (the blue dashed is drawn by GDI and the gray is drawn by GDI+)

Result drawn by code

I can clearly see that the two coordinate systems are different. I expected some round-off errors, but not a scaling error as seen here. Also, when I change the zoom factor, the GDI+ jumps around ±4 pixel in both directions depending on the zoom. This is also highlighted in the screenshot as the GDI+ rectangle has a positive offset on the X axis and a negative offset on the Y axis as compared to the GDI rectangle.

  • Does anybody know what's going on here?

  • How would I go about investigating/debugging this? This happens in the bowels of windows so I'm unfortunately unable to debug it.

For reference, this is what my viewport/window org/ext looks like:

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

Update:

I have fixed the problem, but it's not pretty. The basic approach is:

  1. Take two coordinates (origin and a second appropriate point) in screen space and transform them to logical coordinates using GDI (DPtoLP function).

  2. Reset the GDI transformation to MM_TEXT.

  3. Use the transformed points to construct a transformation matrix for GDI+ which represent the same transformation

  4. And finally use this matrix to construct a GDI+ context with the correct transformation.

This is a bit of a hack, but it works. I still don't know why there is a difference between the two, though. At least it goes to show that it is possible to have a GDI+ context mimic the GDI transformation.

Best Answer

Short answer: call graphics.SetPageUnit(Gdiplus::UnitPixel)

I had the same problem as https://stackoverflow.com/a/4894969/700027: when printing, the coordinates for GDI+ (Gdiplus::Graphics) did not match the coordinates from GDI (HDC).

graphics.GetPageUnit() was returning UnitDisplay. The documentation of UnitDisplay is:

Specifies display units. For example, if the display device is a monitor, then the unit is 1 pixel.

I had wrongly assumed that for a printer, UnitDisplay would use printer dots. After much struggle I finally found out that it was actually using 1/100 of inches for an unknown reason. If I use Gdiplus::UnitPixel then GDI+ coordinates are the same as GDI coordinates.

Related Topic