src/opengl/gl2paintengineex/qtriangulatingstroker.cpp
changeset 3 41300fa6a67c
parent 0 1918ee327afb
child 4 3b1da2848fc7
--- a/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp	Tue Jan 26 12:42:25 2010 +0200
+++ b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp	Tue Feb 02 00:43:10 2010 +0200
@@ -42,6 +42,7 @@
 #include "qtriangulatingstroker_p.h"
 #include <qmath.h>
 
+QT_BEGIN_NAMESPACE
 
 #define CURVE_FLATNESS Q_PI / 8
 
@@ -60,6 +61,15 @@
     } else {
         endCap(cur);
     }
+    int count = m_vertices.size();
+
+    // Copy the (x, y) values because QDataBuffer::add(const float& t)
+    // may resize the buffer, which will leave t pointing at the
+    // previous buffer's memory region if we don't copy first.
+    float x = m_vertices.at(count-2);
+    float y = m_vertices.at(count-1);
+    m_vertices.add(x);
+    m_vertices.add(y);
 }
 
 
@@ -113,7 +123,7 @@
         if (m_join_style == Qt::RoundJoin)
             m_join_style = Qt::MiterJoin;
         m_curvyness_add = 0.5;
-        m_curvyness_mul = CURVE_FLATNESS;
+        m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
         m_roundness = 1;
     } else if (cosmetic) {
         m_curvyness_add = realWidth / 2;
@@ -130,8 +140,8 @@
     if (m_roundness > 24)
         m_roundness = 24;
 
-    m_sin_theta = qSin(Q_PI / m_roundness); // ### Use qFastSin
-    m_cos_theta = qCos(Q_PI / m_roundness);
+    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;
@@ -143,7 +153,6 @@
 
         bool endsAtStart = startPts[0] == *(endPts-2) && startPts[1] == *(endPts-1);
 
-        Qt::PenCapStyle cap = m_cap_style;
         if (endsAtStart || path.hasImplicitClose())
             m_cap_style = Qt::FlatCap;
         moveTo(pts);
@@ -165,7 +174,7 @@
             switch (*types) {
             case QPainterPath::MoveToElement: {
                 if (pts != path.points())
-                    endCapOrJoinClosed(startPts, pts, path.hasImplicitClose(), endsAtStart);
+                    endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
 
                 startPts = pts;
                 int end = (endPts - pts) / 2;
@@ -206,6 +215,65 @@
     }
 }
 
+void QTriangulatingStroker::moveTo(const qreal *pts)
+{
+    m_cx = pts[0];
+    m_cy = pts[1];
+
+    float x2 = pts[2];
+    float y2 = pts[3];
+    normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy);
+
+
+    // To acheive jumps we insert zero-area tringles. This is done by
+    // adding two identical points in both the end of previous strip
+    // and beginning of next strip
+    bool invisibleJump = m_vertices.size();
+
+    switch (m_cap_style) {
+    case Qt::FlatCap:
+        if (invisibleJump) {
+            m_vertices.add(m_cx + m_nvx);
+            m_vertices.add(m_cy + m_nvy);
+        }
+        break;
+    case Qt::SquareCap: {
+        float sx = m_cx - m_nvy;
+        float sy = m_cy + m_nvx;
+        if (invisibleJump) {
+            m_vertices.add(sx + m_nvx);
+            m_vertices.add(sy + m_nvy);
+        }
+        emitLineSegment(sx, sy, m_nvx, m_nvy);
+        break; }
+    case Qt::RoundCap: {
+        QVarLengthArray<float> points;
+        arcPoints(m_cx, m_cy, m_cx + m_nvx, m_cy + m_nvy, m_cx - m_nvx, m_cy - m_nvy, points);
+        m_vertices.resize(m_vertices.size() + points.size() + 2 * int(invisibleJump));
+        int count = m_vertices.size();
+        int front = 0;
+        int end = points.size() / 2;
+        while (front != end) {
+            m_vertices.at(--count) = points[2 * end - 1];
+            m_vertices.at(--count) = points[2 * end - 2];
+            --end;
+            if (front == end)
+                break;
+            m_vertices.at(--count) = points[2 * front + 1];
+            m_vertices.at(--count) = points[2 * front + 0];
+            ++front;
+        }
+
+        if (invisibleJump) {
+            m_vertices.at(count - 1) = m_vertices.at(count + 1);
+            m_vertices.at(count - 2) = m_vertices.at(count + 0);
+        }
+        break; }
+    default: break; // ssssh gcc...
+    }
+    emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+}
+
 void QTriangulatingStroker::cubicTo(const qreal *pts)
 {
     const QPointF *p = (const QPointF *) pts;
@@ -243,7 +311,151 @@
     m_nvy = vy;
 }
 
+void QTriangulatingStroker::join(const qreal *pts)
+{
+    // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1])
+    normalVector(m_cx, m_cy, pts[0], pts[1], &m_nvx, &m_nvy);
 
+    switch (m_join_style) {
+    case Qt::BevelJoin:
+        break;
+    case Qt::SvgMiterJoin:
+    case Qt::MiterJoin: {
+        // Find out on which side the join should be.
+        int count = m_vertices.size();
+        float prevNvx = m_vertices.at(count - 2) - m_cx;
+        float prevNvy = m_vertices.at(count - 1) - m_cy;
+        float xprod = prevNvx * m_nvy - prevNvy * m_nvx;
+        float px, py, qx, qy;
+
+        // If the segments are parallel, use bevel join.
+        if (qFuzzyIsNull(xprod))
+            break;
+
+        // Find the corners of the previous and next segment to join.
+        if (xprod < 0) {
+            px = m_vertices.at(count - 2);
+            py = m_vertices.at(count - 1);
+            qx = m_cx - m_nvx;
+            qy = m_cy - m_nvy;
+        } else {
+            px = m_vertices.at(count - 4);
+            py = m_vertices.at(count - 3);
+            qx = m_cx + m_nvx;
+            qy = m_cy + m_nvy;
+        }
+
+        // Find intersection point.
+        float pu = px * prevNvx + py * prevNvy;
+        float qv = qx * m_nvx + qy * m_nvy;
+        float ix = (m_nvy * pu - prevNvy * qv) / xprod;
+        float iy = (prevNvx * qv - m_nvx * pu) / xprod;
+
+        // Check that the distance to the intersection point is less than the miter limit.
+        if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= m_miter_limit * m_miter_limit) {
+            m_vertices.add(ix);
+            m_vertices.add(iy);
+            m_vertices.add(ix);
+            m_vertices.add(iy);
+        }
+        // else
+        // Do a plain bevel join if the miter limit is exceeded or if
+        // the lines are parallel. This is not what the raster
+        // engine's stroker does, but it is both faster and similar to
+        // what some other graphics API's do.
+
+        break; }
+    case Qt::RoundJoin: {
+        QVarLengthArray<float> points;
+        int count = m_vertices.size();
+        float prevNvx = m_vertices.at(count - 2) - m_cx;
+        float prevNvy = m_vertices.at(count - 1) - m_cy;
+        if (m_nvx * prevNvy - m_nvy * prevNvx < 0) {
+            arcPoints(0, 0, m_nvx, m_nvy, -prevNvx, -prevNvy, points);
+            for (int i = points.size() / 2; i > 0; --i)
+                emitLineSegment(m_cx, m_cy, points[2 * i - 2], points[2 * i - 1]);
+        } else {
+            arcPoints(0, 0, -prevNvx, -prevNvy, m_nvx, m_nvy, points);
+            for (int i = 0; i < points.size() / 2; ++i)
+                emitLineSegment(m_cx, m_cy, points[2 * i + 0], points[2 * i + 1]);
+        }
+        break; }
+    default: break; // gcc warn--
+    }
+
+    emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+}
+
+void QTriangulatingStroker::endCap(const qreal *)
+{
+    switch (m_cap_style) {
+    case Qt::FlatCap:
+        break;
+    case Qt::SquareCap:
+        emitLineSegment(m_cx + m_nvy, m_cy - m_nvx, m_nvx, m_nvy);
+        break;
+    case Qt::RoundCap: {
+        QVarLengthArray<float> points;
+        int count = m_vertices.size();
+        arcPoints(m_cx, m_cy, m_vertices.at(count - 2), m_vertices.at(count - 1), m_vertices.at(count - 4), m_vertices.at(count - 3), points);
+        int front = 0;
+        int end = points.size() / 2;
+        while (front != end) {
+            m_vertices.add(points[2 * end - 2]);
+            m_vertices.add(points[2 * end - 1]);
+            --end;
+            if (front == end)
+                break;
+            m_vertices.add(points[2 * front + 0]);
+            m_vertices.add(points[2 * front + 1]);
+            ++front;
+        }
+        break; }
+    default: break; // to shut gcc up...
+    }
+}
+
+void QTriangulatingStroker::arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points)
+{
+    float dx1 = fromX - cx;
+    float dy1 = fromY - cy;
+    float dx2 = toX - cx;
+    float dy2 = toY - cy;
+
+    // while more than 180 degrees left:
+    while (dx1 * dy2 - dx2 * dy1 < 0) {
+        float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
+        float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
+        dx1 = tmpx;
+        dy1 = tmpy;
+        points.append(cx + dx1);
+        points.append(cy + dy1);
+    }
+
+    // while more than 90 degrees left:
+    while (dx1 * dx2 + dy1 * dy2 < 0) {
+        float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
+        float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
+        dx1 = tmpx;
+        dy1 = tmpy;
+        points.append(cx + dx1);
+        points.append(cy + dy1);
+    }
+
+    // while more than 0 degrees left:
+    while (dx1 * dy2 - dx2 * dy1 > 0) {
+        float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
+        float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
+        dx1 = tmpx;
+        dy1 = tmpy;
+        points.append(cx + dx1);
+        points.append(cy + dy1);
+    }
+
+    // remove last point which was rotated beyond [toX, toY].
+    if (!points.isEmpty())
+        points.resize(points.size() - 2);
+}
 
 static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
 {
@@ -278,12 +490,12 @@
     m_points.reset();
     m_types.reset();
 
-    qreal width = pen.width();
+    qreal width = qpen_widthf(pen);
     if (width == 0)
         width = 1;
 
     m_dash_stroker.setDashPattern(pen.dashPattern());
-    m_dash_stroker.setStrokeWidth(width);
+    m_dash_stroker.setStrokeWidth(pen.isCosmetic() ? width * m_inv_scale : width);
     m_dash_stroker.setMiterLimit(pen.miterLimit());
     qreal curvyness = sqrt(width) * m_inv_scale / 8;
 
@@ -338,3 +550,6 @@
 
     m_dash_stroker.end();
 }
+
+QT_END_NAMESPACE
+