diff --git a/libs/brush/tests/CMakeLists.txt b/libs/brush/tests/CMakeLists.txt index 1458e253b0..7ae6ecbb8a 100644 --- a/libs/brush/tests/CMakeLists.txt +++ b/libs/brush/tests/CMakeLists.txt @@ -1,30 +1,31 @@ ########### next target ############### set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ) endif() macro_add_unittest_definitions() include(ECMAddTests) ecm_add_tests( kis_auto_brush_test.cpp + kis_auto_brush_factory_test.cpp kis_gbr_brush_test.cpp kis_boundary_test.cpp kis_imagepipe_brush_test.cpp NAME_PREFIX "krita-libbrush-" LINK_LIBRARIES kritaimage kritalibbrush Qt5::Test ) diff --git a/libs/brush/tests/kis_auto_brush_factory_test.cpp b/libs/brush/tests/kis_auto_brush_factory_test.cpp new file mode 100644 index 0000000000..0b9930b46b --- /dev/null +++ b/libs/brush/tests/kis_auto_brush_factory_test.cpp @@ -0,0 +1,51 @@ +#include "kis_auto_brush_factory_test.h" + +#include +#include +#include "kis_auto_brush.h" +#include "kis_auto_brush_factory.h" +#include "kis_mask_generator.h" +#include +#include + +void KisAutoBrushFactoryTest::testXMLClone() +{ + // Set up an autobrush. + KisBrushSP brush = new KisAutoBrush(new KisCircleMaskGenerator(20, 0.6, 0.8, 0.4, 3, true), 1.0, + 0.0); + brush->setSpacing(0.15); + brush->setAutoSpacing(true, 0.1); + + // Try to clone the brush by converting to XML and back. + QDomDocument d; + QDomElement e = d.createElement("Brush"); + brush->toXML(d, e); + KisBrushSP clone = KisAutoBrushFactory().getOrCreateBrush(e, false); + + // Test that the clone has the same settings as the original brush. + QCOMPARE(brush->width(), clone->width()); + QCOMPARE(brush->height(), clone->height()); + QCOMPARE(brush->angle(), clone->angle()); + QCOMPARE(brush->spacing(), clone->spacing()); + QCOMPARE(brush->autoSpacingActive(), clone->autoSpacingActive()); + QCOMPARE(brush->autoSpacingCoeff(), clone->autoSpacingCoeff()); + + // Test that the clone draws the same as the original brush. + + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); + KisPaintInformation info(QPointF(100.0, 100.0), 0.5); + KisDabShape shape(0.9, 0.7, 1.0); + KoColor color(Qt::yellow, cs); + + KisFixedPaintDeviceSP fdev1 = new KisFixedPaintDevice(cs); + brush->mask(fdev1, color, shape, info); + QImage res1 = fdev1->convertToQImage(0); + + KisFixedPaintDeviceSP fdev2 = new KisFixedPaintDevice(cs); + clone->mask(fdev2, color, shape, info); + QImage res2 = fdev2->convertToQImage(0); + + QCOMPARE(res1, res2); +} + +QTEST_MAIN(KisAutoBrushFactoryTest) diff --git a/libs/brush/tests/kis_auto_brush_factory_test.h b/libs/brush/tests/kis_auto_brush_factory_test.h new file mode 100644 index 0000000000..f75deee7ca --- /dev/null +++ b/libs/brush/tests/kis_auto_brush_factory_test.h @@ -0,0 +1,13 @@ +#ifndef KIS_AUTO_BRUSH_FACTORY_TEST_H +#define KIS_AUTO_BRUSH_FACTORY_TEST_H + +#include + +class KisAutoBrushFactoryTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testXMLClone(); +}; + +#endif // KIS_AUTO_BRUSH_FACTORY_TEST_H diff --git a/libs/global/kis_algebra_2d.cpp b/libs/global/kis_algebra_2d.cpp index 1ba61509d0..9081b4428a 100644 --- a/libs/global/kis_algebra_2d.cpp +++ b/libs/global/kis_algebra_2d.cpp @@ -1,570 +1,586 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_algebra_2d.h" #include #include #include #include #include #include #include #include +#include #include #define SANITY_CHECKS namespace KisAlgebra2D { void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt) { const int numPoints = poly.size(); for (int i = 0; i < numPoints; i++) { int nextI = i + 1; if (nextI >= numPoints) { nextI = 0; } const QPointF &p0 = poly[i]; const QPointF &p1 = poly[nextI]; QPointF edge = p1 - p0; qreal cross = crossProduct(edge, *pt - p0) / (0.5 * edge.manhattanLength()); if (cross < 1.0 && isInRange(pt->x(), p0.x(), p1.x()) && isInRange(pt->y(), p0.y(), p1.y())) { QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection); QPointF adjustedPoint = *pt + salt; // in case the polygon is self-intersecting, polygon direction // might not help if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { adjustedPoint = *pt - salt; #ifdef SANITY_CHECKS if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { dbgKrita << ppVar(*pt); dbgKrita << ppVar(adjustedPoint); dbgKrita << ppVar(QLineF(p0, p1)); dbgKrita << ppVar(salt); dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill)); dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1))); dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1))); } *pt = adjustedPoint; KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4); #endif /* SANITY_CHECKS */ } } } } QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) { qreal len1 = norm(base1); if (len1 < 1e-5) return pt; qreal sin1 = base1.y() / len1; qreal cos1 = base1.x() / len1; qreal len2 = norm(base2); if (len2 < 1e-5) return QPointF(); qreal sin2 = base2.y() / len2; qreal cos2 = base2.x() / len2; qreal sinD = sin2 * cos1 - cos2 * sin1; qreal cosD = cos1 * cos2 + sin1 * sin2; qreal scaleD = len2 / len1; QPointF result; result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD); result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD); return result; } qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2) { qreal a1 = std::atan2(v1.y(), v1.x()); qreal a2 = std::atan2(v2.y(), v2.x()); return a2 - a1; } +qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle) +{ + if (fuzzyPointCompare(p1, p2)) { + return defaultAngle; + } + + const QVector2D diff(p2 - p1); + return std::atan2(diff.y(), diff.x()); +} + QPainterPath smallArrow() { QPainterPath p; p.moveTo(5, 2); p.lineTo(-3, 8); p.lineTo(-5, 5); p.lineTo( 2, 0); p.lineTo(-5,-5); p.lineTo(-3,-8); p.lineTo( 5,-2); p.arcTo(QRectF(3, -2, 4, 4), 90, -180); return p; } template inline Point ensureInRectImpl(Point pt, const Rect &bounds) { if (pt.x() > bounds.right()) { pt.rx() = bounds.right(); } else if (pt.x() < bounds.left()) { pt.rx() = bounds.left(); } if (pt.y() > bounds.bottom()) { pt.ry() = bounds.bottom(); } else if (pt.y() < bounds.top()) { pt.ry() = bounds.top(); } return pt; } QPoint ensureInRect(QPoint pt, const QRect &bounds) { return ensureInRectImpl(pt, bounds); } QPointF ensureInRect(QPointF pt, const QRectF &bounds) { return ensureInRectImpl(pt, bounds); } bool intersectLineRect(QLineF &line, const QRect rect) { QPointF pt1 = QPointF(), pt2 = QPointF(); QPointF tmp; if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &tmp) != QLineF::NoIntersection) { if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) { pt1 = tmp; } } if (line.intersect(QLineF(rect.topRight(), rect.bottomRight()), &tmp) != QLineF::NoIntersection) { if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &tmp) != QLineF::NoIntersection) { if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (line.intersect(QLineF(rect.bottomLeft(), rect.topLeft()), &tmp) != QLineF::NoIntersection) { if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (pt1.isNull() || pt2.isNull()) return false; // Attempt to retain polarity of end points if ((line.x1() < line.x2()) != (pt1.x() > pt2.x()) || (line.y1() < line.y2()) != (pt1.y() > pt2.y())) { tmp = pt1; pt1 = pt2; pt2 = tmp; } line.setP1(pt1); line.setP2(pt2); return true; } template QVector sampleRectWithPoints(const Rect &rect) { QVector points; Point m1 = 0.5 * (rect.topLeft() + rect.topRight()); Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight()); points << rect.topLeft(); points << m1; points << rect.topRight(); points << 0.5 * (rect.topLeft() + rect.bottomLeft()); points << 0.5 * (m1 + m2); points << 0.5 * (rect.topRight() + rect.bottomRight()); points << rect.bottomLeft(); points << m2; points << rect.bottomRight(); return points; } QVector sampleRectWithPoints(const QRect &rect) { return sampleRectWithPoints(rect); } QVector sampleRectWithPoints(const QRectF &rect) { return sampleRectWithPoints(rect); } template Rect approximateRectFromPointsImpl(const QVector &points) { using namespace boost::accumulators; accumulator_set > accX; accumulator_set > accY; Q_FOREACH (const Point &pt, points) { accX(pt.x()); accY(pt.y()); } Rect resultRect; if (alignPixels) { resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), std::ceil(max(accX)), std::ceil(max(accY))); } else { resultRect.setCoords(min(accX), min(accY), max(accX), max(accY)); } return resultRect; } QRect approximateRectFromPoints(const QVector &points) { return approximateRectFromPointsImpl(points); } QRectF approximateRectFromPoints(const QVector &points) { return approximateRectFromPointsImpl(points); } QRect approximateRectWithPointTransform(const QRect &rect, std::function func) { QVector points = sampleRectWithPoints(rect); using namespace boost::accumulators; accumulator_set > accX; accumulator_set > accY; Q_FOREACH (const QPoint &pt, points) { QPointF dstPt = func(pt); accX(dstPt.x()); accY(dstPt.y()); } QRect resultRect; resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), std::ceil(max(accX)), std::ceil(max(accY))); return resultRect; } QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p) { QVector points; const QLineF cutLine = p.getLine(); points << rc.topLeft(); points << rc.topRight(); points << rc.bottomRight(); points << rc.bottomLeft(); QPointF p1 = points[3]; bool p1Valid = p.pos(p1) >= 0; QVector resultPoints; for (int i = 0; i < 4; i++) { const QPointF p2 = points[i]; const bool p2Valid = p.pos(p2) >= 0; if (p1Valid != p2Valid) { QPointF intersection; cutLine.intersect(QLineF(p1, p2), &intersection); resultPoints << intersection; } if (p2Valid) { resultPoints << p2; } p1 = p2; p1Valid = p2Valid; } return approximateRectFromPoints(resultPoints); } int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2) { int numSolutions = 0; const qreal D = pow2(b) - 4 * a * c; if (D < 0) { return 0; } else if (qFuzzyCompare(D, 0)) { *x1 = -b / (2 * a); numSolutions = 1; } else { const qreal sqrt_D = std::sqrt(D); *x1 = (-b + sqrt_D) / (2 * a); *x2 = (-b - sqrt_D) / (2 * a); numSolutions = 2; } return numSolutions; } QVector intersectTwoCircles(const QPointF ¢er1, qreal r1, const QPointF ¢er2, qreal r2) { QVector points; const QPointF diff = (center2 - center1); const QPointF c1; const QPointF c2 = diff; const qreal centerDistance = norm(diff); if (centerDistance > r1 + r2) return points; if (centerDistance < qAbs(r1 - r2)) return points; if (centerDistance < qAbs(r1 - r2) + 0.001) { qDebug() << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2)); return points; } const qreal x_kp1 = diff.x(); const qreal y_kp1 = diff.y(); const qreal F2 = 0.5 * (pow2(x_kp1) + pow2(y_kp1) + pow2(r1) - pow2(r2)); if (qFuzzyCompare(diff.y(), 0)) { qreal x = F2 / diff.x(); qreal y1, y2; int result = KisAlgebra2D::quadraticEquation( 1, 0, pow2(x) - pow2(r2), &y1, &y2); KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } if (result == 1) { points << QPointF(x, y1); } else if (result == 2) { KisAlgebra2D::RightHalfPlane p(c1, c2); QPointF p1(x, y1); QPointF p2(x, y2); if (p.pos(p1) >= 0) { points << p1; points << p2; } else { points << p2; points << p1; } } } else { const qreal A = diff.x() / diff.y(); const qreal C = F2 / diff.y(); qreal x1, x2; int result = KisAlgebra2D::quadraticEquation( 1 + pow2(A), -2 * A * C, pow2(C) - pow2(r1), &x1, &x2); KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } if (result == 1) { points << QPointF(x1, C - x1 * A); } else if (result == 2) { KisAlgebra2D::RightHalfPlane p(c1, c2); QPointF p1(x1, C - x1 * A); QPointF p2(x2, C - x2 * A); if (p.pos(p1) >= 0) { points << p1; points << p2; } else { points << p2; points << p1; } } } for (int i = 0; i < points.size(); i++) { points[i] = center1 + points[i]; } return points; } QTransform mapToRect(const QRectF &rect) { return QTransform(rect.width(), 0, 0, rect.height(), rect.x(), rect.y()); } bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) { return qAbs(t1.m11() - t2.m11()) < delta && qAbs(t1.m12() - t2.m12()) < delta && qAbs(t1.m13() - t2.m13()) < delta && qAbs(t1.m21() - t2.m21()) < delta && qAbs(t1.m22() - t2.m22()) < delta && qAbs(t1.m23() - t2.m23()) < delta && qAbs(t1.m31() - t2.m31()) < delta && qAbs(t1.m32() - t2.m32()) < delta && qAbs(t1.m33() - t2.m33()) < delta; } +bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2) +{ + return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y()); +} + /********************************************************/ /* DecomposedMatix */ /********************************************************/ DecomposedMatix::DecomposedMatix() { } DecomposedMatix::DecomposedMatix(const QTransform &t0) { QTransform t(t0); QTransform projMatrix; if (t.m33() == 0.0 || t0.determinant() == 0.0) { qWarning() << "Cannot decompose matrix!" << t; valid = false; return; } if (t.type() == QTransform::TxProject) { QTransform affineTransform(t.toAffine()); projMatrix = affineTransform.inverted() * t; t = affineTransform; proj[0] = projMatrix.m13(); proj[1] = projMatrix.m23(); proj[2] = projMatrix.m33(); } std::array rows; rows[0] = QVector3D(t.m11(), t.m12(), t.m13()); rows[1] = QVector3D(t.m21(), t.m22(), t.m23()); rows[2] = QVector3D(t.m31(), t.m32(), t.m33()); if (!qFuzzyCompare(t.m33(), 1.0)) { const qreal invM33 = 1.0 / t.m33(); for (auto row : rows) { row *= invM33; } } dx = rows[2].x(); dy = rows[2].y(); rows[2] = QVector3D(0,0,1); scaleX = rows[0].length(); rows[0] *= 1.0 / scaleX; shearXY = QVector3D::dotProduct(rows[0], rows[1]); rows[1] = rows[1] - shearXY * rows[0]; scaleY = rows[1].length(); rows[1] *= 1.0 / scaleY; shearXY *= 1.0 / scaleY; // If determinant is negative, one axis was flipped. qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x(); if (determinant < 0) { // Flip axis with minimum unit vector dot product. if (rows[0].x() < rows[1].y()) { scaleX = -scaleX; rows[0] = -rows[0]; } else { scaleY = -scaleY; rows[1] = -rows[1]; } shearXY = - shearXY; } angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x())); if (angle != 0.0) { // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)] // = [row0x, -row0y, row0y, row0x] // Thanks to the normalization above. qreal sn = -rows[0].y(); qreal cs = rows[0].x(); qreal m11 = rows[0].x(); qreal m12 = rows[0].y(); qreal m21 = rows[1].x(); qreal m22 = rows[1].y(); rows[0].setX(cs * m11 + sn * m21); rows[0].setY(cs * m12 + sn * m22); rows[1].setX(-sn * m11 + cs * m21); rows[1].setY(-sn * m12 + cs * m22); } QTransform leftOver( rows[0].x(), rows[0].y(), rows[0].z(), rows[1].x(), rows[1].y(), rows[1].z(), rows[2].x(), rows[2].y(), rows[2].z()); KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)); } } diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h index e95cab697b..97e1248b1d 100644 --- a/libs/global/kis_algebra_2d.h +++ b/libs/global/kis_algebra_2d.h @@ -1,569 +1,582 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_ALGEBRA_2D_H #define __KIS_ALGEBRA_2D_H #include #include #include #include #include #include #include #include #include class QPainterPath; class QTransform; namespace KisAlgebra2D { template struct PointTypeTraits { }; template <> struct PointTypeTraits { typedef int value_type; typedef qreal calculation_type; typedef QRect rect_type; }; template <> struct PointTypeTraits { typedef qreal value_type; typedef qreal calculation_type; typedef QRectF rect_type; }; template typename PointTypeTraits::value_type dotProduct(const T &a, const T &b) { return a.x() * b.x() + a.y() * b.y(); } template typename PointTypeTraits::value_type crossProduct(const T &a, const T &b) { return a.x() * b.y() - a.y() * b.x(); } template qreal norm(const T &a) { return std::sqrt(pow2(a.x()) + pow2(a.y())); } template Point normalize(const Point &a) { const qreal length = norm(a); return (1.0 / length) * a; } /** * Usual sign() function with positive zero */ template T signPZ(T x) { return x >= T(0) ? T(1) : T(-1); } /** * Usual sign() function with zero returning zero */ template T signZZ(T x) { return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1); } /** * Copies the sign of \p y into \p x and returns the result */ template inline T copysign(T x, T y) { T strippedX = qAbs(x); return y >= T(0) ? strippedX : -strippedX; } template T leftUnitNormal(const T &a) { T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0); qreal length = norm(result); result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length; return -result; } template T rightUnitNormal(const T &a) { return -leftUnitNormal(a); } template T inwardUnitNormal(const T &a, int polygonDirection) { return polygonDirection * leftUnitNormal(a); } /** * \return 1 if the polygon is counterclockwise * -1 if the polygon is clockwise * * Note: the sign is flipped because our 0y axis * is reversed */ template int polygonDirection(const QVector &polygon) { typename PointTypeTraits::value_type doubleSum = 0; const int numPoints = polygon.size(); for (int i = 1; i <= numPoints; i++) { int prev = i - 1; int next = i == numPoints ? 0 : i; doubleSum += (polygon[next].x() - polygon[prev].x()) * (polygon[next].y() + polygon[prev].y()); } return doubleSum >= 0 ? 1 : -1; } template bool isInRange(T x, T a, T b) { T length = qAbs(a - b); return qAbs(x - a) <= length && qAbs(x - b) <= length; } void KRITAGLOBAL_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt); /** * Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled * and then rotated into \p base2 using transformation matrix S * * R. The function applies the same transformation to \pt and returns * the result. **/ QPointF KRITAGLOBAL_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2); qreal KRITAGLOBAL_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2); +/** + * Computes an angle indicating the direction from p1 to p2. If p1 and p2 are too close together to + * compute an angle, defaultAngle is returned. + */ +qreal KRITAGLOBAL_EXPORT directionBetweenPoints(const QPointF &p1, const QPointF &p2, + qreal defaultAngle); + namespace Private { inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) { *rc = QRect(pt, QSize(1, 1)); } inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) { static const qreal eps = 1e-10; *rc = QRectF(pt, QSizeF(eps, eps)); } } template inline void accumulateBounds(const Point &pt, Rect *bounds) { if (bounds->isEmpty()) { Private::resetEmptyRectangle(pt, bounds); } if (pt.x() > bounds->right()) { bounds->setRight(pt.x()); } if (pt.x() < bounds->left()) { bounds->setLeft(pt.x()); } if (pt.y() > bounds->bottom()) { bounds->setBottom(pt.y()); } if (pt.y() < bounds->top()) { bounds->setTop(pt.y()); } } template