Kstars

skylabeler.h
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
17class QString;
18class QPointF;
19class SkyMap;
20class Projector;
21struct 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();
103
104 public:
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
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};
The Projector class is the primary class that serves as an interface to handle projections.
Definition projector.h:58
The purpose of this class is to prevent labels from overlapping.
Definition skylabeler.h:99
float fillRatio()
diagnostic.
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.
void resetFont()
sets the font in SkyLabeler and in psky back to the zoom dependent value that was set in reset().
void shrinkFont(int delta)
decreases the size of the font in psky and in the SkyLabeler by the delta points.
void setZoomFont()
adjusts the font in psky to be smaller if we are zoomed out.
bool markRegion(qreal left, qreal right, qreal top, qreal bot)
Works just like markText() above but for an arbitrary rectangular region bounded by top,...
void drawQueuedLabels()
draws the labels stored in all the buffers.
static void AddLabel(SkyObject *obj, label_t type)
static version of addLabel() below.
Definition skylabeler.h:135
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.
void reset(SkyMap *skyMap)
clears the virtual screen (if needed) and resizes the virtual screen (if needed) to match skyMap.
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.
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.
void addLabel(SkyObject *obj, label_t type)
queues the label in the "type" buffer for later drawing.
void setPen(const QPen &pen)
sets the pen used for drawing labels on the sky.
static double ZoomOffset()
returns the zoom dependent label offset.
float hitRatio()
diagnostic, the number of times mark() returned true divided by the total number of times mark was ca...
void useStdFont()
sets the font in SkyLabeler and in psky to the font psky had originally when reset() was called.
void printInfo()
diagnostic, prints some brief statistics to the console.
void drawQueuedLabelsType(SkyLabeler::label_t type)
a convenience routine that draws all the labels from a single buffer.
@ NUM_LABEL_TYPES
Rude labels block other labels FIXME: find a better solution.
Definition skylabeler.h:117
void draw(QPainter &p)
KStars Lite version of the function above.
void drawRudeNameLabel(SkyObject *obj, const QPointF &_p)
draw the object's name label on the map, without checking for overlap with other labels.
bool drawNameLabel(SkyObject *obj, const QPointF &_p, const qreal padding_factor=1)
Tries to draw a label for an object.
This is the canvas on which the sky is painted.
Definition skymap.h:54
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:42
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:04 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.