/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "oldtessellator.h" #include #include #include #include #include #include #include "limits.h" #include "utils.h" #include "qnum.h" #include "XrenderFake.h" /* * Polygon tesselator - can probably be optimized a bit more */ //#define QT_DEBUG_TESSELATOR #define FloatToXFixed(i) (int)((i) * 65536) #define IntToXFixed(i) ((i) << 16) //inline int qrealToXFixed(qreal f) //{ return f << 8; } struct QEdge { inline QEdge() {} inline QEdge(const QPointF &pt1, const QPointF &pt2) { p1.x = XDoubleToFixed(pt1.x()); p1.y = XDoubleToFixed(pt1.y()); p2.x = XDoubleToFixed(pt2.x()); p2.y = XDoubleToFixed(pt2.y()); m = (pt1.x() - pt2.x()) ? (pt1.y() - pt2.y()) / (pt1.x() - pt2.x()) : 0; im = m ? 1/m : 0; b = pt1.y() - m * pt1.x(); vertical = p1.x == p2.x; horizontal = p1.y == p2.y; } inline qreal xAt(const qreal &y) const { Q_ASSERT(p1.y != p2.y); XFixed yf = XDoubleToFixed(y); if (yf == p1.y) return XFixedToDouble(p1.x); else if (yf == p2.y) return XFixedToDouble(p2.x); return (!vertical) ? (((y - b)*im)) : pf1.x(); } QPointF pf1, pf2; XPointFixed p1, p2; qreal m; qreal im; qreal b; qreal intersection; signed short winding; bool vertical; bool horizontal; }; struct QVrtx { typedef QList Edges; XPointFixed coords; Edges startingEdges; Edges endingEdges; Edges intersectingEdges; }; struct QIntersectionPoint { qreal x; const QEdge *edge; }; QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(QEdge, Q_PRIMITIVE_TYPE); Q_DECLARE_TYPEINFO(QVrtx, Q_PRIMITIVE_TYPE); Q_DECLARE_TYPEINFO(QIntersectionPoint, Q_PRIMITIVE_TYPE); QT_END_NAMESPACE // used by the edge point sort algorithm static qreal currentY = 0.f; static inline bool compareEdges(const QEdge *e1, const QEdge *e2) { return e1->p1.y < e2->p1.y; } static inline bool isEqual(const XPointFixed &p1, const XPointFixed &p2) { return ((p1.x == p2.x) && (p1.y == p2.y)); } static inline bool compareIntersections(const QIntersectionPoint &i1, const QIntersectionPoint &i2) { if (qAbs(i1.x - i2.x) > 0.01) { // x != other.x in 99% of the cases return i1.x < i2.x; } else { qreal x1 = (i1.edge->p1.x != i1.edge->p2.x) ? ((currentY+1 - i1.edge->b)*i1.edge->m) : XFixedToDouble(i1.edge->p1.x); qreal x2 = (i2.edge->p1.x != i2.edge->p2.x) ? ((currentY+1 - i2.edge->b)*i2.edge->m) : XFixedToDouble(i2.edge->p1.x); // qDebug() << ">>>" << currentY << i1.edge << i2.edge << x1 << x2; return x1 < x2; } } #ifdef QT_USE_FIXED_POINT inline int qrealToXFixed(qreal f) { return f.value() << 8; } #else #define qrealToXFixed FloatToXFixed #endif static XTrapezoid QT_FASTCALL toXTrapezoid(XFixed y1, XFixed y2, const QEdge &left, const QEdge &right) { XTrapezoid trap; trap.top = y1; trap.bottom = y2; trap.left.p1.y = left.p1.y; trap.left.p2.y = left.p2.y; trap.right.p1.y = right.p1.y; trap.right.p2.y = right.p2.y; trap.left.p1.x = left.p1.x; trap.left.p2.x = left.p2.x; trap.right.p1.x = right.p1.x; trap.right.p2.x = right.p2.x; return trap; } #ifdef QT_DEBUG_TESSELATOR static void dump_edges(const QList &et) { for (int x = 0; x < et.size(); ++x) { qDebug() << "edge#" << x << et.at(x) << "(" << XFixedToDouble(et.at(x)->p1.x) << XFixedToDouble(et.at(x)->p1.y) << ") (" << XFixedToDouble(et.at(x)->p2.x) << XFixedToDouble(et.at(x)->p2.y) << ") b: " << et.at(x)->b << "m:" << et.at(x)->m; } } static void dump_trap(const XTrapezoid &t) { qDebug() << "trap# t=" << t.top/65536.0 << "b=" << t.bottom/65536.0 << "h=" << XFixedToDouble(t.bottom - t.top) << "\tleft p1: (" << XFixedToDouble(t.left.p1.x) << ","<< XFixedToDouble(t.left.p1.y) << ")" << "\tleft p2: (" << XFixedToDouble(t.left.p2.x) << "," << XFixedToDouble(t.left.p2.y) << ")" << "\n\t\t\t\tright p1:(" << XFixedToDouble(t.right.p1.x) << "," << XFixedToDouble(t.right.p1.y) << ")" << "\tright p2:(" << XFixedToDouble(t.right.p2.x) << "," << XFixedToDouble(t.right.p2.y) << ")"; } #endif typedef int Q27Dot5; #define Q27Dot5ToDouble(i) (i/32.) #define FloatToQ27Dot5(i) (int)((i) * 32) #define IntToQ27Dot5(i) ((i) << 5) #define Q27Dot5ToXFixed(i) ((i) << 11) #define Q27Dot5Factor 32 void old_tesselate_polygon(QVector *traps, const QPointF *pg, int pgSize, bool winding) { QVector edges; edges.reserve(128); qreal ymin(INT_MAX/256); qreal ymax(INT_MIN/256); //painter.begin(pg, pgSize); Q_ASSERT(pg[0] == pg[pgSize-1]); // generate edge table // qDebug() << "POINTS:"; for (int x = 0; x < pgSize-1; ++x) { QEdge edge; QPointF p1(Q27Dot5ToDouble(FloatToQ27Dot5(pg[x].x())), Q27Dot5ToDouble(FloatToQ27Dot5(pg[x].y()))); QPointF p2(Q27Dot5ToDouble(FloatToQ27Dot5(pg[x+1].x())), Q27Dot5ToDouble(FloatToQ27Dot5(pg[x+1].y()))); // qDebug() << " " // << p1; edge.winding = p1.y() > p2.y() ? 1 : -1; if (edge.winding > 0) qSwap(p1, p2); edge.p1.x = XDoubleToFixed(p1.x()); edge.p1.y = XDoubleToFixed(p1.y()); edge.p2.x = XDoubleToFixed(p2.x()); edge.p2.y = XDoubleToFixed(p2.y()); edge.m = (p1.y() - p2.y()) / (p1.x() - p2.x()); // line derivative edge.b = p1.y() - edge.m * p1.x(); // intersection with y axis edge.m = edge.m != 0.0 ? 1.0 / edge.m : 0.0; // inverted derivative edges.append(edge); ymin = qMin(ymin, qreal(XFixedToDouble(edge.p1.y))); ymax = qMax(ymax, qreal(XFixedToDouble(edge.p2.y))); } QList et; // edge list for (int i = 0; i < edges.size(); ++i) et.append(&edges.at(i)); // sort edge table by min y value qSort(et.begin(), et.end(), compareEdges); // eliminate shared edges for (int i = 0; i < et.size(); ++i) { for (int k = i+1; k < et.size(); ++k) { const QEdge *edgeI = et.at(i); const QEdge *edgeK = et.at(k); if (edgeK->p1.y > edgeI->p1.y) break; if (edgeI->winding != edgeK->winding && isEqual(edgeI->p1, edgeK->p1) && isEqual(edgeI->p2, edgeK->p2) ) { et.removeAt(k); et.removeAt(i); --i; break; } } } if (ymax <= ymin) return; QList aet; // edges that intersects the current scanline // if (ymin < 0) // ymin = 0; // if (paintEventClipRegion) // don't scan more lines than we have to // ymax = paintEventClipRegion->boundingRect().height(); #ifdef QT_DEBUG_TESSELATOR qDebug("==> ymin = %f, ymax = %f", ymin, ymax); #endif // QT_DEBUG_TESSELATOR currentY = ymin; // used by the less than op for (qreal y = ymin; y < ymax;) { // fill active edge table with edges that intersect the current line for (int i = 0; i < et.size(); ++i) { const QEdge *edge = et.at(i); if (edge->p1.y > XDoubleToFixed(y)) break; aet.append(edge); et.removeAt(i); --i; } // remove processed edges from active edge table for (int i = 0; i < aet.size(); ++i) { if (aet.at(i)->p2.y <= XDoubleToFixed(y)) { aet.removeAt(i); --i; } } if (aet.size()%2 != 0) { #ifndef QT_NO_DEBUG qWarning("QX11PaintEngine: aet out of sync - this should not happen."); #endif return; } // done? if (!aet.size()) { if (!et.size()) { break; } else { y = currentY = XFixedToDouble(et.at(0)->p1.y); continue; } } // calculate the next y where we have to start a new set of trapezoids qreal next_y(INT_MAX/256); for (int i = 0; i < aet.size(); ++i) { const QEdge *edge = aet.at(i); if (XFixedToDouble(edge->p2.y) < next_y) next_y = XFixedToDouble(edge->p2.y); } if (et.size() && next_y > XFixedToDouble(et.at(0)->p1.y)) next_y = XFixedToDouble(et.at(0)->p1.y); int aetSize = aet.size(); for (int i = 0; i < aetSize; ++i) { for (int k = i+1; k < aetSize; ++k) { const QEdge *edgeI = aet.at(i); const QEdge *edgeK = aet.at(k); qreal m1 = edgeI->m; qreal b1 = edgeI->b; qreal m2 = edgeK->m; qreal b2 = edgeK->b; if (qAbs(m1 - m2) < 0.001) continue; // ### intersect is not calculated correctly when optimized with -O2 (gcc) volatile qreal intersect = 0; if (!qIsFinite(b1)) intersect = (1.f / m2) * XFixedToDouble(edgeI->p1.x) + b2; else if (!qIsFinite(b2)) intersect = (1.f / m1) * XFixedToDouble(edgeK->p1.x) + b1; else intersect = (b1*m1 - b2*m2) / (m1 - m2); if (intersect > y && intersect < next_y) next_y = intersect; } } XFixed yf, next_yf; yf = qrealToXFixed(y); next_yf = qrealToXFixed(next_y); if (yf == next_yf) { y = currentY = next_y; continue; } #ifdef QT_DEBUG_TESSELATOR qDebug("###> y = %f, next_y = %f, %d active edges", y, next_y, aet.size()); qDebug("===> edges"); dump_edges(et); qDebug("===> active edges"); dump_edges(aet); #endif // calc intersection points QVarLengthArray isects(aet.size()+1); for (int i = 0; i < isects.size()-1; ++i) { const QEdge *edge = aet.at(i); isects[i].x = (edge->p1.x != edge->p2.x) ? ((y - edge->b)*edge->m) : XFixedToDouble(edge->p1.x); isects[i].edge = edge; } Q_ASSERT(isects.size()%2 == 1); // sort intersection points qSort(&isects[0], &isects[isects.size()-1], compareIntersections); // qDebug() << "INTERSECTION_POINTS:"; // for (int i = 0; i < isects.size(); ++i) // qDebug() << isects[i].edge << isects[i].x; if (winding) { // winding fill rule for (int i = 0; i < isects.size()-1;) { int winding = 0; const QEdge *left = isects[i].edge; const QEdge *right = 0; winding += isects[i].edge->winding; for (++i; i < isects.size()-1 && winding != 0; ++i) { winding += isects[i].edge->winding; right = isects[i].edge; } if (!left || !right) break; //painter.addTrapezoid(&toXTrapezoid(yf, next_yf, *left, *right)); traps->append(toXTrapezoid(yf, next_yf, *left, *right)); } } else { // odd-even fill rule for (int i = 0; i < isects.size()-2; i += 2) { //painter.addTrapezoid(&toXTrapezoid(yf, next_yf, *isects[i].edge, *isects[i+1].edge)); traps->append(toXTrapezoid(yf, next_yf, *isects[i].edge, *isects[i+1].edge)); } } y = currentY = next_y; } #ifdef QT_DEBUG_TESSELATOR qDebug("==> number of trapezoids: %d - edge table size: %d\n", traps->size(), et.size()); for (int i = 0; i < traps->size(); ++i) dump_trap(traps->at(i)); #endif // optimize by unifying trapezoids that share left/right lines // and have a common top/bottom edge // for (int i = 0; i < tps.size(); ++i) { // for (int k = i+1; k < tps.size(); ++k) { // if (i != k && tps.at(i).right == tps.at(k).right // && tps.at(i).left == tps.at(k).left // && (tps.at(i).top == tps.at(k).bottom // || tps.at(i).bottom == tps.at(k).top)) // { // tps[i].bottom = tps.at(k).bottom; // tps.removeAt(k); // i = 0; // break; // } // } // } //static int i = 0; //QImage img = painter.end(); //img.save(QString("res%1.png").arg(i++), "PNG"); }