Skip to content

Commit

Permalink
Consistent arc drawing across different paint devices
Browse files Browse the repository at this point in the history
Consistent with how Qt's QPainter draws arcs
  • Loading branch information
RockinRoel committed Sep 6, 2019
1 parent 1b0a067 commit b410df0
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 76 deletions.
75 changes: 48 additions & 27 deletions src/Wt/WCanvasPaintDevice.C
Expand Up @@ -22,30 +22,32 @@
#include <cmath>
#include <sstream>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace {
static const double EPSILON = 1E-5;
}

namespace Wt {

namespace {
WPointF normalizedDegreesToRadians(double angle, double sweep) {
angle = 360 - angle;
int i = (int)angle / 360;
angle -= (i * 360);

double r1 = WTransform::degreesToRadians(angle);

if (std::fabs(sweep - 360) < 0.01)
sweep = 359.9;
else if (std::fabs(sweep + 360) < 0.01)
sweep = -359.9;

double a2 = angle - sweep;

double r2 = WTransform::degreesToRadians(a2);
double adjust360(double d) {
if (d > 360.0)
return 360.0;
else if (d < -360.0)
return -360.0;
else
return d;
}

return WPointF(r1, r2);
double adjustPositive360(double d) {
const double result = std::fmod(d, 360.0);
if (result < 0)
return result + 360.0;
else
return result;
}

bool fequal(double d1, double d2) {
Expand Down Expand Up @@ -254,19 +256,28 @@ void WCanvasPaintDevice::drawArc(const WRectF& rect, double startAngle,

renderStateChanges(true);

WPointF ra = normalizedDegreesToRadians(startAngle, spanAngle);
const double rStartAngle = WTransform::degreesToRadians(adjustPositive360(-startAngle));
double rEndAngle;
if (spanAngle >= 360.0 || spanAngle <= -360.0) {
rEndAngle = rStartAngle - 2.0 * M_PI * (spanAngle > 0 ? 1.0 : -1.0);
} else {
rEndAngle = WTransform::degreesToRadians(adjustPositive360(-startAngle - adjust360(spanAngle)));
}
const bool anticlockwise = spanAngle > 0;

double sx, sy, r, lw;
if (rect.width() > rect.height()) {
sx = 1;
sy = std::max(0.005, rect.height() / rect.width());
r = rect.width()/2;
} else {
} else if (rect.width() < rect.height()) {
sx = std::max(0.005, rect.width() / rect.height());
sy = 1;
lw = painter()->normalizedPenWidth(painter()->pen().width(), true).value()
* 1 / std::min(sx, sy);
r = rect.height()/2;
} else {
sx = 1;
sy = 1;
r = rect.width() / 2;
}

const WPen& pen = painter()->pen();
Expand All @@ -288,8 +299,9 @@ void WCanvasPaintDevice::drawArc(const WRectF& rect, double startAngle,
js_ << "ctx.lineWidth = " << Utils::round_js_str(lw, 3, buf) << ";"
<< "ctx.beginPath();";
js_ << "ctx.arc(0,0," << Utils::round_js_str(r, 3, buf);
js_ << ',' << Utils::round_js_str(ra.x(), 3, buf);
js_ << "," << Utils::round_js_str(ra.y(), 3, buf) << ",true);";
js_ << ',' << Utils::round_js_str(rStartAngle, 6, buf);
js_ << ',' << Utils::round_js_str(rEndAngle, 6, buf) << ',';
js_ << (anticlockwise ? "true" : "false") << ");";

// restore comes before fill and stroke, otherwise the gradient will use
// this temporary coordinate system
Expand Down Expand Up @@ -390,11 +402,20 @@ void WCanvasPaintDevice::drawPlainPath(std::stringstream& out,
break;
case ArcAngleSweep:
{
WPointF r = normalizedDegreesToRadians(s.x(), s.y());

out << ',' << Utils::round_js_str(r.x(), 3, buf);
out << ',' << Utils::round_js_str(r.y(), 3, buf);
out << ',' << (s.y() > 0 ? "true" : "false") << ");";
const double startAngle = s.x();
const double spanAngle = s.y();
const double rStartAngle = WTransform::degreesToRadians(adjustPositive360(-startAngle));
double rEndAngle;
if (spanAngle >= 360.0 || spanAngle <= -360.0) {
rEndAngle = rStartAngle - 2.0 * M_PI * (spanAngle > 0 ? 1.0 : -1.0);
} else {
rEndAngle = WTransform::degreesToRadians(adjustPositive360(-startAngle - adjust360(spanAngle)));
}
const bool anticlockwise = spanAngle > 0;

out << ',' << Utils::round_js_str(rStartAngle, 6, buf);
out << ',' << Utils::round_js_str(rEndAngle, 6, buf);
out << ',' << (anticlockwise ? "true" : "false") << ");";
}
break;
case QuadC: {
Expand Down
21 changes: 15 additions & 6 deletions src/Wt/WPdfImage.C
Expand Up @@ -20,6 +20,7 @@
#include "ImageUtils.h"
#include "WebUtils.h"

#include <cmath>
#include <ctype.h>
#include <stdio.h>
#include <hpdf.h>
Expand Down Expand Up @@ -215,7 +216,10 @@ void WPdfImage::setChanged(WFlags<PainterChangeFlag> flags)
if (pen.style() != PenStyle::None) {
setStrokeColor(pen.color());

WLength w = painter()->normalizedPenWidth(pen.width(), false);
// Pen width is already scaled by the PDF transform (Page_Concat)
// Cosmetic pens are also correctly drawn as 1px
WLength w = pen.width();

HPDF_Page_SetLineWidth(page_, w.toPixels());

switch (pen.capStyle()) {
Expand Down Expand Up @@ -388,7 +392,7 @@ void WPdfImage::drawArc(const WRectF& rect, double startAngle, double spanAngle)
if (end < start)
std::swap(start, end);

if (spanAngle < (360 - EPSILON) )
if (std::fabs(spanAngle) < (360 - EPSILON) )
HPDF_Page_Arc(page_, 0, 0, rect.width() / 2, start + 90, end + 90);
else
HPDF_Page_Circle(page_, 0, 0, rect.width() / 2);
Expand Down Expand Up @@ -540,10 +544,15 @@ void WPdfImage::drawPlainPath(const WPainterPath& path)
const double x = s.x();
const double y = s.y();
const double radius = segments[i+1].x();
double ang1 = segments[i+2].x();
double ang2 = ang1 + segments[i+2].y();

HPDF_Page_Arc(page_, x, y, radius, ang1 + 90, ang2 + 90);
const double startAngle = segments[i+2].x();
double spanAngle = segments[i+2].y();
if (std::fabs(spanAngle) >= (360 - EPSILON))
spanAngle = 360;
const double betweenAngle = startAngle + spanAngle / 2;
const double endAngle = startAngle + spanAngle;

HPDF_Page_Arc(page_, x, y, radius, startAngle + 90, betweenAngle + 90);
HPDF_Page_Arc(page_, x, y, radius, betweenAngle + 90, endAngle + 90);

i += 2;
break;
Expand Down
31 changes: 25 additions & 6 deletions src/Wt/WRasterImage-d2d1.C
Expand Up @@ -43,6 +43,25 @@

namespace {

double adjust360(double d)
{
if (d > 360.0)
return 360.0;
else if (d < -360.0)
return -360.0;
else
return d;
}

double adjustPositive360(double d)
{
const double result = std::fmod(d, 360.0);
if (result < 0)
return result + 360.0;
else
return result;
}

template <class T> void SafeRelease(T *&ppT)
{
if (ppT) {
Expand Down Expand Up @@ -606,8 +625,8 @@ void WRasterImage::drawArc(const WRectF& rect,
const double rx = rect.width() / 2.0;
const double ry = rect.height() / 2.0;

const double startAngleRad = startAngle / 180.0 * M_PI;
const double spanAngleRad = min(spanAngle / 180.0 * M_PI, 2 * M_PI);
const double startAngleRad = adjustPositive360(startAngle) / 180.0 * M_PI;
const double spanAngleRad = adjust360(spanAngle) / 180.0 * M_PI;
const double midAngle = startAngleRad + spanAngleRad / 2.0;
const double endAngle = startAngleRad + spanAngleRad;
D2D1_POINT_2F startPoint = D2D1::Point2F(
Expand All @@ -624,14 +643,14 @@ void WRasterImage::drawArc(const WRectF& rect,
midPoint,
D2D1::SizeF(static_cast<FLOAT>(rx), static_cast<FLOAT>(ry)),
0.f,
D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
spanAngle > 0 ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : D2D1_SWEEP_DIRECTION_CLOCKWISE,
D2D1_ARC_SIZE_SMALL
);
D2D1_ARC_SEGMENT arc2 = D2D1::ArcSegment(
endPoint,
D2D1::SizeF(static_cast<FLOAT>(rx), static_cast<FLOAT>(ry)),
0.f,
D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
spanAngle > 0 ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : D2D1_SWEEP_DIRECTION_CLOCKWISE,
D2D1_ARC_SIZE_SMALL
);

Expand Down Expand Up @@ -860,8 +879,8 @@ void WRasterImage::Impl::drawPlainPath(ID2D1PathGeometry *p, const WPainterPath&
const double cy = s.y();
const double rx = segments[i+1].x();
const double ry = segments[i+1].y();
const double startAngle = segments[i+2].x() / 180.0 * M_PI;
const double sweepAngle = min(segments[i+2].y() / 180.0 * M_PI, 2 * M_PI);
const double startAngle = adjustPositive360(segments[i+2].x()) / 180.0 * M_PI;
const double sweepAngle = adjust360(segments[i+2].y()) / 180.0 * M_PI;

const double endAngle = startAngle + sweepAngle;
const double midAngle = startAngle + sweepAngle / 2.0;
Expand Down
33 changes: 23 additions & 10 deletions src/Wt/WRasterImage-gm.C
Expand Up @@ -54,14 +54,17 @@ namespace {
}
#endif

#include <algorithm>
#include <utility>

namespace {
static const double EPSILON = 1E-5;

double adjust360(double d) {
if (std::fabs(d - 360) < 0.01)
return 359.5;
else if (std::fabs(d + 360) < 0.01)
return -359.5;
if (d > 360.0)
return 360.0;
else if (d < -360.0)
return -360.0;
else
return d;
}
Expand Down Expand Up @@ -646,8 +649,14 @@ void WRasterImage::drawArc(const WRectF& rect,
{
impl_->internalInit();

DrawArc(impl_->context_, rect.left(), rect.top(), rect.right(), rect.bottom(),
startAngle, startAngle + spanAngle);
double start = - startAngle;
double end = - startAngle - spanAngle;

if (end < start)
std::swap(start, end);

DrawArc(impl_->context_, rect.left(), rect.top(), rect.right(), rect.bottom(),
start, end);
}

void WRasterImage::drawImage(const WRectF& rect, const std::string& imgUri,
Expand Down Expand Up @@ -843,16 +852,20 @@ void WRasterImage::Impl::drawPlainPath(const WPainterPath& path)

const double x1 = rx * std::cos(theta1) + cx;
const double y1 = ry * std::sin(theta1) + cy;
const double x2 = rx * std::cos(theta1 + deltaTheta) + cx;
const double y2 = ry * std::sin(theta1 + deltaTheta) + cy;
const int fa = (std::fabs(deltaTheta) > M_PI ? 1 : 0);
const int fs = (deltaTheta > 0 ? 1 : 0);
const double x2 = rx * std::cos(theta1 + deltaTheta / 2.0) + cx;
const double y2 = ry * std::sin(theta1 + deltaTheta / 2.0) + cy;
const double x3 = rx * std::cos(theta1 + deltaTheta) + cx;
const double y3 = ry * std::sin(theta1 + deltaTheta) + cy;
const int fa = 0;
const unsigned int fs = (deltaTheta > 0 ? 1 : 0);

if (!fequal(current.x(), x1) || !fequal(current.y(), y1))
DrawPathLineToAbsolute(context_, x1 - 0.5, y1 - 0.5);

DrawPathEllipticArcAbsolute(context_, rx, ry, 0, fa, fs,
x2 - 0.5, y2 - 0.5);
DrawPathEllipticArcAbsolute(context_, rx, ry, 0, fa, fs,
x3 - 0.5, y3 - 0.5);

i += 2;
break;
Expand Down
25 changes: 17 additions & 8 deletions src/Wt/WSvgImage.C
Expand Up @@ -28,10 +28,10 @@

namespace {
double adjust360(double d) {
if (std::fabs(d - 360) < 0.01)
return 359.5;
else if (std::fabs(d + 360) < 0.01)
return -359.5;
if (d > 360.0)
return 360.0;
else if (d < -360.0)
return -360.0;
else
return d;
}
Expand Down Expand Up @@ -104,7 +104,7 @@ void WSvgImage::drawArc(const WRectF& rect, double startAngle, double spanAngle)
{
char buf[30];

if (std::fabs(spanAngle - 360.0) < 0.01) {
if (std::fabs(spanAngle - 360.0) < 0.01 || spanAngle > 360.0) {
finishPath();
makeNewGroup();

Expand Down Expand Up @@ -439,9 +439,12 @@ void WSvgImage::drawPlainPath(WStringStream& out, const WPainterPath& path)
*/
const double x1 = rx * std::cos(theta1) + cx;
const double y1 = ry * std::sin(theta1) + cy;
const double x2 = rx * std::cos(theta1 + deltaTheta) + cx;
const double y2 = ry * std::sin(theta1 + deltaTheta) + cy;
const int fa = (std::fabs(deltaTheta) > M_PI ? 1 : 0);
const double x2 = rx * std::cos(theta1 + deltaTheta / 2.0) + cx;
const double y2 = ry * std::sin(theta1 + deltaTheta / 2.0) + cy;
const double x3 = rx * std::cos(theta1 + deltaTheta) + cx;
const double y3 = ry * std::sin(theta1 + deltaTheta) + cy;
// const int fa = (std::fabs(deltaTheta) > M_PI ? 1 : 0);
const int fa = 0;
const int fs = (deltaTheta > 0 ? 1 : 0);

if (!fequal(current.x(), x1) || !fequal(current.y(), y1)) {
Expand All @@ -454,6 +457,12 @@ void WSvgImage::drawPlainPath(WStringStream& out, const WPainterPath& path)
out << " 0 " << fa << "," << fs;
out << ' ' << Utils::round_js_str(x2 + pathTranslation_.x(), 3, buf);
out << ',' << Utils::round_js_str(y2 + pathTranslation_.y(), 3, buf);

out << 'A' << Utils::round_js_str(rx, 3, buf);
out << ',' << Utils::round_js_str(ry, 3, buf);
out << " 0 " << fa << "," << fs;
out << ' ' << Utils::round_js_str(x3 + pathTranslation_.x(), 3, buf);
out << ',' << Utils::round_js_str(y3 + pathTranslation_.y(), 3, buf);
} else {
switch (s.type()) {
case MoveTo:
Expand Down
2 changes: 1 addition & 1 deletion src/Wt/WTransform.C
Expand Up @@ -216,7 +216,7 @@ WTransform& WTransform::rotate(double angle)

double WTransform::degreesToRadians(double angle)
{
return (angle / 180.) * M_PI;
return (angle / 180.) * M_PI;
}

WTransform& WTransform::scale(double sx, double sy)
Expand Down

0 comments on commit b410df0

Please sign in to comment.