File indexing completed on 2024-05-12 15:57:03

0001 /*
0002  *  SPDX-FileCopyrightText: 2020 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "KisRegion.h"
0007 
0008 #include <QRegion>
0009 #include "kis_debug.h"
0010 
0011 namespace detail {
0012 
0013 struct HorizontalMergePolicy
0014 {
0015     static int col(const QRect &rc) {
0016         return rc.x();
0017     }
0018     static int nextCol(const QRect &rc) {
0019         return rc.x() + rc.width();
0020     }
0021     static int rowHeight(const QRect &rc) {
0022         return rc.height();
0023     }
0024     static bool rowIsLess(const QRect &lhs, const QRect &rhs) {
0025         return lhs.y() < rhs.y();
0026     }
0027     static bool elementIsLess(const QRect &lhs, const QRect &rhs) {
0028         return lhs.y() < rhs.y() || (lhs.y() == rhs.y() && lhs.x() < rhs.x());
0029     }
0030 };
0031 
0032 struct VerticalMergePolicy
0033 {
0034     static int col(const QRect &rc) {
0035         return rc.y();
0036     }
0037     static int nextCol(const QRect &rc) {
0038         return rc.y() + rc.height();
0039     }
0040     static int rowHeight(const QRect &rc) {
0041         return rc.width();
0042     }
0043     static bool rowIsLess(const QRect &lhs, const QRect &rhs) {
0044         return lhs.x() < rhs.x();
0045     }
0046     static bool elementIsLess(const QRect &lhs, const QRect &rhs) {
0047         return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y());
0048     }
0049 };
0050 
0051 template <typename MergePolicy>
0052 QVector<QRect>::iterator mergeRects(QVector<QRect>::iterator beginIt,
0053                                     QVector<QRect>::iterator endIt,
0054                                     MergePolicy policy)
0055 {
0056     if (beginIt == endIt) return endIt;
0057 
0058     std::sort(beginIt, endIt, MergePolicy::elementIsLess);
0059 
0060     auto resultIt = beginIt;
0061     auto it = std::next(beginIt);
0062 
0063     while (it != endIt) {
0064         auto rowEnd = std::upper_bound(it, endIt, *it, MergePolicy::rowIsLess);
0065         for (auto rowIt = it; rowIt != rowEnd; ++rowIt) {
0066             if (policy.rowHeight(*resultIt) == policy.rowHeight(*rowIt) &&
0067                     policy.nextCol(*resultIt) == policy.col(*rowIt)) {
0068                 *resultIt |= *rowIt;
0069             } else {
0070                 resultIt++;
0071                 *resultIt = *rowIt;
0072             }
0073         }
0074 
0075         it = rowEnd;
0076     }
0077 
0078     return std::next(resultIt);
0079 }
0080 
0081 struct VerticalSplitPolicy
0082 {
0083     static int rowStart(const QRect &rc) {
0084         return rc.y();
0085     }
0086     static int rowEnd(const QRect &rc) {
0087         return rc.bottom();
0088     }
0089     static int rowHeight(const QRect &rc) {
0090         return rc.height();
0091     }
0092     static void setRowEnd(QRect &rc, int rowEnd) {
0093         return rc.setBottom(rowEnd);
0094     }
0095     static bool rowIsLess(const QRect &lhs, const QRect &rhs) {
0096         return lhs.y() < rhs.y();
0097     }
0098     static QRect splitRectHi(const QRect &rc, int rowEnd) {
0099         return QRect(rc.x(), rc.y(),
0100                      rc.width(), rowEnd - rc.y() + 1);
0101     }
0102     static QRect splitRectLo(const QRect &rc, int rowEnd) {
0103         return QRect(rc.x(), rowEnd + 1,
0104                      rc.width(), rc.height() - (rowEnd - rc.y() + 1));
0105     }
0106 };
0107 
0108 struct HorizontalSplitPolicy
0109 {
0110     static int rowStart(const QRect &rc) {
0111         return rc.x();
0112     }
0113     static int rowEnd(const QRect &rc) {
0114         return rc.right();
0115     }
0116     static int rowHeight(const QRect &rc) {
0117         return rc.width();
0118     }
0119     static void setRowEnd(QRect &rc, int rowEnd) {
0120         return rc.setRight(rowEnd);
0121     }
0122     static bool rowIsLess(const QRect &lhs, const QRect &rhs) {
0123         return lhs.x() < rhs.x();
0124     }
0125     static QRect splitRectHi(const QRect &rc, int rowEnd) {
0126         return QRect(rc.x(), rc.y(),
0127                      rowEnd - rc.x() + 1, rc.height());
0128     }
0129     static QRect splitRectLo(const QRect &rc, int rowEnd) {
0130         return QRect(rowEnd + 1, rc.y(),
0131                      rc.width() - (rowEnd - rc.x() + 1), rc.height());
0132     }
0133 };
0134 
0135 
0136 struct VoidNoOp {
0137     void operator()() const { };
0138     template<typename P1, typename... Params>
0139     void operator()(P1 p1, Params... parameters) {
0140         Q_UNUSED(p1);
0141         operator()(parameters...);
0142     }
0143 };
0144 
0145 struct MergeRectsOp
0146 {
0147     MergeRectsOp(QVector<QRect> &source, QVector<QRect> &destination)
0148         : m_source(source),
0149           m_destination(destination)
0150     {
0151     }
0152 
0153     void operator()() {
0154         m_destination.append(std::accumulate(m_source.begin(), m_source.end(),
0155                                              QRect(), std::bit_or<QRect>()));
0156         m_source.clear();
0157     }
0158 
0159 private:
0160     QVector<QRect> &m_source;
0161     QVector<QRect> &m_destination;
0162 };
0163 
0164 template <typename Policy, typename RowMergeOp, typename OutIt>
0165 void splitRects(QVector<QRect>::iterator beginIt, QVector<QRect>::iterator endIt,
0166                 OutIt resultIt,
0167                 QVector<QRect> tempBuf[2],
0168                 int gridSize,
0169                  RowMergeOp rowMergeOp)
0170 {
0171     if (beginIt == endIt) return;
0172 
0173     QVector<QRect> &nextRowExtra = tempBuf[0];
0174     QVector<QRect> &nextRowExtraTmp = tempBuf[1];
0175 
0176     std::sort(beginIt, endIt, Policy::rowIsLess);
0177     int rowStart = Policy::rowStart(*beginIt);
0178     int rowEnd = rowStart + gridSize - 1;
0179 
0180     auto it = beginIt;
0181     while (1) {
0182         bool switchToNextRow = false;
0183 
0184         if (it == endIt) {
0185             if (nextRowExtra.isEmpty()) {
0186                 rowMergeOp();
0187                 break;
0188             } else {
0189                 switchToNextRow = true;
0190             }
0191         } else if (Policy::rowStart(*it) > rowEnd) {
0192             switchToNextRow = true;
0193         }
0194 
0195         if (switchToNextRow) {
0196             rowMergeOp();
0197 
0198             if (!nextRowExtra.isEmpty()) {
0199                 rowStart = Policy::rowStart(nextRowExtra.first());
0200                 rowEnd = rowStart + gridSize - 1;
0201 
0202                 for (auto nextIt = nextRowExtra.begin(); nextIt != nextRowExtra.end(); ++nextIt) {
0203                     if (Policy::rowEnd(*nextIt) > rowEnd) {
0204                         nextRowExtraTmp.append(Policy::splitRectLo(*nextIt, rowEnd));
0205                         *resultIt++ = Policy::splitRectHi(*nextIt, rowEnd);
0206                     } else {
0207                         *resultIt++ = *nextIt;
0208                     }
0209                 }
0210                 nextRowExtra.clear();
0211                 std::swap(nextRowExtra, nextRowExtraTmp);
0212 
0213                 continue;
0214             } else {
0215                 rowStart = Policy::rowStart(*it);
0216                 rowEnd = rowStart + gridSize - 1;
0217             }
0218         }
0219 
0220         if (Policy::rowEnd(*it) > rowEnd) {
0221             nextRowExtra.append(Policy::splitRectLo(*it, rowEnd));
0222             *resultIt++ = Policy::splitRectHi(*it, rowEnd);
0223         } else {
0224             *resultIt++ = *it;
0225         }
0226 
0227         ++it;
0228     }
0229 }
0230 
0231 }
0232 
0233 QVector<QRect>::iterator KisRegion::mergeSparseRects(QVector<QRect>::iterator beginIt, QVector<QRect>::iterator endIt)
0234 {
0235     endIt = detail::mergeRects(beginIt, endIt, detail::HorizontalMergePolicy());
0236     endIt = detail::mergeRects(beginIt, endIt, detail::VerticalMergePolicy());
0237     return endIt;
0238 }
0239 
0240 void KisRegion::approximateOverlappingRects(QVector<QRect> &rects, int gridSize)
0241 {
0242     using namespace detail;
0243 
0244     if (rects.isEmpty()) return;
0245 
0246     QVector<QRect> rowsBuf;
0247     QVector<QRect> intermediate;
0248     QVector<QRect> tempBuf[2];
0249 
0250     splitRects<VerticalSplitPolicy>(rects.begin(), rects.end(),
0251                                     std::back_inserter(rowsBuf),
0252                                     tempBuf, gridSize, VoidNoOp());
0253 
0254     rects.clear();
0255     KIS_SAFE_ASSERT_RECOVER_NOOP(tempBuf[0].isEmpty());
0256     KIS_SAFE_ASSERT_RECOVER_NOOP(tempBuf[1].isEmpty());
0257 
0258     auto rowBegin = rowsBuf.begin();
0259     while (rowBegin != rowsBuf.end()) {
0260         auto rowEnd = std::upper_bound(rowBegin, rowsBuf.end(),
0261                                        QRect(rowBegin->x(),
0262                                              rowBegin->y() + gridSize - 1,
0263                                              1,1),
0264                                        VerticalSplitPolicy::rowIsLess);
0265 
0266         splitRects<HorizontalSplitPolicy>(rowBegin, rowEnd,
0267                                           std::back_inserter(intermediate),
0268                                           tempBuf, gridSize,
0269                                           MergeRectsOp(intermediate, rects));
0270         rowBegin = rowEnd;
0271 
0272         KIS_SAFE_ASSERT_RECOVER_NOOP(intermediate.isEmpty());
0273         KIS_SAFE_ASSERT_RECOVER_NOOP(tempBuf[0].isEmpty());
0274         KIS_SAFE_ASSERT_RECOVER_NOOP(tempBuf[1].isEmpty());
0275     }
0276 }
0277 
0278 void KisRegion::makeGridLikeRectsUnique(QVector<QRect> &rects)
0279 {
0280     std::sort(rects.begin(), rects.end(), detail::HorizontalMergePolicy::elementIsLess);
0281     auto it = std::unique(rects.begin(), rects.end());
0282     rects.erase(it, rects.end());
0283 }
0284 
0285 KisRegion::KisRegion(const QRect &rect)
0286 {
0287     m_rects << rect;
0288 }
0289 
0290 KisRegion::KisRegion(std::initializer_list<QRect> rects)
0291     : m_rects(rects)
0292 {
0293 }
0294 
0295 KisRegion::KisRegion(const QVector<QRect> &rects)
0296     : m_rects(rects)
0297 {
0298     mergeAllRects();
0299 }
0300 
0301 KisRegion::KisRegion(QVector<QRect> &&rects)
0302     : m_rects(rects)
0303 {
0304     mergeAllRects();
0305 }
0306 
0307 KisRegion &KisRegion::operator=(const KisRegion &rhs)
0308 {
0309     m_rects = rhs.m_rects;
0310     return *this;
0311 }
0312 
0313 KisRegion &KisRegion::operator&=(const QRect &rect)
0314 {
0315     for (auto it = m_rects.begin(); it != m_rects.end(); /* noop */) {
0316         *it &= rect;
0317         if (it->isEmpty()) {
0318             it = m_rects.erase(it);
0319         } else {
0320             ++it;
0321         }
0322     }
0323     mergeAllRects();
0324     return *this;
0325 }
0326 
0327 QRect KisRegion::boundingRect() const
0328 {
0329     return std::accumulate(m_rects.constBegin(), m_rects.constEnd(), QRect(), std::bit_or<QRect>());
0330 }
0331 
0332 QVector<QRect> KisRegion::rects() const
0333 {
0334     return m_rects;
0335 }
0336 
0337 int KisRegion::rectCount() const
0338 {
0339     return m_rects.size();
0340 }
0341 
0342 bool KisRegion::isEmpty() const
0343 {
0344     return boundingRect().isEmpty();
0345 }
0346 
0347 QRegion KisRegion::toQRegion() const
0348 {
0349     // TODO: ustilize QRegion::setRects to make creation of QRegion much
0350     //       faster. The only reason why we cannot use it "as is", our m_rects
0351     //       do not satisfy the second setRects()'s precondition: "All rectangles with
0352     //       a given top coordinate must have the same height". We can implement an
0353     //       simple algorithm for cropping m_rects, and it will be much faster than
0354     //       constructing QRegion iterationally.
0355 
0356     return std::accumulate(m_rects.constBegin(), m_rects.constEnd(), QRegion(), std::bit_or<QRegion>());
0357 }
0358 
0359 void KisRegion::translate(int dx, int dy)
0360 {
0361     std::transform(m_rects.begin(), m_rects.end(),
0362                    m_rects.begin(),
0363                    [dx, dy] (const QRect &rc) { return rc.translated(dx, dy); });
0364 }
0365 
0366 KisRegion KisRegion::translated(int dx, int dy) const
0367 {
0368     KisRegion region(*this);
0369     region.translate(dx, dy);
0370     return region;
0371 }
0372 
0373 KisRegion KisRegion::fromQRegion(const QRegion &region)
0374 {
0375     KisRegion result;
0376     result.m_rects.clear();
0377     QRegion::const_iterator begin = region.begin();
0378     while (begin != region.end()) {
0379         result.m_rects << *begin;
0380         begin++;
0381     }
0382     return result;
0383 }
0384 
0385 KisRegion KisRegion::fromOverlappingRects(const QVector<QRect> &rects, int gridSize)
0386 {
0387     QVector<QRect> tmp = rects;
0388     approximateOverlappingRects(tmp, gridSize);
0389     return KisRegion(tmp);
0390 }
0391 
0392 void KisRegion::mergeAllRects()
0393 {
0394     auto endIt = mergeSparseRects(m_rects.begin(), m_rects.end());
0395     m_rects.erase(endIt, m_rects.end());
0396 }
0397 
0398 bool operator==(const KisRegion &lhs, const KisRegion &rhs)
0399 {
0400     return lhs.m_rects == rhs.m_rects;
0401 }