From 9f9d5930190fc1ef08f82ec4a23ff2b1327958f3 Mon Sep 17 00:00:00 2001 From: Artur Wieczorek Date: Sat, 4 Jun 2016 10:20:34 +0200 Subject: [PATCH] Fixed adding arc to wxGraphicsPath with Direct2D renderer. To handle properly all combinations of the start and end angles and to ensure consistency with Cairo renderer behaviour there is necessary 1. To normalize angle values the same way as it is done in Cairo. and 2. When difference between end angle and start angle >= 2*pi then in addition to the arc itself also one or more full circles have to be added to the path. See #17558 See #17557 --- interface/wx/graphics.h | 5 ++ src/msw/graphicsd2d.cpp | 113 ++++++++++++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 27 deletions(-) diff --git a/interface/wx/graphics.h b/interface/wx/graphics.h index 6f6648554b..eca9c84f15 100644 --- a/interface/wx/graphics.h +++ b/interface/wx/graphics.h @@ -31,6 +31,11 @@ public: The angles are measured in radians but, contrary to the usual mathematical convention, are always @e clockwise from the horizontal axis. + If for clockwise arc @a endAngle is less than @a startAngle it will be + progressively increased by 2*pi until it is greater than @a startAngle. + If for counter-clockwise arc @a endAngle is greater than @a startAngle + it will be progressively decreased by 2*pi until it is less than + @a startAngle. */ //@{ virtual void AddArc(wxDouble x, wxDouble y, wxDouble r, diff --git a/src/msw/graphicsd2d.cpp b/src/msw/graphicsd2d.cpp index 4befc60c70..d58b601a83 100644 --- a/src/msw/graphicsd2d.cpp +++ b/src/msw/graphicsd2d.cpp @@ -1414,7 +1414,39 @@ void wxD2DPathData::AddCurveToPoint(wxDouble cx1, wxDouble cy1, wxDouble cx2, wx // adds an arc of a circle centering at (x,y) with radius (r) from startAngle to endAngle void wxD2DPathData::AddArc(wxDouble x, wxDouble y, wxDouble r, wxDouble startAngle, wxDouble endAngle, bool clockwise) { - wxPoint2DDouble center = wxPoint2DDouble(x, y); + double angle; + + // For the sake of consistency normalize angles the same way + // as it is done in Cairo. + if ( clockwise ) + { + // If endAngle < startAngle it needs to be progressively + // increased by 2*M_PI until endAngle > startAngle. + if ( endAngle < startAngle ) + { + while ( endAngle <= startAngle ) + { + endAngle += 2.0*M_PI; + } + } + + angle = endAngle - startAngle; + } + else + { + // If endAngle > startAngle it needs to be progressively + // decreased by 2*M_PI until endAngle < startAngle. + if ( endAngle > startAngle ) + { + while ( endAngle >= startAngle ) + { + endAngle -= 2.0*M_PI; + } + } + + angle = startAngle - endAngle; + } + wxPoint2DDouble start = wxPoint2DDouble(cos(startAngle) * r, sin(startAngle) * r); wxPoint2DDouble end = wxPoint2DDouble(cos(endAngle) * r, sin(endAngle) * r); @@ -1427,41 +1459,68 @@ void wxD2DPathData::AddArc(wxDouble x, wxDouble y, wxDouble r, wxDouble startAng MoveToPoint(start.m_x + x, start.m_y + y); } - double angle = (end.GetVectorAngle() - start.GetVectorAngle()); - if ( angle == 360 || angle == -360 ) - { - AddCircle(center.m_x, center.m_y, r); - return; - } - - if ( !clockwise ) - angle = -angle; - - if ( angle < 0 ) - angle += 360; - - while (abs(angle) > 360) - { - angle -= (angle / abs(angle)) * 360; - } - D2D1_SWEEP_DIRECTION sweepDirection = clockwise ? D2D1_SWEEP_DIRECTION_CLOCKWISE : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + D2D1_SIZE_F size = D2D1::SizeF((FLOAT)r, (FLOAT)r); - D2D1_ARC_SIZE arcSize = angle > 180 ? + if ( angle >= 2.0*M_PI ) + { + // In addition to arc we need to draw full circle(s). + // Remarks: + // 1. Parity of the number of the circles has to be + // preserved because this matters when path would be + // filled with wxODDEVEN_RULE flag set (using + // D2D1_FILL_MODE_ALTERNATE mode) when number of the + // edges is counted. + // 2. ID2D1GeometrySink::AddArc() doesn't work + // with 360-degree arcs so we need to construct + // the circle from two halves. + D2D1_ARC_SEGMENT circleSegment1 = + { + D2D1::Point2((FLOAT)(x - start.m_x), (FLOAT)(y - start.m_y)), // end point + size, // size + 0.0f, // rotation + sweepDirection, // sweep direction + D2D1_ARC_SIZE_SMALL // arc size + }; + D2D1_ARC_SEGMENT circleSegment2 = + { + D2D1::Point2((FLOAT)(x + start.m_x), (FLOAT)(y + start.m_y)), // end point + size, // size + 0.0f, // rotation + sweepDirection, // sweep direction + D2D1_ARC_SIZE_SMALL // arc size + }; + + int numCircles = (int)(angle / (2.0*M_PI)); + numCircles = (numCircles - 1) % 2 + 1; + for( int i = 0; i < numCircles; i++ ) + { + m_geometrySink->AddArc(circleSegment1); + m_geometrySink->AddArc(circleSegment2); + } + + // Reduce the angle to [0..2*M_PI) range. + angle = fmod(angle, 2.0*M_PI); + } + + D2D1_ARC_SIZE arcSize = angle > M_PI ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL; + D2D1_POINT_2F endPoint = + D2D1::Point2((FLOAT)(end.m_x + x), (FLOAT)(end.m_y + y)); - D2D1_ARC_SEGMENT arcSegment = { - D2D1::Point2((FLOAT)(end.m_x + x), (FLOAT)(end.m_y + y)), // end point - D2D1::SizeF((FLOAT)r, (FLOAT)r), // size - 0.0f, // rotation - sweepDirection, // sweep direction - arcSize // arc size + D2D1_ARC_SEGMENT arcSegment = + { + endPoint, // end point + size, // size + 0.0f, // rotation + sweepDirection, // sweep direction + arcSize // arc size }; m_geometrySink->AddArc(arcSegment); - m_currentPoint = D2D1::Point2F(end.m_x + x, end.m_y + y); + m_currentPoint = endPoint; } // appends an ellipsis as a new closed subpath fitting the passed rectangle