Yngve Hammersland
Yngve Hammersland

Reputation: 1674

Why does ArcTo sometimes not update the current position

Background

I'm working a legacy MFC application which uses GDI draw its content.

I need to draw rounded rectangles where each corner has a (potentially) different radius. This means that I can no longer use RoundRect and have to roll my own using ArcTo. I'm using SetWindowExtEx, SetWindowOrgEx, SetViewportExtEx and SetViewportOrgExt to implement zooming.

This works fine in most situations.

Problem

On certain zoom levels, my code fails to construct a proper path of the outline of the roundrect.

The following screenshots is of my RoundRect code used to create a path, then used to clip a bigger rectangle (to get an idea of it's shape). The clipping region created by this path is sometimes missing a corner, clips everything (two missing corners?) or clips nothing.

My guess is that due to rounding errors, the arcs are too small, and is skipped alltogether by GDI. I find this hard to believe though since it is working correctly for smaller zoom factors than the ones pictured here.

Working correctly:
Correct

Missing a corner:
enter image description here

The Code

I have tried to reduce the code needed to reproduce it and have ended up with the following. Note that the number in the screenshots is the value of zoomFactor, the only variable. You should be able to paste this code into the OnPaint function of a newly created Win32 application project and manually declare zoomFactor a constant.

SetMapMode(hdc, MM_ISOTROPIC);
SetWindowOrgEx(hdc, 0, 40, nullptr);
SetWindowExtEx(hdc, 8000, 6000, nullptr);
SetViewportOrgEx(hdc, 16, 56, nullptr);
SetViewportExtEx(hdc, 16 + (396)*zoomFactor/1000,
                      48 + (279)*zoomFactor/1000, nullptr);

BeginPath(hdc);

MoveToEx(hdc, 70, 1250, nullptr);
ArcTo(hdc,
    50,   1250, 90,   1290,
    70,   1250,
    50,   1270);
ArcTo(hdc,
    50,   2311, 90,   2351,
    50,   2331,
    70,   2351);
ArcTo(hdc,
    1068, 2311, 1108, 2351,
    1088, 2351,
    1108, 2331);
ArcTo(hdc,
    1068, 1250, 1108, 1290,
    1108, 1270,
    1088, 1250);

CloseFigure(hdc);
EndPath(hdc);
SelectClipPath(hdc, RGN_AND);

HBRUSH br = CreateSolidBrush(RGB(255,0,255));
const RECT r = {0, 0, 8000, 6000};
FillRect(hdc, &r, br);

Upvotes: 2

Views: 196

Answers (1)

arx
arx

Reputation: 16896

Here is a simpler bit of code to illustrate the problem:

const int r = 20;
MoveToEx(hdc, 200, 100, 0);
BOOL b = ArcTo(hdc,
    100 + 2 * r, 100,
    100,         100 + 2 * r,
    100 + r,     100,
    100,         100 + r);
POINT p;
GetCurrentPositionEx(hdc, &p);

This draws a single corner of radius r. This works fine for non-zero values of r and the position p is correctly updated to match the end of the arc: (100, 100+r), give or take a pixel.

However, when r is zero ArcTo returns TRUE but the position is not updated: p contains the starting position of (200,100).

The documentation states that "If no error occurs, the current position is set to the ending point of the arc." The function returned TRUE indicating success so the position should have been updated.

In my view this a bug. The function should return FALSE because the rectangle is empty so there is no arc and thus no well-defined endpoint. However, it would be more useful in practice if the function returned TRUE and updated the current position to match the final coordinate pair in the parameter list. But it does neither of these things. EDIT: An even better implementation in your case would be to calculate the arc end points in logical coordinates before converting to device coordinates, but GDI in general doesn't work like this.

The problem occurs in your code because your coordinate transformation collapses the second arc's rectangle to an empty rectangle when the zoom is 266. You can see this yourself by adding the following to your code to transform the coordinates of the second arc:

POINT points[4] = {{50,2311},{90,2351},{50,2331},{70,2351}};
LPtoDP(hdc, points, 4);

With the zoom set to 266 the points are transformed to (17,90), (17,91), (17,91), (17,91) so the rectangle has no width and is empty. And you hit the ArcTo bug.

I guess it works for smaller zooms when the rounding happens to put the x-coordinates into adjacent integers rather than the same integer.

A simple fix would be to create a MyArcTo function that replaces the arc with a LineTo when it is too small to be visible.

Upvotes: 2

Related Questions