void Path::addRoundedRectangle (const float x, const float y, const float w, const float h, float csx, float csy, const bool curveTopLeft, const bool curveTopRight, const bool curveBottomLeft, const bool curveBottomRight) { csx = jmin (csx, w * 0.5f); csy = jmin (csy, h * 0.5f); const float cs45x = csx * 0.45f; const float cs45y = csy * 0.45f; const float x2 = x + w; const float y2 = y + h; if (curveTopLeft) { startNewSubPath (x, y + csy); cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); } else { startNewSubPath (x, y); } if (curveTopRight) { lineTo (x2 - csx, y); cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); } else { lineTo (x2, y); } if (curveBottomRight) { lineTo (x2, y2 - csy); cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); } else { lineTo (x2, y2); } if (curveBottomLeft) { lineTo (x + csx, y2); cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); } else { lineTo (x, y2); } closeSubPath(); }
void Path::addRectangle(const double x, const double y, const double w, const double h, double rx, double ry, const char corner) noexcept { rx = clip(rx, 0., w * 0.5f); ry = clip(ry, 0., h * 0.5f); const double r45x = rx * 0.45f; const double r45y = ry * 0.45f; const double x2 = x + w; const double y2 = y + h; if (corner & Rectangle::TopLeft) { addNode(Node(Point(x, y + ry), Move)); cubicTo(Point(x, y + r45y), Point(x + r45x, y), Point(x + rx, y)); } else { addNode(Node(Point(x, y), Move)); } if (corner & Rectangle::TopRight) { lineTo(Point(x2 - rx, y)); cubicTo(Point(x2 - r45x, y), Point(x2, y + r45y), Point(x2, y + ry)); } else { lineTo(Point(x2, y)); } if (corner & Rectangle::BottomRight) { lineTo(Point(x2, y2 - ry)); cubicTo(Point(x2, y2 - r45y), Point(x2 - r45x, y2), Point(x2 - rx, y2)); } else { lineTo(Point(x2, y2)); } if (corner & Rectangle::BottomLeft) { lineTo(Point(x + rx, y2)); cubicTo(Point(x + r45x, y2), Point(x, y2 - r45y), Point(x, y2 - ry)); } else { lineTo(Point(x, y2)); } close(); }
void Path::addEllipse (Rectangle<float> area) { const float hw = area.getWidth() * 0.5f; const float hw55 = hw * 0.55f; const float hh = area.getHeight() * 0.5f; const float hh55 = hh * 0.55f; const float cx = area.getX() + hw; const float cy = area.getY() + hh; startNewSubPath (cx, cy - hh); cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh); cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy); cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh); closeSubPath(); }
void Path::addEllipse(Rectangle const& rect) noexcept { const double hw = rect.width() * 0.5f; const double hw55 = hw * 0.55f; const double hh = rect.height() * 0.5f; const double hh55 = hh * 0.55f; const double cx = rect.x() + hw; const double cy = rect.y() + hh; moveTo(Point(cx, cy - hh)); cubicTo(Point(cx + hw55, cy - hh), Point(cx + hw, cy - hh55), Point(cx + hw, cy)); cubicTo(Point(cx + hw, cy + hh55), Point(cx + hw55, cy + hh), Point(cx, cy + hh)); cubicTo(Point(cx - hw55, cy + hh), Point(cx - hw, cy + hh55), Point(cx - hw, cy)); cubicTo(Point(cx - hw, cy - hh55), Point(cx - hw55, cy - hh), Point(cx, cy - hh)); close(); }
void ShapeMaker::cubicTo (const Bezier &cubic) { const double sqrt3 = 1.7320508075688772935274463415059; const double precision = 600 * 18 / (60 * factorx * sqrt3); // distance between control points of quadratic approximations of either end double D01 = (cubic.p1 - (cubic.c1 - cubic.c0) * 3 - cubic.p0).magnitude () / 2; double tMax3 = precision / D01; if (tMax3 >= 1) { curveTo (cubic.quadraticCtrl(), cubic.p1); return; } Bezier end(cubic); if (tMax3 >= 0.5 * 0.5 * 0.5) { Bezier start (end.split (0.5)); curveTo (start.quadraticCtrl (), start.p1); } else { double tMax = pow (tMax3, 1.0/3.0); Bezier start (end.split (tMax)); Bezier middle (end.split (1 - tMax / (1 - tMax))); curveTo (start.quadraticCtrl (), start.p1); cubicTo (middle); } curveTo (end.quadraticCtrl (), end.p1); }
void Path::addEllipse (const float x, const float y, const float w, const float h) { const float hw = w * 0.5f; const float hw55 = hw * 0.55f; const float hh = h * 0.5f; const float hh55 = hh * 0.55f; const float cx = x + hw; const float cy = y + hh; startNewSubPath (cx, cy - hh); cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh); cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy); cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh); closeSubPath(); }
void Path::cubicTo (const Point<float> controlPoint1, const Point<float> controlPoint2, const Point<float> endPoint) { cubicTo (controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y); }
void Path::addPath (const Path& other, const AffineTransform& transformToApply) { size_t i = 0; const float* const d = other.data.elements; while (i < other.numElements) { const float type = d [i++]; if (type == closeSubPathMarker) { closeSubPath(); } else { float x = d[i++]; float y = d[i++]; transformToApply.transformPoint (x, y); if (type == moveMarker) { startNewSubPath (x, y); } else if (type == lineMarker) { lineTo (x, y); } else if (type == quadMarker) { float x2 = d [i++]; float y2 = d [i++]; transformToApply.transformPoint (x2, y2); quadraticTo (x, y, x2, y2); } else if (type == cubicMarker) { float x2 = d [i++]; float y2 = d [i++]; float x3 = d [i++]; float y3 = d [i++]; transformToApply.transformPoints (x2, y2, x3, y3); cubicTo (x, y, x2, y2, x3, y3); } else { // something's gone wrong with the element list! jassertfalse; } } } }
void Path::addRoundedRectangle (const float x, const float y, const float w, const float h, float csx, float csy) { csx = jmin (csx, w * 0.5f); csy = jmin (csy, h * 0.5f); const float cs45x = csx * 0.45f; const float cs45y = csy * 0.45f; const float x2 = x + w; const float y2 = y + h; startNewSubPath (x + csx, y); lineTo (x2 - csx, y); cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); lineTo (x2, y2 - csy); cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); lineTo (x + csx, y2); cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); lineTo (x, y + csy); cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); closeSubPath(); }
void Path::addPath (const Path& other) { size_t i = 0; const float* const d = other.data.elements; while (i < other.numElements) { const float type = d[i++]; if (type == moveMarker) { startNewSubPath (d[i], d[i + 1]); i += 2; } else if (type == lineMarker) { lineTo (d[i], d[i + 1]); i += 2; } else if (type == quadMarker) { quadraticTo (d[i], d[i + 1], d[i + 2], d[i + 3]); i += 4; } else if (type == cubicMarker) { cubicTo (d[i], d[i + 1], d[i + 2], d[i + 3], d[i + 4], d[i + 5]); i += 6; } else if (type == closeSubPathMarker) { closeSubPath(); } else { // something's gone wrong with the element list! jassertfalse; } } }
void ShapeMaker::cubicTo(double x1, double y1, double x2, double y2, double ax, double ay) { Point a(lastx,lasty); Point b(x1,y1); Point c(x2,y2); Point d(ax,ay); Bezier cubic (a, b, c, d); double t0, t1; int cInflections = cubic.computeInflections (t0, t1); if (cInflections == 0) { cubicTo(cubic); } else if (cInflections == 1) { Bezier cubic0 = cubic.split (t0); cubicTo (cubic0); cubicTo (cubic); } else { Bezier cubic0 = cubic.split (t0); cubicTo (cubic0); Bezier cubic1 = cubic.split (1 - (1 - t1) / (1 - t0)); cubicTo (cubic1); cubicTo (cubic); } lastx = ax; lasty = ay; smoothx = ax - x2; smoothy = ay - y2; }
void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &) { const qreal *pts = path.points(); const QPainterPath::ElementType *types = path.elements(); int count = path.elementCount(); if (count < 2) return; float realWidth = qpen_widthf(pen); if (realWidth == 0) realWidth = 1; m_width = realWidth / 2; bool cosmetic = pen.isCosmetic(); if (cosmetic) { m_width = m_width * m_inv_scale; } m_join_style = qpen_joinStyle(pen); m_cap_style = qpen_capStyle(pen); m_vertices.reset(); m_miter_limit = pen.miterLimit() * qpen_widthf(pen); // The curvyness is based on the notion that I originally wanted // roughly one line segment pr 4 pixels. This may seem little, but // because we sample at constantly incrementing B(t) E [0<t<1], we // will get longer segments where the curvature is small and smaller // segments when the curvature is high. // // To get a rough idea of the length of each curve, I pretend that // the curve is a 90 degree arc, whose radius is // qMax(curveBounds.width, curveBounds.height). Based on this // logic we can estimate the length of the outline edges based on // the radius + a pen width and adjusting for scale factors // depending on if the pen is cosmetic or not. // // The curvyness value of PI/14 was based on, // arcLength = 2*PI*r/4 = PI*r/2 and splitting length into somewhere // between 3 and 8 where 5 seemed to be give pretty good results // hence: Q_PI/14. Lower divisors will give more detail at the // direct cost of performance. // simplfy pens that are thin in device size (2px wide or less) if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) { if (m_cap_style == Qt::RoundCap) m_cap_style = Qt::SquareCap; if (m_join_style == Qt::RoundJoin) m_join_style = Qt::MiterJoin; m_curvyness_add = 0.5; m_curvyness_mul = CURVE_FLATNESS / m_inv_scale; m_roundness = 1; } else if (cosmetic) { m_curvyness_add = realWidth / 2; m_curvyness_mul = CURVE_FLATNESS; m_roundness = qMax<int>(4, realWidth * CURVE_FLATNESS); } else { m_curvyness_add = m_width; m_curvyness_mul = CURVE_FLATNESS / m_inv_scale; m_roundness = qMax<int>(4, realWidth * m_curvyness_mul); } // Over this level of segmentation, there doesn't seem to be any // benefit, even for huge penWidth if (m_roundness > 24) m_roundness = 24; m_sin_theta = qFastSin(Q_PI / m_roundness); m_cos_theta = qFastCos(Q_PI / m_roundness); const qreal *endPts = pts + (count<<1); const qreal *startPts = 0; Qt::PenCapStyle cap = m_cap_style; if (!types) { // skip duplicate points while((pts + 2) < endPts && pts[0] == pts[2] && pts[1] == pts[3]) pts += 2; if ((pts + 2) == endPts) return; startPts = pts; bool endsAtStart = startPts[0] == *(endPts-2) && startPts[1] == *(endPts-1); if (endsAtStart || path.hasImplicitClose()) m_cap_style = Qt::FlatCap; moveTo(pts); m_cap_style = cap; pts += 2; lineTo(pts); pts += 2; while (pts < endPts) { if (m_cx != pts[0] || m_cy != pts[1]) { join(pts); lineTo(pts); } pts += 2; } endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart); } else { bool endsAtStart = false; while (pts < endPts) { switch (*types) { case QPainterPath::MoveToElement: { if (pts != path.points()) endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart); startPts = pts; int end = (endPts - pts) / 2; int i = 2; // Start looking to ahead since we never have two moveto's in a row while (i<end && types[i] != QPainterPath::MoveToElement) { ++i; } endsAtStart = startPts[0] == pts[i*2 - 2] && startPts[1] == pts[i*2 - 1]; if (endsAtStart || path.hasImplicitClose()) m_cap_style = Qt::FlatCap; moveTo(pts); m_cap_style = cap; pts+=2; ++types; break; } case QPainterPath::LineToElement: if (*(types - 1) != QPainterPath::MoveToElement) join(pts); lineTo(pts); pts+=2; ++types; break; case QPainterPath::CurveToElement: if (*(types - 1) != QPainterPath::MoveToElement) join(pts); cubicTo(pts); pts+=6; types+=3; break; default: Q_ASSERT(false); break; } } endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart); } }
void WPainterPath::cubicTo(const WPointF& c1, const WPointF& c2, const WPointF& endPoint) { cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), endPoint.x(), endPoint.y()); }
void ShapeMaker::smoothCubicTo( double x2, double y2, double ax, double ay ) { cubicTo( lastx + smoothx, lasty + smoothy, x2, y2, ax, ay ); }
void ShapeMaker::cubicToR( double x1, double y1, double x2, double y2, double ax, double ay ) { cubicTo(lastx + x1, lasty + y1, lastx + x2, lasty + y2, lastx + ax, lasty + ay); }
void Path::restoreFromString (StringRef stringVersion) { clear(); setUsingNonZeroWinding (true); String::CharPointerType t (stringVersion.text); juce_wchar marker = 'm'; int numValues = 2; float values [6]; for (;;) { const String token (PathHelpers::nextToken (t)); const juce_wchar firstChar = token[0]; int startNum = 0; if (firstChar == 0) break; if (firstChar == 'm' || firstChar == 'l') { marker = firstChar; numValues = 2; } else if (firstChar == 'q') { marker = firstChar; numValues = 4; } else if (firstChar == 'c') { marker = firstChar; numValues = 6; } else if (firstChar == 'z') { marker = firstChar; numValues = 0; } else if (firstChar == 'a') { setUsingNonZeroWinding (false); continue; } else { ++startNum; values [0] = token.getFloatValue(); } for (int i = startNum; i < numValues; ++i) values [i] = PathHelpers::nextToken (t).getFloatValue(); switch (marker) { case 'm': startNewSubPath (values[0], values[1]); break; case 'l': lineTo (values[0], values[1]); break; case 'q': quadraticTo (values[0], values[1], values[2], values[3]); break; case 'c': cubicTo (values[0], values[1], values[2], values[3], values[4], values[5]); break; case 'z': closeSubPath(); break; default: jassertfalse; break; // illegal string format? } } }
//============================================================================== void Path::loadPathFromStream (InputStream& source) { while (! source.isExhausted()) { switch (source.readByte()) { case 'm': { const float x = source.readFloat(); const float y = source.readFloat(); startNewSubPath (x, y); break; } case 'l': { const float x = source.readFloat(); const float y = source.readFloat(); lineTo (x, y); break; } case 'q': { const float x1 = source.readFloat(); const float y1 = source.readFloat(); const float x2 = source.readFloat(); const float y2 = source.readFloat(); quadraticTo (x1, y1, x2, y2); break; } case 'b': { const float x1 = source.readFloat(); const float y1 = source.readFloat(); const float x2 = source.readFloat(); const float y2 = source.readFloat(); const float x3 = source.readFloat(); const float y3 = source.readFloat(); cubicTo (x1, y1, x2, y2, x3, y3); break; } case 'c': closeSubPath(); break; case 'n': useNonZeroWinding = true; break; case 'z': useNonZeroWinding = false; break; case 'e': return; // end of path marker default: jassertfalse; // illegal char in the stream break; } } }