LCOV - code coverage report
Current view: top level - src/engraving/infrastructure - skyline.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 66.3 % 163 108
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2023 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "skyline.h"
      24              : 
      25              : #include "realfn.h"
      26              : #include "draw/painter.h"
      27              : 
      28              : #include "../dom/arpeggio.h"
      29              : #include "../dom/beam.h"
      30              : #include "../dom/chord.h"
      31              : #include "../dom/stem.h"
      32              : #include "../dom/tremolotwochord.h"
      33              : 
      34              : #include "shape.h"
      35              : 
      36              : using namespace mu;
      37              : using namespace muse::draw;
      38              : 
      39              : namespace mu::engraving {
      40       452878 : void Skyline::add(const ShapeElement& r)
      41              : {
      42       452878 :     if (r.ignoreForLayout()) {
      43           35 :         return;
      44              :     }
      45       452843 :     const EngravingItem* item = r.item();
      46       452843 :     bool crossSouth = false;
      47       452843 :     bool crossNorth = false;
      48       452843 :     if (item && item->isStem()) {
      49        43867 :         Chord* chord = toStem(item)->chord();
      50        43867 :         if (chord) {
      51        43867 :             Beam* beam = chord->beam();
      52        43867 :             TremoloTwoChord* tremolo = chord->tremoloTwoChord();
      53        14577 :             bool isCross = (beam && beam->cross())
      54        58444 :                            || (tremolo && tremolo->chord1()->staffMove() != tremolo->chord2()->staffMove());
      55        43867 :             if (isCross) {
      56           35 :                 std::vector<ChordRest*> elements;
      57           35 :                 if (beam) {
      58           35 :                     elements = beam->elements();
      59            0 :                 } else if (tremolo) {
      60            0 :                     elements = { tremolo->chord1(), tremolo->chord2() };
      61              :                 }
      62           35 :                 int thisStaffMove = chord->staffMove();
      63           35 :                 if (thisStaffMove < 0) {
      64            7 :                     crossNorth = true;
      65           28 :                 } else if (thisStaffMove > 0) {
      66           11 :                     crossSouth = true;
      67              :                 }
      68          162 :                 for (ChordRest* element : elements) {
      69          127 :                     int staffMove = element->staffMove();
      70          127 :                     if (staffMove < thisStaffMove) {
      71           31 :                         crossNorth = true;
      72              :                     }
      73          127 :                     if (staffMove > thisStaffMove) {
      74           31 :                         crossSouth = true;
      75              :                     }
      76              :                 }
      77           35 :             }
      78              :         }
      79              :     }
      80       452843 :     if (item && (item->isArpeggio() || item->isChordBracket())) {
      81          311 :         const Arpeggio* arpeggio = toArpeggio(item);
      82          311 :         if (arpeggio->crossStaff()) {
      83           52 :             crossSouth = true;
      84              :         }
      85              :     }
      86       452843 :     if (!crossNorth) {
      87       452819 :         _north.add(r);
      88              :     }
      89       452843 :     if (!crossSouth) {
      90       452762 :         _south.add(r);
      91              :     }
      92              : }
      93              : 
      94        22392 : void SkylineLine::add(const Shape& s)
      95              : {
      96       106027 :     for (const ShapeElement& se : s.elements()) {
      97        83635 :         add(se);
      98              :     }
      99        22392 : }
     100              : 
     101        18088 : SkylineLine SkylineLine::getFilteredCopy(std::function<bool(const ShapeElement&)> filterOut) const
     102              : {
     103        18088 :     SkylineLine newSkylineLine(*this);
     104              : 
     105        18088 :     newSkylineLine.m_shape.clear();
     106              : 
     107       470013 :     for (const ShapeElement& shapeEl : m_shape.elements()) {
     108       451925 :         if (filterOut(shapeEl)) {
     109        41134 :             continue;
     110              :         }
     111       410791 :         newSkylineLine.m_shape.add(shapeEl);
     112              :     }
     113              : 
     114        18088 :     return newSkylineLine;
     115            0 : }
     116              : 
     117       989576 : void SkylineLine::add(const ShapeElement& r)
     118              : {
     119       989576 :     if (r.ignoreForLayout()) {
     120            0 :         return;
     121              :     }
     122              : 
     123       989576 :     double x = r.x();
     124       989576 :     double top = r.top();
     125       989576 :     double bottom = r.bottom();
     126       989576 :     double staffLinesTop = staffLinesTopAtX(x);
     127       989576 :     double staffLinesBottom = staffLinesBottomAtX(x);
     128              : 
     129       989576 :     if (r.item() && r.item()->isStaffLines()) {
     130        93644 :         if (!(muse::RealIsEqual(top, staffLinesTop) && muse::RealIsEqual(bottom, staffLinesBottom))) {
     131        22384 :             m_staffLineEdges.emplace(round(x), StaffLineEdge(top, bottom));
     132              :         }
     133       895932 :     } else if (hasValidStaffLineEdges()) {
     134       872406 :         if (m_isNorth && muse::RealIsEqualOrMore(top, staffLinesTop)) {
     135       318699 :             return; // Only add if it pokes above the staff
     136              :         }
     137       553707 :         if (!m_isNorth && muse::RealIsEqualOrLess(bottom, staffLinesBottom)) {
     138       297839 :             return; // Only add if it pokes below the staff
     139              :         }
     140              :     }
     141              : 
     142       373038 :     m_shape.add(r);
     143              : }
     144              : 
     145       989576 : double SkylineLine::staffLinesTopAtX(double x) const
     146              : {
     147       989576 :     if (m_staffLineEdges.empty()) {
     148        45910 :         return 0.0;
     149              :     }
     150              : 
     151       943666 :     auto upperBound = m_staffLineEdges.upper_bound(std::round(x)); // iterator to first element in map with key larger than x
     152       943666 :     if (upperBound != m_staffLineEdges.begin()) {
     153       940246 :         --upperBound; // back by one (i.e. get last element with key smaller than x)
     154              :     }
     155              : 
     156       943666 :     return (*upperBound).second.top;
     157              : }
     158              : 
     159       989576 : double SkylineLine::staffLinesBottomAtX(double x) const
     160              : {
     161       989576 :     if (m_staffLineEdges.empty()) {
     162        45910 :         return 0.0;
     163              :     }
     164              : 
     165       943666 :     auto upperBound = m_staffLineEdges.upper_bound(std::round(x)); // iterator to first element in map with key larger than x
     166       943666 :     if (upperBound != m_staffLineEdges.begin()) {
     167       940246 :         --upperBound; // back by one (i.e. get last element with key smaller than x)
     168              :     }
     169              : 
     170       943666 :     return (*upperBound).second.bottom;
     171              : }
     172              : 
     173       183744 : void Skyline::add(const Shape& s)
     174              : {
     175       585565 :     for (const auto& r : s.elements()) {
     176       401821 :         add(r);
     177              :     }
     178       183744 : }
     179              : 
     180        22221 : void Skyline::clear()
     181              : {
     182        22221 :     _north.clear();
     183        22221 :     _south.clear();
     184        22221 : }
     185              : 
     186        44442 : void SkylineLine::clear()
     187              : {
     188        44442 :     m_staffLineEdges.clear();
     189        44442 :     m_shape.clear();
     190        44442 : }
     191              : 
     192              : //-------------------------------------------------------------------
     193              : //   minDistance
     194              : //    a is located below this skyline.
     195              : //    Calculates the minimum distance between two skylines
     196              : //-------------------------------------------------------------------
     197              : 
     198        10869 : double Skyline::minDistance(const Skyline& s, double minHorizontalClearance) const
     199              : {
     200        10869 :     return south().minDistance(s.north(), minHorizontalClearance);
     201              : }
     202              : 
     203        11761 : double SkylineLine::minDistance(const SkylineLine& sl, double minHorizontalClearance) const
     204              : {
     205        11761 :     return m_shape.minVerticalDistance(sl.m_shape, minHorizontalClearance);
     206              : }
     207              : 
     208        13153 : double SkylineLine::minDistanceToShapeAbove(const Shape& shapeAbove, double minHorizontalClearance) const
     209              : {
     210        13153 :     return shapeAbove.minVerticalDistance(m_shape, minHorizontalClearance);
     211              : }
     212              : 
     213         3105 : double SkylineLine::minDistanceToShapeBelow(const Shape& shapeBelow, double minHorizontalClearance) const
     214              : {
     215         3105 :     return m_shape.minVerticalDistance(shapeBelow, minHorizontalClearance);
     216              : }
     217              : 
     218          257 : double SkylineLine::verticalClearanceAbove(const Shape& shapeAbove) const
     219              : {
     220          257 :     return shapeAbove.verticalClearance(m_shape);
     221              : }
     222              : 
     223          257 : double SkylineLine::verticalClearanceBelow(const Shape& shapeBelow) const
     224              : {
     225          257 :     return m_shape.verticalClearance(shapeBelow);
     226              : }
     227              : 
     228            0 : void Skyline::paint(Painter& painter, double lineWidth) const // DEBUG only
     229              : {
     230            0 :     painter.save();
     231              : 
     232            0 :     painter.setBrush(BrushStyle::NoBrush);
     233              : 
     234            0 :     for (const ShapeElement& northShapeEl : _north.elements()) {
     235            0 :         painter.setPen(Pen(Color(144, 238, 144), lineWidth));
     236            0 :         for (const ShapeElement& southShapeEl : _south.elements()) {
     237            0 :             if (northShapeEl.item() == southShapeEl.item()) {
     238              :                 // Paint in different color items that belong to both north and south skyline
     239            0 :                 painter.setPen(Pen(Color(144, 191, 191), lineWidth));
     240            0 :                 break;
     241              :             }
     242              :         }
     243            0 :         painter.drawRect(northShapeEl);
     244              :     }
     245              : 
     246            0 :     painter.setPen(Pen(Color(144, 144, 238), lineWidth));
     247            0 :     for (const ShapeElement& southShapeEl : _south.elements()) {
     248            0 :         bool alreadyPainted = false;
     249            0 :         for (const ShapeElement& northShapeEl : _north.elements()) {
     250            0 :             if (northShapeEl.item() == southShapeEl.item()) {
     251            0 :                 alreadyPainted = true;
     252            0 :                 break;
     253              :             }
     254              :         }
     255            0 :         if (!alreadyPainted) {
     256            0 :             painter.drawRect(southShapeEl);
     257              :         }
     258              :     }
     259              : 
     260            0 :     painter.restore();
     261            0 : }
     262              : 
     263            0 : bool SkylineLine::valid() const
     264              : {
     265            0 :     return !m_shape.empty();
     266              : }
     267              : 
     268          129 : SkylineLine& SkylineLine::translateY(double y)
     269              : {
     270          129 :     m_shape.translateY(y);
     271          129 :     return *this;
     272              : }
     273              : 
     274            0 : double SkylineLine::top(double startX, double endX)
     275              : {
     276            0 :     double top = DBL_MAX;
     277            0 :     for (const ShapeElement& element : m_shape.elements()) {
     278            0 :         if (element.right() > startX && element.left() < endX) {
     279            0 :             top = std::min(top, element.top());
     280              :         }
     281              :     }
     282              : 
     283            0 :     if (top == DBL_MAX) {
     284            0 :         top = 0.0;
     285              :     }
     286              : 
     287            0 :     return top;
     288              : }
     289              : 
     290            0 : double SkylineLine::bottom(double startX, double endX)
     291              : {
     292            0 :     double bottom = -DBL_MAX;
     293            0 :     for (const ShapeElement& element : m_shape.elements()) {
     294            0 :         if (element.right() > startX && element.left() < endX) {
     295            0 :             bottom = std::max(bottom, element.bottom());
     296              :         }
     297              :     }
     298              : 
     299            0 :     if (bottom == -DBL_MAX) {
     300            0 :         bottom = 0.0;
     301              :     }
     302              : 
     303            0 :     return bottom;
     304              : }
     305              : 
     306        32743 : double SkylineLine::max() const
     307              : {
     308        32743 :     return m_isNorth ? m_shape.top() : m_shape.bottom();
     309              : }
     310              : } // namespace mu::engraving
     311              : 
     312            0 : std::string mu::engraving::dump(const Skyline& skyline)
     313              : {
     314            0 :     std::stringstream ss;
     315            0 :     ss << "Skyline dump.\n";
     316            0 :     dump(skyline.north(), ss);
     317            0 :     dump(skyline.south(), ss);
     318            0 :     return ss.str();
     319            0 : }
     320              : 
     321            0 : void mu::engraving::dump(const SkylineLine& skylineLine, std::stringstream& ss)
     322              : {
     323            0 :     ss << "North: " << skylineLine.isNorth();
     324            0 :     for (const ShapeElement& se : skylineLine.elements()) {
     325            0 :         dump(se, ss);
     326              :     }
     327            0 : }
        

Generated by: LCOV version 2.0-1