File indexing completed on 2025-12-14 05:01:12

0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 // SPDX-FileCopyrightText: 2018 Kevin Ottens <ervin@kde.org>
0003 
0004 #include "stroke.h"
0005 
0006 #include <QVector2D>
0007 
0008 bool StrokeSample::operator==(const StrokeSample &other) const
0009 {
0010     return position == other.position && qFuzzyCompare(width, other.width);
0011 }
0012 
0013 Stroke::Type Stroke::type() const
0014 {
0015     return m_type;
0016 }
0017 
0018 void Stroke::setType(Stroke::Type type)
0019 {
0020     m_type = type;
0021 }
0022 
0023 QColor Stroke::color() const
0024 {
0025     return m_color;
0026 }
0027 
0028 void Stroke::setColor(const QColor &color)
0029 {
0030     m_color = color;
0031 }
0032 
0033 QVector<StrokeSample> Stroke::samples() const
0034 {
0035     return m_samples;
0036 }
0037 
0038 void Stroke::addSample(const StrokeSample &sample)
0039 {
0040     m_samples << sample;
0041     m_boundingRectCache = QRectF{};
0042 }
0043 
0044 void Stroke::addSamples(const QVector<StrokeSample> &samples)
0045 {
0046     m_samples += samples;
0047     m_boundingRectCache = QRectF{};
0048 }
0049 
0050 QVector<Stroke> Stroke::eraseArea(const QVector2D &center, float radius)
0051 {
0052     const auto areaBoundingRect = QRectF{(center - QVector2D{radius, radius}).toPointF(), (center + QVector2D{radius, radius}).toPointF()};
0053     auto result = QVector<Stroke>{};
0054 
0055     // For sure won't get any hits
0056     if (!boundingRect().intersects(areaBoundingRect)) {
0057         result.append(*this);
0058         return result;
0059     }
0060 
0061     const auto isHit = [=](const StrokeSample &sample) {
0062         return QVector2D{sample.position - center}.length() <= radius;
0063     };
0064 
0065     auto it = m_samples.cbegin();
0066     const auto end = m_samples.cend();
0067 
0068     while (it != end) {
0069         auto previous = it;
0070         // Find first hit
0071         it = std::find_if(it, end, isHit);
0072 
0073         // Copy [previous, it) in a new stroke if there's more than one sample
0074         const auto distance = static_cast<int>(std::distance(previous, it));
0075         if (distance >= 2) {
0076             auto stroke = *this;
0077             stroke.m_samples.clear();
0078             stroke.m_samples.reserve(distance);
0079             std::copy(previous, it, std::back_inserter(stroke.m_samples));
0080 
0081             result.append(stroke);
0082         }
0083 
0084         // Jump to next non-hit
0085         it = std::find_if_not(it, end, isHit);
0086     }
0087 
0088     return result;
0089 }
0090 
0091 QRectF Stroke::boundingRect() const
0092 {
0093     if (!m_boundingRectCache.isValid()) {
0094         auto minX = 0.0f;
0095         auto minY = 0.0f;
0096         auto maxX = 0.0f;
0097         auto maxY = 0.0f;
0098 
0099         for (const auto &sample : std::as_const(m_samples)) {
0100             const auto position = sample.position;
0101             if (position.x() < minX)
0102                 minX = position.x();
0103             else if (position.x() > maxX)
0104                 maxX = position.x();
0105 
0106             if (position.y() < minY)
0107                 minY = position.y();
0108             else if (position.y() > maxY)
0109                 maxY = position.y();
0110         }
0111 
0112         m_boundingRectCache = {QPointF{static_cast<qreal>(minX), static_cast<qreal>(minY)}, QPointF{static_cast<qreal>(maxX), static_cast<qreal>(maxY)}};
0113     }
0114 
0115     return m_boundingRectCache;
0116 }