1 /*
2     SPDX-FileCopyrightText: 2007 James B. Bowlin <bowlin@mindspring.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #pragma once
7 
8 #include "skylabel.h"
9 
10 #include <QFontMetricsF>
11 #include <QList>
12 #include <QVector>
13 #include <QPainter>
14 #include <QPicture>
15 #include <QFont>
16 
17 class QString;
18 class QPointF;
19 class SkyMap;
20 class Projector;
21 struct LabelRun;
22 
23 typedef QList<LabelRun *> LabelRow;
24 typedef QVector<LabelRow *> ScreenRows;
25 
26 /**
27  *@class SkyLabeler
28  * The purpose of this class is to prevent labels from overlapping.  We do this
29  * by creating a virtual (lower Y-resolution) screen. Each "pixel" of this
30  * screen essentially contains a boolean value telling us whether or not there
31  * is an existing label covering at least part of that pixel.  Before you draw
32  * a label, call mark( QPointF, QString ) of that label.  We will check to see
33  * if it would overlap any existing label.  If there is overlap we return false.
34  * If there is no overlap then we mark all the pixels that cover the new label
35  * and return true.
36  *
37  * Since we need to check for overlap for every label every time it is
38  * potentially drawn on the screen, efficiency is essential.  So instead of
39  * having a 2-dimensional array of boolean values we use Run Length Encoding
40  * and store the virtual array in a QVector of QLists.  Each element of the
41  * vector, a LabelRow, corresponds to a horizontal strip of pixels on the actual
42  * screen.  How many vertical pixels are in each strip is controlled by
43  * m_yDensity.  The higher the density, the fewer vertical pixels per strip and
44  * hence a larger number of strips are needed to cover the screen.  As
45  * m_yDensity increases so does the density of the strips.
46  *
47  * The information in the X-dimension is completed run length encoded. A
48  * consecutive run of pixels in one strip that are covered by one or more labels
49  * is stored in a LabelRun object that merely stores the start pixel and the end
50  * pixel.  A LabelRow is a list of LabelRun's stored in ascending order.  This
51  * saves a lot of space over an explicit array and it also makes checking for
52  * overlaps faster and even makes inserting new overlaps faster on average.
53  *
54  * Synopsis:
55  *
56  *   1) Create a new SkyLabeler
57  *
58  *   2) every time you want to draw a new screen, reset the labeler.
59  *
60  *   3) Either:
61  *
62  *	 A) Call drawLabel() or drawOffsetLabel(), or
63  *
64  *	 B) Call addLabel() or addOffsetLabel()
65  *
66  *  4) Call draw() if addLabel() or addOffsetLabel() were used.
67  *
68  *
69  *
70  * SkyLabeler works totally on a first come, first served basis which is almost
71  * the direct opposite of a z-buffer where the objects drawn last are most
72  * visible.  This is why the addLabel() and draw() routines were created.
73  * They allow us to time-shift the drawing of labels and thus gives us control
74  * over their priority.  The drawLabel() routines are still available but are
75  * not being used.  The addLabel() routines adds a label to a specific buffer.
76  * Each type of label has its own buffer which lets us control the font and
77  * color as well as the priority.  The priority is now manually set in the
78  * draw() routine by adjusting the order in which the various buffers get
79  * drawn.
80  *
81  * Finally, even though this code was written to be very efficient, we might
82  * want to take some care in how many labels we throw at it.  Sending it
83  * a large number of overlapping labels can be wasteful. Also, if one type
84  * of object floods it with labels early on then there may not be any room
85  * left for other types of labels.  Therefore for some types of objects (stars)
86  * we may want to have a zoom dependent label threshold magnitude just like
87  * we have for drawing the stars themselves.  This would throw few labels
88  * at the labeler when we are zoomed at and they would mostly overlap anyway
89  * and it would give us more labels when the user is zoomed in and there
90  * is more room for them.  The "b" key currently causes the labeler statistics
91  * to be printed.  This may be useful in figuring out the best settings for
92  * the magnitude limits.  It may even be possible to have KStars do some of
93  * this throttling automatically but I haven't really thought about that
94  * problem yet.
95  *
96  * -- James B. Bowlin  2007-08-02
97  */
98 class SkyLabeler
99 {
100   protected:
101     SkyLabeler();
102     SkyLabeler(SkyLabeler &skyLabler);
103 
104   public:
105     enum label_t
106     {
107         STAR_LABEL,
108         ASTEROID_LABEL,
109         COMET_LABEL,
110         PLANET_LABEL,
111         JUPITER_MOON_LABEL,
112         SATURN_MOON_LABEL,
113         DEEP_SKY_LABEL,
114         CONSTEL_NAME_LABEL,
115         SATELLITE_LABEL,
116         RUDE_LABEL, ///Rude labels block other labels FIXME: find a better solution
117         NUM_LABEL_TYPES
118     };
119 
120     //----- Static Methods ----------------------------------------------//
121 
122     static SkyLabeler *Instance();
123 
124     /**
125          * @short returns the zoom dependent label offset.  This is used in this
126          * class and in SkyObject.  It is important that the offsets be the same
127          * so highlighted labels are always drawn exactly on top of the normally
128          * drawn labels.
129          */
130     static double ZoomOffset();
131 
132     /**
133          * @short static version of addLabel() below.
134          */
AddLabel(SkyObject * obj,label_t type)135     inline static void AddLabel(SkyObject *obj, label_t type) { pinstance->addLabel(obj, type); }
136 
137     //--------------------------------------------------------------------//
138     ~SkyLabeler();
139 
140     /**
141          * @short clears the virtual screen (if needed) and resizes the virtual
142          * screen (if needed) to match skyMap.  A font must be specified which
143          * is taken to be the average or normal font that will be used.  The
144          * size of the horizontal strips will be (slightly) optimized for this
145          * font.  We also adjust the font size in psky to smaller fonts if the
146          * screen is zoomed out.  You can mimic this setting with the static
147          * method SkyLabeler::setZoomFont( psky ).
148          */
149     void reset(SkyMap *skyMap);
150 
151 /**
152          * @short KStars Lite version of the function above
153          */
154 #ifdef KSTARS_LITE
155     void reset();
156 #endif
157 
158     /**
159          * @short Draws labels using the given painter
160          * @param p the painter to draw labels with
161          */
162     void draw(QPainter &p);
163 
164     //----- Font Setting -----//
165 
166     /**
167          * @short adjusts the font in psky to be smaller if we are zoomed out.
168          */
169     void setZoomFont();
170 
171     /**
172          * @short sets the pen used for drawing labels on the sky.
173          */
174     void setPen(const QPen &pen);
175 
176     /**
177          * @short tells the labeler the font you will be using so it can figure
178          * out the height and width of the labels.  Also sets this font in the
179          * psky since this is almost always what is wanted.
180          */
181     void setFont(const QFont &font);
182 
183     /**
184          * @short decreases the size of the font in psky and in the SkyLabeler
185          * by the delta points.  Negative deltas will increase the font size.
186          */
187     void shrinkFont(int delta);
188 
189     /**
190          * @short sets the font in SkyLabeler and in psky to the font psky
191          * had originally when reset() was called.  Used by ConstellationNames.
192          */
193     void useStdFont();
194 
195     /**
196          * @short sets the font in SkyLabeler and in psky back to the zoom
197          * dependent value that was set in reset().  Also used in
198          * ConstellationLines.
199          */
200     void resetFont();
201 
202     /**
203          * @short returns the fontMetricsF we have already created.
204          */
fontMetrics()205     QFontMetricsF &fontMetrics() { return m_fontMetrics; }
206 
207     //----- Drawing/Adding Labels -----//
208 
209     /**
210          *@short sets four margins for help in keeping labels entirely on the
211          * screen.
212          */
213     void getMargins(const QString &text, float *left, float *right, float *top, float *bot);
214 
215     /**
216          * @short Tries to draw the text at the position and angle specified. If
217          * the label would overlap an existing label it is not drawn and we
218          * return false, otherwise the label is drawn, its position is marked
219          * and we return true.
220          */
221     bool drawGuideLabel(QPointF &o, const QString &text, double angle);
222 
223     /**
224          * @short Tries to draw a label for an object.
225          * @param obj the object to draw the label for
226          * @param _p the position of that object
227          * @return true if the label was drawn
228          * //FIXME: should this just take an object pointer and do its own projection?
229          *
230          * \p padding_factor is the factor by which the real label size is scaled
231          */
232     bool drawNameLabel(SkyObject *obj, const QPointF &_p, const qreal padding_factor = 1);
233 
234     /**
235          *@short draw the object's name label on the map, without checking for
236          *overlap with other labels.
237          *@param obj reference to the QPainter on which to draw (either the sky pixmap or printer device)
238          *@param _p The screen position for the label (in pixels; typically as found by SkyMap::toScreen())
239          */
240     void drawRudeNameLabel(SkyObject *obj, const QPointF &_p);
241 
242     /**
243          * @short queues the label in the "type" buffer for later drawing.
244          */
245     void addLabel(SkyObject *obj, label_t type);
246 
247 #ifdef KSTARS_LITE
248     /**
249          * @short queues the label in the "type" buffer for later drawing. Doesn't calculate the position of
250          * SkyObject but uses pos as a position of label.
251          */
252     void addLabel(SkyObject *obj, QPointF pos, label_t type);
253 #endif
254     /**
255          *@short draws the labels stored in all the buffers.  You can change the
256          * priority by editing the .cpp file and changing the order in which
257          * buffers are drawn.  You can also change the fonts and colors there
258          * too.
259          */
260     void drawQueuedLabels();
261 
262     /**
263          * @short a convenience routine that draws all the labels from a single
264          * buffer. Currently this is only called from within draw() above.
265          */
266     void drawQueuedLabelsType(SkyLabeler::label_t type);
267 
268     //----- Marking Regions -----//
269 
270     /**
271          * @short tells the labeler the location and text of a label you want
272          * to draw.  Returns true if there is room for the label and returns
273          * false otherwise.  If it returns true, the location of the label is
274          * marked on the virtual screen so future labels won't overlap it.
275          *
276          * It is usually easier to use drawLabel() or drawLabelOffest() instead
277          * which both call mark() internally.
278          *
279          * \p padding_factor is the factor by which the real label size is
280          * scaled
281          */
282     bool markText(const QPointF &p, const QString &text, qreal padding_factor = 1);
283 
284     /**
285          * @short Works just like markText() above but for an arbitrary
286          * rectangular region bounded by top, bot, left, and right.
287          */
288     bool markRegion(qreal left, qreal right, qreal top, qreal bot);
289 
290     //----- Diagnostics and Information -----//
291 
292     /**
293          * @short diagnostic. the *percentage* of pixels that have been filled.
294          * Expect return values between 0.0 and 100.0.  A fillRatio above 20
295          * is pretty busy and crowded.  I think a fillRatio of about 10 looks
296          * good.  The fillRatio will be lowered of the screen is zoomed out
297          * so are big blank spaces around the celestial sphere.
298          */
299     float fillRatio();
300 
301     /**
302          * @short diagnostic, the number of times mark() returned true divided by
303          * the total number of times mark was called multiplied by 100.  Expect
304          * return values between 0.0 an 100.  A hit ratio of 100 means no labels
305          * would have overlapped.  A ratio of zero means no labels got drawn
306          * (which should never happen).  A hitRatio around 50 might be a good
307          * target to shoot for.  Expect it to be lower when fully zoomed out and
308          * higher when zoomed in.
309          */
310     float hitRatio();
311 
312     /**
313          * @short diagnostic, prints some brief statistics to the console.
314          * Currently this is connected to the "b" key in SkyMapEvents.
315          */
316     void printInfo();
317 
stdFont()318     inline QFont stdFont() { return m_stdFont; }
skyFont()319     inline QFont skyFont() { return m_skyFont; }
320 #ifdef KSTARS_LITE
drawFont()321     inline QFont drawFont() { return m_drawFont; }
322 #endif
323 
hits()324     int hits() { return m_hits; }
marks()325     int marks() { return m_marks; }
326 
327   private:
328     ScreenRows screenRows;
329     int m_maxX { 0 };
330     int m_maxY { 0 };
331     int m_size { 0 };
332     /// When to merge two adjacent regions
333     int m_minDeltaX { 30 };
334     int m_marks { 0 };
335     int m_hits { 0 };
336     int m_misses { 0 };
337     int m_elements { 0 };
338     int m_errors { 0 };
339     qreal m_yScale { 0 };
340     double m_offset { 0 };
341     QFont m_stdFont, m_skyFont;
342     QFontMetricsF m_fontMetrics;
343 //In KStars Lite this font should be used wherever font of m_p was changed or used
344 #ifdef KSTARS_LITE
345     QFont m_drawFont;
346 #endif
347     QPainter m_p;
348     QPicture m_picture;
349     QVector<LabelList> labelList;
350     const Projector *m_proj { nullptr };
351     static SkyLabeler *pinstance;
352 };
353