Warning, file /education/kstars/kstars/skycomponents/skylabeler.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 };