Kstars

skylabeler.h
1 /*
2  SPDX-FileCopyrightText: 2007 James B. Bowlin <[email protected]>
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 
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  */
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
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  */
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  */
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  */
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 
318  inline QFont stdFont() { return m_stdFont; }
319  inline QFont skyFont() { return m_skyFont; }
320 #ifdef KSTARS_LITE
321  inline QFont drawFont() { return m_drawFont; }
322 #endif
323 
324  int hits() { return m_hits; }
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 };
void drawRudeNameLabel(SkyObject *obj, const QPointF &_p)
draw the object's name label on the map, without checking for overlap with other labels.
Definition: skylabeler.cpp:647
static void AddLabel(SkyObject *obj, label_t type)
static version of addLabel() below.
Definition: skylabeler.h:135
void resetFont()
sets the font in SkyLabeler and in psky back to the zoom dependent value that was set in reset().
Definition: skylabeler.cpp:222
void setZoomFont()
adjusts the font in psky to be smaller if we are zoomed out.
Definition: skylabeler.cpp:44
void setPen(const QPen &pen)
sets the pen used for drawing labels on the sky.
Definition: skylabeler.cpp:197
void shrinkFont(int delta)
decreases the size of the font in psky and in the SkyLabeler by the delta points.
Definition: skylabeler.cpp:206
@ NUM_LABEL_TYPES
Rude labels block other labels FIXME: find a better solution.
Definition: skylabeler.h:117
void addLabel(SkyObject *obj, label_t type)
queues the label in the "type" buffer for later drawing.
Definition: skylabeler.cpp:577
void draw(QPainter &p)
KStars Lite version of the function above.
Definition: skylabeler.cpp:388
QFontMetricsF & fontMetrics()
returns the fontMetricsF we have already created.
Definition: skylabeler.h:205
bool drawGuideLabel(QPointF &o, const QString &text, double angle)
Tries to draw the text at the position and angle specified.
Definition: skylabeler.cpp:112
void getMargins(const QString &text, float *left, float *right, float *top, float *bot)
sets four margins for help in keeping labels entirely on the screen.
Definition: skylabeler.cpp:227
bool markText(const QPointF &p, const QString &text, qreal padding_factor=1)
tells the labeler the location and text of a label you want to draw.
Definition: skylabeler.cpp:408
void setFont(const QFont &font)
tells the labeler the font you will be using so it can figure out the height and width of the labels.
Definition: skylabeler.cpp:187
bool markRegion(qreal left, qreal right, qreal top, qreal bot)
Works just like markText() above but for an arbitrary rectangular region bounded by top,...
Definition: skylabeler.cpp:425
void printInfo()
diagnostic, prints some brief statistics to the console.
Definition: skylabeler.cpp:683
float fillRatio()
diagnostic.
Definition: skylabeler.cpp:669
static double ZoomOffset()
returns the zoom dependent label offset.
Definition: skylabeler.cpp:75
bool drawNameLabel(SkyObject *obj, const QPointF &_p, const qreal padding_factor=1)
Tries to draw a label for an object.
Definition: skylabeler.cpp:161
Canvas widget for displaying the sky bitmap; also handles user interaction events.
Definition: skymap.h:53
float hitRatio()
diagnostic, the number of times mark() returned true divided by the total number of times mark was ca...
Definition: skylabeler.cpp:676
void useStdFont()
sets the font in SkyLabeler and in psky to the font psky had originally when reset() was called.
Definition: skylabeler.cpp:217
void drawQueuedLabelsType(SkyLabeler::label_t type)
a convenience routine that draws all the labels from a single buffer.
Definition: skylabeler.cpp:633
Information about an object in the sky.
Definition: skyobject.h:41
void drawQueuedLabels()
draws the labels stored in all the buffers.
Definition: skylabeler.cpp:593
void reset(SkyMap *skyMap)
clears the virtual screen (if needed) and resizes the virtual screen (if needed) to match skyMap.
Definition: skylabeler.cpp:250
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Oct 1 2023 04:02:44 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.