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 ¢er, 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 }