File indexing completed on 2024-07-14 07:17:23

0001 /*
0002     SPDX-FileCopyrightText: 2007 James B. Bowlin <bowlin@mindspring.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #pragma once
0007 
0008 #include "skylabel.h"
0009 
0010 #include <QFontMetricsF>
0011 #include <QList>
0012 #include <QVector>
0013 #include <QPainter>
0014 #include <QPicture>
0015 #include <QFont>
0016 
0017 class QString;
0018 class QPointF;
0019 class SkyMap;
0020 class Projector;
0021 struct LabelRun;
0022 
0023 typedef QList<LabelRun *> LabelRow;
0024 typedef QVector<LabelRow *> ScreenRows;
0025 
0026 /**
0027  *@class SkyLabeler
0028  * The purpose of this class is to prevent labels from overlapping.  We do this
0029  * by creating a virtual (lower Y-resolution) screen. Each "pixel" of this
0030  * screen essentially contains a boolean value telling us whether or not there
0031  * is an existing label covering at least part of that pixel.  Before you draw
0032  * a label, call mark( QPointF, QString ) of that label.  We will check to see
0033  * if it would overlap any existing label.  If there is overlap we return false.
0034  * If there is no overlap then we mark all the pixels that cover the new label
0035  * and return true.
0036  *
0037  * Since we need to check for overlap for every label every time it is
0038  * potentially drawn on the screen, efficiency is essential.  So instead of
0039  * having a 2-dimensional array of boolean values we use Run Length Encoding
0040  * and store the virtual array in a QVector of QLists.  Each element of the
0041  * vector, a LabelRow, corresponds to a horizontal strip of pixels on the actual
0042  * screen.  How many vertical pixels are in each strip is controlled by
0043  * m_yDensity.  The higher the density, the fewer vertical pixels per strip and
0044  * hence a larger number of strips are needed to cover the screen.  As
0045  * m_yDensity increases so does the density of the strips.
0046  *
0047  * The information in the X-dimension is completed run length encoded. A
0048  * consecutive run of pixels in one strip that are covered by one or more labels
0049  * is stored in a LabelRun object that merely stores the start pixel and the end
0050  * pixel.  A LabelRow is a list of LabelRun's stored in ascending order.  This
0051  * saves a lot of space over an explicit array and it also makes checking for
0052  * overlaps faster and even makes inserting new overlaps faster on average.
0053  *
0054  * Synopsis:
0055  *
0056  *   1) Create a new SkyLabeler
0057  *
0058  *   2) every time you want to draw a new screen, reset the labeler.
0059  *
0060  *   3) Either:
0061  *
0062  *   A) Call drawLabel() or drawOffsetLabel(), or
0063  *
0064  *   B) Call addLabel() or addOffsetLabel()
0065  *
0066  *  4) Call draw() if addLabel() or addOffsetLabel() were used.
0067  *
0068  *
0069  *
0070  * SkyLabeler works totally on a first come, first served basis which is almost
0071  * the direct opposite of a z-buffer where the objects drawn last are most
0072  * visible.  This is why the addLabel() and draw() routines were created.
0073  * They allow us to time-shift the drawing of labels and thus gives us control
0074  * over their priority.  The drawLabel() routines are still available but are
0075  * not being used.  The addLabel() routines adds a label to a specific buffer.
0076  * Each type of label has its own buffer which lets us control the font and
0077  * color as well as the priority.  The priority is now manually set in the
0078  * draw() routine by adjusting the order in which the various buffers get
0079  * drawn.
0080  *
0081  * Finally, even though this code was written to be very efficient, we might
0082  * want to take some care in how many labels we throw at it.  Sending it
0083  * a large number of overlapping labels can be wasteful. Also, if one type
0084  * of object floods it with labels early on then there may not be any room
0085  * left for other types of labels.  Therefore for some types of objects (stars)
0086  * we may want to have a zoom dependent label threshold magnitude just like
0087  * we have for drawing the stars themselves.  This would throw few labels
0088  * at the labeler when we are zoomed at and they would mostly overlap anyway
0089  * and it would give us more labels when the user is zoomed in and there
0090  * is more room for them.  The "b" key currently causes the labeler statistics
0091  * to be printed.  This may be useful in figuring out the best settings for
0092  * the magnitude limits.  It may even be possible to have KStars do some of
0093  * this throttling automatically but I haven't really thought about that
0094  * problem yet.
0095  *
0096  * -- James B. Bowlin  2007-08-02
0097  */
0098 class SkyLabeler
0099 {
0100   protected:
0101     SkyLabeler();
0102     SkyLabeler(SkyLabeler &skyLabler);
0103 
0104   public:
0105     enum label_t
0106     {
0107         STAR_LABEL,
0108         ASTEROID_LABEL,
0109         COMET_LABEL,
0110         PLANET_LABEL,
0111         JUPITER_MOON_LABEL,
0112         SATURN_MOON_LABEL,
0113         DEEP_SKY_LABEL,
0114         CONSTEL_NAME_LABEL,
0115         SATELLITE_LABEL,
0116         RUDE_LABEL, ///Rude labels block other labels FIXME: find a better solution
0117         NUM_LABEL_TYPES
0118     };
0119 
0120     //----- Static Methods ----------------------------------------------//
0121 
0122     static SkyLabeler *Instance();
0123 
0124     /**
0125          * @short returns the zoom dependent label offset.  This is used in this
0126          * class and in SkyObject.  It is important that the offsets be the same
0127          * so highlighted labels are always drawn exactly on top of the normally
0128          * drawn labels.
0129          */
0130     static double ZoomOffset();
0131 
0132     /**
0133          * @short static version of addLabel() below.
0134          */
0135     inline static void AddLabel(SkyObject *obj, label_t type) { pinstance->addLabel(obj, type); }
0136 
0137     //--------------------------------------------------------------------//
0138     ~SkyLabeler();
0139 
0140     /**
0141          * @short clears the virtual screen (if needed) and resizes the virtual
0142          * screen (if needed) to match skyMap.  A font must be specified which
0143          * is taken to be the average or normal font that will be used.  The
0144          * size of the horizontal strips will be (slightly) optimized for this
0145          * font.  We also adjust the font size in psky to smaller fonts if the
0146          * screen is zoomed out.  You can mimic this setting with the static
0147          * method SkyLabeler::setZoomFont( psky ).
0148          */
0149     void reset(SkyMap *skyMap);
0150 
0151 /**
0152          * @short KStars Lite version of the function above
0153          */
0154 #ifdef KSTARS_LITE
0155     void reset();
0156 #endif
0157 
0158     /**
0159          * @short Draws labels using the given painter
0160          * @param p the painter to draw labels with
0161          */
0162     void draw(QPainter &p);
0163 
0164     //----- Font Setting -----//
0165 
0166     /**
0167          * @short adjusts the font in psky to be smaller if we are zoomed out.
0168          */
0169     void setZoomFont();
0170 
0171     /**
0172          * @short sets the pen used for drawing labels on the sky.
0173          */
0174     void setPen(const QPen &pen);
0175 
0176     /**
0177          * @short tells the labeler the font you will be using so it can figure
0178          * out the height and width of the labels.  Also sets this font in the
0179          * psky since this is almost always what is wanted.
0180          */
0181     void setFont(const QFont &font);
0182 
0183     /**
0184          * @short decreases the size of the font in psky and in the SkyLabeler
0185          * by the delta points.  Negative deltas will increase the font size.
0186          */
0187     void shrinkFont(int delta);
0188 
0189     /**
0190          * @short sets the font in SkyLabeler and in psky to the font psky
0191          * had originally when reset() was called.  Used by ConstellationNames.
0192          */
0193     void useStdFont();
0194 
0195     /**
0196          * @short sets the font in SkyLabeler and in psky back to the zoom
0197          * dependent value that was set in reset().  Also used in
0198          * ConstellationLines.
0199          */
0200     void resetFont();
0201 
0202     /**
0203          * @short returns the fontMetricsF we have already created.
0204          */
0205     QFontMetricsF &fontMetrics() { return m_fontMetrics; }
0206 
0207     //----- Drawing/Adding Labels -----//
0208 
0209     /**
0210          *@short sets four margins for help in keeping labels entirely on the
0211          * screen.
0212          */
0213     void getMargins(const QString &text, float *left, float *right, float *top, float *bot);
0214 
0215     /**
0216          * @short Tries to draw the text at the position and angle specified. If
0217          * the label would overlap an existing label it is not drawn and we
0218          * return false, otherwise the label is drawn, its position is marked
0219          * and we return true.
0220          */
0221     bool drawGuideLabel(QPointF &o, const QString &text, double angle);
0222 
0223     /**
0224          * @short Tries to draw a label for an object.
0225          * @param obj the object to draw the label for
0226          * @param _p the position of that object
0227          * @return true if the label was drawn
0228          * //FIXME: should this just take an object pointer and do its own projection?
0229          *
0230          * \p padding_factor is the factor by which the real label size is scaled
0231          */
0232     bool drawNameLabel(SkyObject *obj, const QPointF &_p, const qreal padding_factor = 1);
0233 
0234     /**
0235          *@short draw the object's name label on the map, without checking for
0236          *overlap with other labels.
0237          *@param obj reference to the QPainter on which to draw (either the sky pixmap or printer device)
0238          *@param _p The screen position for the label (in pixels; typically as found by SkyMap::toScreen())
0239          */
0240     void drawRudeNameLabel(SkyObject *obj, const QPointF &_p);
0241 
0242     /**
0243          * @short queues the label in the "type" buffer for later drawing.
0244          */
0245     void addLabel(SkyObject *obj, label_t type);
0246 
0247 #ifdef KSTARS_LITE
0248     /**
0249          * @short queues the label in the "type" buffer for later drawing. Doesn't calculate the position of
0250          * SkyObject but uses pos as a position of label.
0251          */
0252     void addLabel(SkyObject *obj, QPointF pos, label_t type);
0253 #endif
0254     /**
0255          *@short draws the labels stored in all the buffers.  You can change the
0256          * priority by editing the .cpp file and changing the order in which
0257          * buffers are drawn.  You can also change the fonts and colors there
0258          * too.
0259          */
0260     void drawQueuedLabels();
0261 
0262     /**
0263          * @short a convenience routine that draws all the labels from a single
0264          * buffer. Currently this is only called from within draw() above.
0265          */
0266     void drawQueuedLabelsType(SkyLabeler::label_t type);
0267 
0268     //----- Marking Regions -----//
0269 
0270     /**
0271          * @short tells the labeler the location and text of a label you want
0272          * to draw.  Returns true if there is room for the label and returns
0273          * false otherwise.  If it returns true, the location of the label is
0274          * marked on the virtual screen so future labels won't overlap it.
0275          *
0276          * It is usually easier to use drawLabel() or drawLabelOffest() instead
0277          * which both call mark() internally.
0278          *
0279          * \p padding_factor is the factor by which the real label size is
0280          * scaled
0281          */
0282     bool markText(const QPointF &p, const QString &text, qreal padding_factor = 1);
0283 
0284     /**
0285          * @short Works just like markText() above but for an arbitrary
0286          * rectangular region bounded by top, bot, left, and right.
0287          */
0288     bool markRegion(qreal left, qreal right, qreal top, qreal bot);
0289 
0290     //----- Diagnostics and Information -----//
0291 
0292     /**
0293          * @short diagnostic. the *percentage* of pixels that have been filled.
0294          * Expect return values between 0.0 and 100.0.  A fillRatio above 20
0295          * is pretty busy and crowded.  I think a fillRatio of about 10 looks
0296          * good.  The fillRatio will be lowered of the screen is zoomed out
0297          * so are big blank spaces around the celestial sphere.
0298          */
0299     float fillRatio();
0300 
0301     /**
0302          * @short diagnostic, the number of times mark() returned true divided by
0303          * the total number of times mark was called multiplied by 100.  Expect
0304          * return values between 0.0 an 100.  A hit ratio of 100 means no labels
0305          * would have overlapped.  A ratio of zero means no labels got drawn
0306          * (which should never happen).  A hitRatio around 50 might be a good
0307          * target to shoot for.  Expect it to be lower when fully zoomed out and
0308          * higher when zoomed in.
0309          */
0310     float hitRatio();
0311 
0312     /**
0313          * @short diagnostic, prints some brief statistics to the console.
0314          * Currently this is connected to the "b" key in SkyMapEvents.
0315          */
0316     void printInfo();
0317 
0318     inline QFont stdFont() { return m_stdFont; }
0319     inline QFont skyFont() { return m_skyFont; }
0320 #ifdef KSTARS_LITE
0321     inline QFont drawFont() { return m_drawFont; }
0322 #endif
0323 
0324     int hits() { return m_hits; }
0325     int marks() { return m_marks; }
0326 
0327   private:
0328     ScreenRows screenRows;
0329     int m_maxX { 0 };
0330     int m_maxY { 0 };
0331     int m_size { 0 };
0332     /// When to merge two adjacent regions
0333     int m_minDeltaX { 30 };
0334     int m_marks { 0 };
0335     int m_hits { 0 };
0336     int m_misses { 0 };
0337     int m_elements { 0 };
0338     int m_errors { 0 };
0339     qreal m_yScale { 0 };
0340     double m_offset { 0 };
0341     QFont m_stdFont, m_skyFont;
0342     QFontMetricsF m_fontMetrics;
0343 //In KStars Lite this font should be used wherever font of m_p was changed or used
0344 #ifdef KSTARS_LITE
0345     QFont m_drawFont;
0346 #endif
0347     QPainter m_p;
0348     QPicture m_picture;
0349     QVector<LabelList> labelList;
0350     const Projector *m_proj { nullptr };
0351     static SkyLabeler *pinstance;
0352 };