File indexing completed on 2022-12-06 18:58:56

0001 /*
0002     SPDX-FileCopyrightText: 2010 Henry de Valence <hdevalence@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "skyqpainter.h"
0008 
0009 #include <QPointer>
0010 
0011 #include "kstarsdata.h"
0012 #include "Options.h"
0013 #include "skymap.h"
0014 #include "projections/projector.h"
0015 #include "skycomponents/flagcomponent.h"
0016 #include "skycomponents/linelist.h"
0017 #include "skycomponents/linelistlabel.h"
0018 #include "skycomponents/satellitescomponent.h"
0019 #include "skycomponents/skiphashlist.h"
0020 #include "skycomponents/skymapcomposite.h"
0021 #include "skycomponents/solarsystemcomposite.h"
0022 #include "skycomponents/earthshadowcomponent.h"
0023 #include "skyobjects/skyobject.h"
0024 #include "skyobjects/constellationsart.h"
0025 #include "skyobjects/catalogobject.h"
0026 #include "skyobjects/ksasteroid.h"
0027 #include "skyobjects/kscomet.h"
0028 #include "skyobjects/kssun.h"
0029 #include "skyobjects/satellite.h"
0030 #include "skyobjects/supernova.h"
0031 #include "skyobjects/ksearthshadow.h"
0032 #ifdef HAVE_INDI
0033 #include "skyobjects/mosaictiles.h"
0034 #endif
0035 #include "hips/hipsrenderer.h"
0036 #include "terrain/terrainrenderer.h"
0037 
0038 namespace
0039 {
0040 // Convert spectral class to numerical index.
0041 // If spectral class is invalid return index for white star (A class)
0042 int harvardToIndex(char c)
0043 {
0044     switch (c)
0045     {
0046         case 'o':
0047         case 'O':
0048             return 0;
0049         case 'b':
0050         case 'B':
0051             return 1;
0052         case 'a':
0053         case 'A':
0054             return 2;
0055         case 'f':
0056         case 'F':
0057             return 3;
0058         case 'g':
0059         case 'G':
0060             return 4;
0061         case 'k':
0062         case 'K':
0063             return 5;
0064         case 'm':
0065         case 'M':
0066             return 6;
0067         // For unknown spectral class assume A class (white star)
0068         default:
0069             return 2;
0070     }
0071 }
0072 
0073 // Total number of sizes of stars.
0074 const int nStarSizes = 15;
0075 // Total number of spectral classes
0076 // N.B. Must be in sync with harvardToIndex
0077 const int nSPclasses = 7;
0078 
0079 // Cache for star images.
0080 //
0081 // These pixmaps are never deallocated. Not really good...
0082 QPixmap *imageCache[nSPclasses][nStarSizes] = { { nullptr } };
0083 
0084 std::unique_ptr<QPixmap> visibleSatPixmap, invisibleSatPixmap;
0085 } // namespace
0086 
0087 int SkyQPainter::starColorMode           = 0;
0088 QColor SkyQPainter::m_starColor          = QColor();
0089 QMap<char, QColor> SkyQPainter::ColorMap = QMap<char, QColor>();
0090 
0091 void SkyQPainter::releaseImageCache()
0092 {
0093     for (char &color : ColorMap.keys())
0094     {
0095         QPixmap **pmap = imageCache[harvardToIndex(color)];
0096 
0097         for (int size = 1; size < nStarSizes; size++)
0098         {
0099             if (pmap[size])
0100                 delete pmap[size];
0101 
0102             pmap[size] = nullptr;
0103         }
0104     }
0105 }
0106 
0107 SkyQPainter::SkyQPainter(QPaintDevice *pd) : SkyPainter(), QPainter()
0108 {
0109     Q_ASSERT(pd);
0110     m_pd         = pd;
0111     m_size       = QSize(pd->width(), pd->height());
0112     m_hipsRender = new HIPSRenderer();
0113 }
0114 
0115 SkyQPainter::SkyQPainter(QPaintDevice *pd, const QSize &size) : SkyPainter(), QPainter()
0116 {
0117     Q_ASSERT(pd);
0118     m_pd         = pd;
0119     m_size       = size;
0120     m_hipsRender = new HIPSRenderer();
0121 }
0122 
0123 SkyQPainter::SkyQPainter(QWidget *widget, QPaintDevice *pd) : SkyPainter(), QPainter()
0124 {
0125     Q_ASSERT(widget);
0126     // Set paint device pointer to pd or to the widget if pd = 0
0127     m_pd         = (pd ? pd : widget);
0128     m_size       = widget->size();
0129     m_hipsRender = new HIPSRenderer();
0130 }
0131 
0132 SkyQPainter::~SkyQPainter()
0133 {
0134     delete (m_hipsRender);
0135 }
0136 
0137 void SkyQPainter::begin()
0138 {
0139     QPainter::begin(m_pd);
0140     bool aa = !SkyMap::Instance()->isSlewing() && Options::useAntialias();
0141     setRenderHint(QPainter::Antialiasing, aa);
0142     setRenderHint(QPainter::HighQualityAntialiasing, aa);
0143     m_proj = SkyMap::Instance()->projector();
0144 }
0145 
0146 void SkyQPainter::end()
0147 {
0148     QPainter::end();
0149 }
0150 
0151 void SkyQPainter::drawSkyBackground()
0152 {
0153     //FIXME use projector
0154     fillRect(0, 0, m_size.width(), m_size.height(),
0155              KStarsData::Instance()->colorScheme()->colorNamed("SkyColor"));
0156 }
0157 
0158 void SkyQPainter::setPen(const QPen &pen)
0159 {
0160     QPainter::setPen(pen);
0161 }
0162 
0163 void SkyQPainter::setBrush(const QBrush &brush)
0164 {
0165     QPainter::setBrush(brush);
0166 }
0167 
0168 void SkyQPainter::initStarImages()
0169 {
0170     const int starColorIntensity = Options::starColorIntensity();
0171 
0172     ColorMap.clear();
0173     switch (Options::starColorMode())
0174     {
0175         case 1: // Red stars.
0176             m_starColor = Qt::red;
0177             break;
0178         case 2: // Black stars.
0179             m_starColor = Qt::black;
0180             break;
0181         case 3: // White stars
0182             m_starColor = Qt::white;
0183             break;
0184         case 0:  // Real color
0185         default: // And use real color for everything else
0186             m_starColor = QColor();
0187             ColorMap.insert('O', QColor::fromRgb(0, 0, 255));
0188             ColorMap.insert('B', QColor::fromRgb(0, 200, 255));
0189             ColorMap.insert('A', QColor::fromRgb(0, 255, 255));
0190             ColorMap.insert('F', QColor::fromRgb(200, 255, 100));
0191             ColorMap.insert('G', QColor::fromRgb(255, 255, 0));
0192             ColorMap.insert('K', QColor::fromRgb(255, 100, 0));
0193             ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
0194             break;
0195     }
0196     if (ColorMap.isEmpty())
0197     {
0198         ColorMap.insert('O', m_starColor);
0199         ColorMap.insert('B', m_starColor);
0200         ColorMap.insert('A', m_starColor);
0201         ColorMap.insert('F', m_starColor);
0202         ColorMap.insert('G', m_starColor);
0203         ColorMap.insert('K', m_starColor);
0204         ColorMap.insert('M', m_starColor);
0205     }
0206 
0207     for (char &color : ColorMap.keys())
0208     {
0209         QPixmap BigImage(15, 15);
0210         BigImage.fill(Qt::transparent);
0211 
0212         QPainter p;
0213         p.begin(&BigImage);
0214 
0215         if (Options::starColorMode() == 0)
0216         {
0217             qreal h, s, v, a;
0218             p.setRenderHint(QPainter::Antialiasing, false);
0219             QColor starColor = ColorMap[color];
0220             starColor.getHsvF(&h, &s, &v, &a);
0221             for (int i = 0; i < 8; i++)
0222             {
0223                 for (int j = 0; j < 8; j++)
0224                 {
0225                     qreal x    = i - 7;
0226                     qreal y    = j - 7;
0227                     qreal dist = sqrt(x * x + y * y) / 7.0;
0228                     starColor.setHsvF(
0229                         h,
0230                         qMin(qreal(1),
0231                              dist < (10 - starColorIntensity) / 10.0 ? 0 : dist),
0232                         v,
0233                         qMax(qreal(0),
0234                              dist < (10 - starColorIntensity) / 20.0 ? 1 : 1 - dist));
0235                     p.setPen(starColor);
0236                     p.drawPoint(i, j);
0237                     p.drawPoint(14 - i, j);
0238                     p.drawPoint(i, 14 - j);
0239                     p.drawPoint(14 - i, 14 - j);
0240                 }
0241             }
0242         }
0243         else
0244         {
0245             p.setRenderHint(QPainter::Antialiasing, true);
0246             p.setPen(QPen(ColorMap[color], 2.0));
0247             p.setBrush(p.pen().color());
0248             p.drawEllipse(QRectF(2, 2, 10, 10));
0249         }
0250         p.end();
0251 
0252         // Cache array slice
0253         QPixmap **pmap = imageCache[harvardToIndex(color)];
0254 
0255         for (int size = 1; size < nStarSizes; size++)
0256         {
0257             if (!pmap[size])
0258                 pmap[size] = new QPixmap();
0259             *pmap[size] = BigImage.scaled(size, size, Qt::KeepAspectRatio,
0260                                           Qt::SmoothTransformation);
0261         }
0262     }
0263     starColorMode = Options::starColorMode();
0264 
0265     if (!visibleSatPixmap.get())
0266         visibleSatPixmap.reset(new QPixmap(":/icons/kstars_satellites_visible.svg"));
0267     if (!invisibleSatPixmap.get())
0268         invisibleSatPixmap.reset(new QPixmap(":/icons/kstars_satellites_invisible.svg"));
0269 }
0270 
0271 void SkyQPainter::drawSkyLine(SkyPoint *a, SkyPoint *b)
0272 {
0273     bool aVisible, bVisible;
0274     QPointF aScreen = m_proj->toScreen(a, true, &aVisible);
0275     QPointF bScreen = m_proj->toScreen(b, true, &bVisible);
0276 
0277     drawLine(aScreen, bScreen);
0278 
0279     //THREE CASES:
0280     //    if (aVisible && bVisible)
0281     //    {
0282     //        //Both a,b visible, so paint the line normally:
0283     //        drawLine(aScreen, bScreen);
0284     //    }
0285     //    else if (aVisible)
0286     //    {
0287     //        //a is visible but b isn't:
0288     //        drawLine(aScreen, m_proj->clipLine(a, b));
0289     //    }
0290     //    else if (bVisible)
0291     //    {
0292     //        //b is visible but a isn't:
0293     //        drawLine(bScreen, m_proj->clipLine(b, a));
0294     //    } //FIXME: what if both are offscreen but the line isn't?
0295 }
0296 
0297 void SkyQPainter::drawSkyPolyline(LineList *list, SkipHashList *skipList,
0298                                   LineListLabel *label)
0299 {
0300     SkyList *points = list->points();
0301     bool isVisible, isVisibleLast;
0302 
0303     if (points->size() == 0)
0304         return;
0305     QPointF oLast = m_proj->toScreen(points->first().get(), true, &isVisibleLast);
0306     // & with the result of checkVisibility to clip away things below horizon
0307     isVisibleLast &= m_proj->checkVisibility(points->first().get());
0308     QPointF oThis, oThis2;
0309 
0310     for (int j = 1; j < points->size(); j++)
0311     {
0312         SkyPoint *pThis = points->at(j).get();
0313 
0314         oThis2 = oThis = m_proj->toScreen(pThis, true, &isVisible);
0315         // & with the result of checkVisibility to clip away things below horizon
0316         isVisible &= m_proj->checkVisibility(pThis);
0317         bool doSkip = false;
0318         if (skipList)
0319         {
0320             doSkip = skipList->skip(j);
0321         }
0322 
0323         bool pointsVisible = false;
0324         //Temporary solution to avoid random lines in Gnomonic projection and draw lines up to horizon
0325         if (SkyMap::Instance()->projector()->type() == Projector::Gnomonic)
0326         {
0327             if (isVisible && isVisibleLast)
0328                 pointsVisible = true;
0329         }
0330         else
0331         {
0332             if (isVisible || isVisibleLast)
0333                 pointsVisible = true;
0334         }
0335 
0336         if (!doSkip)
0337         {
0338             if (pointsVisible)
0339             {
0340                 drawLine(oLast, oThis);
0341                 if (label)
0342                     label->updateLabelCandidates(oThis.x(), oThis.y(), list, j);
0343             }
0344         }
0345 
0346         oLast         = oThis2;
0347         isVisibleLast = isVisible;
0348     }
0349 }
0350 
0351 void SkyQPainter::drawSkyPolygon(LineList *list, bool forceClip)
0352 {
0353     bool isVisible  = false, isVisibleLast;
0354     SkyList *points = list->points();
0355     QPolygonF polygon;
0356 
0357     if (forceClip == false)
0358     {
0359         for (const auto &point : *points)
0360         {
0361             polygon << m_proj->toScreen(point.get(), false, &isVisibleLast);
0362             isVisible |= isVisibleLast;
0363         }
0364 
0365         // If 1+ points are visible, draw it
0366         if (polygon.size() && isVisible)
0367             drawPolygon(polygon);
0368 
0369         return;
0370     }
0371 
0372     SkyPoint *pLast = points->last().get();
0373     QPointF oLast   = m_proj->toScreen(pLast, true, &isVisibleLast);
0374     // & with the result of checkVisibility to clip away things below horizon
0375     isVisibleLast &= m_proj->checkVisibility(pLast);
0376 
0377     for (const auto &point : *points)
0378     {
0379         SkyPoint *pThis = point.get();
0380         QPointF oThis   = m_proj->toScreen(pThis, true, &isVisible);
0381         // & with the result of checkVisibility to clip away things below horizon
0382         isVisible &= m_proj->checkVisibility(pThis);
0383 
0384         if (isVisible && isVisibleLast)
0385         {
0386             polygon << oThis;
0387         }
0388         else if (isVisibleLast)
0389         {
0390             QPointF oMid = m_proj->clipLine(pLast, pThis);
0391             polygon << oMid;
0392         }
0393         else if (isVisible)
0394         {
0395             QPointF oMid = m_proj->clipLine(pThis, pLast);
0396             polygon << oMid;
0397             polygon << oThis;
0398         }
0399 
0400         pLast         = pThis;
0401         oLast         = oThis;
0402         isVisibleLast = isVisible;
0403     }
0404 
0405     if (polygon.size())
0406         drawPolygon(polygon);
0407 }
0408 
0409 bool SkyQPainter::drawPlanet(KSPlanetBase *planet)
0410 {
0411     if (!m_proj->checkVisibility(planet))
0412         return false;
0413 
0414     bool visible = false;
0415     QPointF pos  = m_proj->toScreen(planet, true, &visible);
0416     if (!visible || !m_proj->onScreen(pos))
0417         return false;
0418 
0419     float fakeStarSize = (10.0 + log10(Options::zoomFactor()) - log10(MINZOOM)) *
0420                          (10 - planet->mag()) / 10;
0421     if (fakeStarSize > 15.0)
0422         fakeStarSize = 15.0;
0423 
0424     double size = planet->angSize() * dms::PI * Options::zoomFactor() / 10800.0;
0425     if (size < fakeStarSize && planet->name() != i18n("Sun") &&
0426             planet->name() != i18n("Moon"))
0427     {
0428         // Draw them as bright stars of appropriate color instead of images
0429         char spType;
0430         //FIXME: do these need i18n?
0431         if (planet->name() == i18n("Mars"))
0432         {
0433             spType = 'K';
0434         }
0435         else if (planet->name() == i18n("Jupiter") || planet->name() == i18n("Mercury") ||
0436                  planet->name() == i18n("Saturn"))
0437         {
0438             spType = 'F';
0439         }
0440         else
0441         {
0442             spType = 'B';
0443         }
0444         drawPointSource(pos, fakeStarSize, spType);
0445     }
0446     else
0447     {
0448         float sizemin = 1.0;
0449         if (planet->name() == i18n("Sun") || planet->name() == i18n("Moon"))
0450             sizemin = 8.0;
0451 
0452         if (size < sizemin)
0453             size = sizemin;
0454 
0455         if (Options::showPlanetImages() && !planet->image().isNull())
0456         {
0457             //Because Saturn has rings, we inflate its image size by a factor 2.5
0458             if (planet->name() == "Saturn")
0459                 size = int(2.5 * size);
0460             // Scale size exponentially so it is visible at large zooms
0461             else if (planet->name() == "Pluto")
0462                 size = int(size * exp(1.5 * size));
0463 
0464             save();
0465             translate(pos);
0466             rotate(m_proj->findPA(planet, pos.x(), pos.y()));
0467             drawImage(QRectF(-0.5 * size, -0.5 * size, size, size), planet->image());
0468             restore();
0469         }
0470         else //Otherwise, draw a simple circle.
0471         {
0472             drawEllipse(pos, size * .5, size * .5);
0473         }
0474     }
0475     return true;
0476 }
0477 
0478 bool SkyQPainter::drawEarthShadow(KSEarthShadow *shadow)
0479 {
0480     if (!m_proj->checkVisibility(shadow))
0481         return false;
0482 
0483     bool visible = false;
0484     QPointF pos  = m_proj->toScreen(shadow, true, &visible);
0485 
0486     if (!visible)
0487         return false;
0488 
0489     double umbra_size =
0490         shadow->getUmbraAngSize() * dms::PI * Options::zoomFactor() / 10800.0;
0491     double penumbra_size =
0492         shadow->getPenumbraAngSize() * dms::PI * Options::zoomFactor() / 10800.0;
0493 
0494     save();
0495     setBrush(QBrush(QColor(255, 96, 38, 128)));
0496     drawEllipse(pos, umbra_size, umbra_size);
0497     setBrush(QBrush(QColor(255, 96, 38, 90)));
0498     drawEllipse(pos, penumbra_size, penumbra_size);
0499     restore();
0500 
0501     return true;
0502 }
0503 
0504 bool SkyQPainter::drawComet(KSComet *com)
0505 {
0506     if (!m_proj->checkVisibility(com))
0507         return false;
0508 
0509     double size =
0510         com->angSize() * dms::PI * Options::zoomFactor() / 10800.0 / 2; // Radius
0511     if (size < 1)
0512         size = 1;
0513 
0514     bool visible = false;
0515     QPointF pos  = m_proj->toScreen(com, true, &visible);
0516 
0517     // Draw the coma. FIXME: Another Check??
0518     if (visible && m_proj->onScreen(pos))
0519     {
0520         // Draw the comet.
0521         drawEllipse(pos, size, size);
0522 
0523         double comaLength =
0524             (com->getComaAngSize().arcmin() * dms::PI * Options::zoomFactor() / 10800.0);
0525 
0526         // If coma is visible and long enough.
0527         if (Options::showCometComas() && comaLength > size)
0528         {
0529             KSSun *sun =
0530                 KStarsData::Instance()->skyComposite()->solarSystemComposite()->sun();
0531 
0532             // Find the angle to the sun.
0533             double comaAngle = m_proj->findPA(sun, pos.x(), pos.y());
0534 
0535             const QVector<QPoint> coma = { QPoint(pos.x() - size, pos.y()),
0536                                            QPoint(pos.x() + size, pos.y()),
0537                                            QPoint(pos.x(), pos.y() + comaLength)
0538                                          };
0539 
0540             QPolygon comaPoly(coma);
0541 
0542             comaPoly =
0543                 QTransform()
0544                 .translate(pos.x(), pos.y())
0545                 .rotate(
0546                     comaAngle) // Already + 180 Deg, because rotated from south, not north.
0547                 .translate(-pos.x(), -pos.y())
0548                 .map(comaPoly);
0549 
0550             save();
0551 
0552             // Nice fade for the Coma.
0553             QLinearGradient linearGrad(pos, comaPoly.point(2));
0554             linearGrad.setColorAt(0, QColor("white"));
0555             linearGrad.setColorAt(size / comaLength, QColor("white"));
0556             linearGrad.setColorAt(0.9, QColor("transparent"));
0557             setBrush(linearGrad);
0558 
0559             // Render Coma.
0560             drawConvexPolygon(comaPoly);
0561             restore();
0562         }
0563 
0564         return true;
0565     }
0566     else
0567     {
0568         return false;
0569     }
0570 }
0571 
0572 bool SkyQPainter::drawPointSource(const SkyPoint *loc, float mag, char sp)
0573 {
0574     //Check if it's even visible before doing anything
0575     if (!m_proj->checkVisibility(loc))
0576         return false;
0577 
0578     bool visible = false;
0579     QPointF pos  = m_proj->toScreen(loc, true, &visible);
0580     if (visible &&
0581             m_proj->onScreen(
0582                 pos)) // FIXME: onScreen here should use canvas size rather than SkyMap size, especially while printing in portrait mode!
0583     {
0584         drawPointSource(pos, starWidth(mag), sp);
0585         return true;
0586     }
0587     else
0588     {
0589         return false;
0590     }
0591 }
0592 
0593 void SkyQPainter::drawPointSource(const QPointF &pos, float size, char sp)
0594 {
0595     int isize = qMin(static_cast<int>(size), 14);
0596     if (!m_vectorStars || starColorMode == 0)
0597     {
0598         // Draw stars as bitmaps, either because we were asked to, or because we're painting real colors
0599         QPixmap *im  = imageCache[harvardToIndex(sp)][isize];
0600         float offset = 0.5 * im->width();
0601         drawPixmap(QPointF(pos.x() - offset, pos.y() - offset), *im);
0602     }
0603     else
0604     {
0605         // Draw stars as vectors, for better printing / SVG export etc.
0606         if (starColorMode != 4)
0607         {
0608             setPen(m_starColor);
0609             setBrush(m_starColor);
0610         }
0611         else
0612         {
0613             // Note: This is not efficient, but we use vector stars only when plotting SVG, not when drawing the skymap, so speed is not very important.
0614             QColor c = ColorMap.value(sp, Qt::white);
0615             setPen(c);
0616             setBrush(c);
0617         }
0618 
0619         // Be consistent with old raster representation
0620         if (size > 14)
0621             size = 14;
0622         if (size >= 2)
0623             drawEllipse(pos.x() - 0.5 * size, pos.y() - 0.5 * size, int(size), int(size));
0624         else if (size >= 1)
0625             drawPoint(pos.x(), pos.y());
0626     }
0627 }
0628 
0629 bool SkyQPainter::drawConstellationArtImage(ConstellationsArt *obj)
0630 {
0631     double zoom = Options::zoomFactor();
0632 
0633     bool visible = false;
0634     obj->EquatorialToHorizontal(KStarsData::Instance()->lst(),
0635                                 KStarsData::Instance()->geo()->lat());
0636     QPointF constellationmidpoint = m_proj->toScreen(obj, true, &visible);
0637 
0638     if (!visible || !m_proj->onScreen(constellationmidpoint))
0639         return false;
0640 
0641     //qDebug() << Q_FUNC_INFO << "o->pa() " << obj->pa();
0642     float positionangle =
0643         m_proj->findPA(obj, constellationmidpoint.x(), constellationmidpoint.y());
0644     //qDebug() << Q_FUNC_INFO << " final PA " << positionangle;
0645 
0646     float w = obj->getWidth() * 60 * dms::PI * zoom / 10800;
0647     float h = obj->getHeight() * 60 * dms::PI * zoom / 10800;
0648 
0649     save();
0650 
0651     setRenderHint(QPainter::SmoothPixmapTransform);
0652 
0653     translate(constellationmidpoint);
0654     rotate(positionangle);
0655     setOpacity(0.7);
0656     drawImage(QRectF(-0.5 * w, -0.5 * h, w, h), obj->image());
0657     setOpacity(1);
0658 
0659     setRenderHint(QPainter::SmoothPixmapTransform, false);
0660     restore();
0661     return true;
0662 }
0663 
0664 #ifdef HAVE_INDI
0665 bool SkyQPainter::drawMosaicPanel(MosaicTiles *obj)
0666 {
0667     bool visible = false;
0668     obj->EquatorialToHorizontal(KStarsData::Instance()->lst(),
0669                                 KStarsData::Instance()->geo()->lat());
0670     QPointF tileMid = m_proj->toScreen(obj, true, &visible);
0671 
0672     if (!visible || !m_proj->onScreen(tileMid) || !obj->isValid())
0673         return false;
0674 
0675     //double northRotation = m_proj->findNorthPA(obj, tileMid.x(), tileMid.y())
0676 
0677     // convert 0 to +180 EAST, and 0 to -180 WEST to 0 to 360 CCW
0678     auto PA = (obj->positionAngle() < 0) ? obj->positionAngle() + 360 : obj->positionAngle();
0679     auto finalPA =  m_proj->findNorthPA(obj, tileMid.x(), tileMid.y()) - PA;
0680 
0681     save();
0682     translate(tileMid.toPoint());
0683     rotate(finalPA);
0684     obj->draw(this);
0685     restore();
0686 
0687     return true;
0688 }
0689 #endif
0690 
0691 bool SkyQPainter::drawHips(bool useCache)
0692 {
0693     int w             = viewport().width();
0694     int h             = viewport().height();
0695 
0696     if (useCache && m_HiPSImage)
0697     {
0698         drawImage(viewport(), *m_HiPSImage.data());
0699         return true;
0700     }
0701     else
0702     {
0703         m_HiPSImage.reset(new QImage(w, h, QImage::Format_ARGB32_Premultiplied));
0704         bool rendered     = m_hipsRender->render(w, h, m_HiPSImage.data(), m_proj);
0705         if (rendered)
0706             drawImage(viewport(), *m_HiPSImage.data());
0707         return rendered;
0708     }
0709 }
0710 
0711 bool SkyQPainter::drawTerrain(bool useCache)
0712 {
0713     // TODO
0714     Q_UNUSED(useCache);
0715     int w                     = viewport().width();
0716     int h                     = viewport().height();
0717     QImage *terrainImage      = new QImage(w, h, QImage::Format_ARGB32_Premultiplied);
0718     TerrainRenderer *renderer = TerrainRenderer::Instance();
0719     bool rendered             = renderer->render(w, h, terrainImage, m_proj);
0720     if (rendered)
0721         drawImage(viewport(), *terrainImage);
0722 
0723     delete (terrainImage);
0724     return rendered;
0725 }
0726 
0727 void SkyQPainter::drawCatalogObjectImage(const QPointF &pos, const CatalogObject &obj,
0728         float positionAngle)
0729 {
0730     const auto &image = obj.image();
0731 
0732     if (!image.first)
0733         return;
0734 
0735     double zoom = Options::zoomFactor();
0736     double w    = obj.a() * dms::PI * zoom / 10800.0;
0737     double h    = obj.e() * w;
0738 
0739     save();
0740     translate(pos);
0741     rotate(positionAngle);
0742     drawImage(QRectF(-0.5 * w, -0.5 * h, w, h), image.second);
0743     restore();
0744 }
0745 
0746 bool SkyQPainter::drawCatalogObject(const CatalogObject &obj)
0747 {
0748     if (!m_proj->checkVisibility(&obj))
0749         return false;
0750 
0751     bool visible = false;
0752     QPointF pos  = m_proj->toScreen(&obj, true, &visible);
0753     if (!visible || !m_proj->onScreen(pos))
0754         return false;
0755 
0756     // if size is 0.0 set it to 1.0, this are normally stars (type 0 and 1)
0757     // if we use size 0.0 the star wouldn't be drawn
0758     float majorAxis = obj.a();
0759     if (majorAxis == 0.0)
0760     {
0761         majorAxis = 1.0;
0762     }
0763 
0764     float size = majorAxis * dms::PI * Options::zoomFactor() / 10800.0;
0765 
0766     const auto positionAngle =
0767         m_proj->findNorthPA(&obj, pos.x(), pos.y()) - obj.pa() + 90;
0768 
0769     // Draw image
0770     if (Options::showInlineImages() && Options::zoomFactor() > 5. * MINZOOM &&
0771             !Options::showHIPS())
0772         drawCatalogObjectImage(pos, obj, positionAngle);
0773 
0774     // Draw Symbol
0775     drawDeepSkySymbol(pos, obj.type(), size, obj.e(), positionAngle);
0776 
0777     return true;
0778 }
0779 
0780 void SkyQPainter::drawDeepSkySymbol(const QPointF &pos, int type, float size, float e,
0781                                     float positionAngle)
0782 {
0783     float x    = pos.x();
0784     float y    = pos.y();
0785     float zoom = Options::zoomFactor();
0786 
0787     int isize = int(size);
0788 
0789     float dx1 = -0.5 * size;
0790     float dx2 = 0.5 * size;
0791     float dy1 = -1.0 * e * size / 2.;
0792     float dy2 = e * size / 2.;
0793     float x1  = x + dx1;
0794     float x2  = x + dx2;
0795     float y1  = y + dy1;
0796     float y2  = y + dy2;
0797 
0798     float dxa = -size / 4.;
0799     float dxb = size / 4.;
0800     float dya = -1.0 * e * size / 4.;
0801     float dyb = e * size / 4.;
0802     float xa  = x + dxa;
0803     float xb  = x + dxb;
0804     float ya  = y + dya;
0805     float yb  = y + dyb;
0806 
0807     QString color;
0808 
0809     float psize;
0810 
0811     QBrush tempBrush;
0812 
0813     std::function<void(float, float, float, float)> lambdaDrawEllipse;
0814     std::function<void(float, float, float, float)> lambdaDrawLine;
0815     std::function<void(float, float, float, float)> lambdaDrawCross;
0816 
0817     if (Options::useAntialias())
0818     {
0819         lambdaDrawEllipse = [this](float x, float y, float width, float height)
0820         {
0821             drawEllipse(QRectF(x, y, width, height));
0822         };
0823         lambdaDrawLine = [this](float x1, float y1, float x2, float y2)
0824         {
0825             drawLine(QLineF(x1, y1, x2, y2));
0826         };
0827         lambdaDrawCross = [this](float centerX, float centerY, float sizeX, float sizeY)
0828         {
0829             drawLine(
0830                 QLineF(centerX - sizeX / 2., centerY, centerX + sizeX / 2., centerY));
0831             drawLine(
0832                 QLineF(centerX, centerY - sizeY / 2., centerX, centerY + sizeY / 2.));
0833         };
0834     }
0835     else
0836     {
0837         lambdaDrawEllipse = [this](float x, float y, float width, float height)
0838         {
0839             drawEllipse(QRect(x, y, width, height));
0840         };
0841         lambdaDrawLine = [this](float x1, float y1, float x2, float y2)
0842         {
0843             drawLine(QLine(x1, y1, x2, y2));
0844         };
0845         lambdaDrawCross = [this](float centerX, float centerY, float sizeX, float sizeY)
0846         {
0847             drawLine(QLine(centerX - sizeX / 2., centerY, centerX + sizeX / 2., centerY));
0848             drawLine(QLine(centerX, centerY - sizeY / 2., centerX, centerY + sizeY / 2.));
0849         };
0850     }
0851 
0852     switch ((SkyObject::TYPE)type)
0853     {
0854         case SkyObject::STAR:
0855         case SkyObject::CATALOG_STAR: //catalog star
0856             //Some NGC/IC objects are stars...changed their type to 1 (was double star)
0857             if (size < 2.)
0858                 size = 2.;
0859             lambdaDrawEllipse(x - size / 2., y - size / 2., size, size);
0860             break;
0861         case SkyObject::PLANET: //Planet
0862             break;
0863         case SkyObject::OPEN_CLUSTER:  //Open cluster; draw circle of points
0864         case SkyObject::ASTERISM: // Asterism
0865         {
0866             tempBrush = brush();
0867             color     = pen().color().name();
0868             setBrush(pen().color());
0869             psize = 2.;
0870             if (size > 50.)
0871                 psize *= 2.;
0872             if (size > 100.)
0873                 psize *= 2.;
0874             auto putDot = [psize, &lambdaDrawEllipse](float x, float y)
0875             {
0876                 lambdaDrawEllipse(x - psize / 2., y - psize / 2., psize, psize);
0877             };
0878             putDot(xa, y1);
0879             putDot(xb, y1);
0880             putDot(xa, y2);
0881             putDot(xb, y2);
0882             putDot(x1, ya);
0883             putDot(x1, yb);
0884             putDot(x2, ya);
0885             putDot(x2, yb);
0886             setBrush(tempBrush);
0887             break;
0888         }
0889         case SkyObject::GLOBULAR_CLUSTER: //Globular Cluster
0890             if (size < 2.)
0891                 size = 2.;
0892             save();
0893             translate(x, y);
0894             color = pen().color().name();
0895             rotate(positionAngle); //rotate the coordinate system
0896             lambdaDrawEllipse(dx1, dy1, size, e * size);
0897             lambdaDrawCross(0, 0, size, e * size);
0898             restore(); //reset coordinate system
0899             break;
0900 
0901         case SkyObject::GASEOUS_NEBULA:  //Gaseous Nebula
0902         case SkyObject::DARK_NEBULA: // Dark Nebula
0903             save();
0904             translate(x, y);
0905             rotate(positionAngle); //rotate the coordinate system
0906             color = pen().color().name();
0907             lambdaDrawLine(dx1, dy1, dx2, dy1);
0908             lambdaDrawLine(dx2, dy1, dx2, dy2);
0909             lambdaDrawLine(dx2, dy2, dx1, dy2);
0910             lambdaDrawLine(dx1, dy2, dx1, dy1);
0911             restore(); //reset coordinate system
0912             break;
0913         case SkyObject::PLANETARY_NEBULA: //Planetary Nebula
0914             if (size < 2.)
0915                 size = 2.;
0916             save();
0917             translate(x, y);
0918             rotate(positionAngle); //rotate the coordinate system
0919             color = pen().color().name();
0920             lambdaDrawEllipse(dx1, dy1, size, e * size);
0921             lambdaDrawLine(0., dy1, 0., dy1 - e * size / 2.);
0922             lambdaDrawLine(0., dy2, 0., dy2 + e * size / 2.);
0923             lambdaDrawLine(dx1, 0., dx1 - size / 2., 0.);
0924             lambdaDrawLine(dx2, 0., dx2 + size / 2., 0.);
0925             restore(); //reset coordinate system
0926             break;
0927         case SkyObject::SUPERNOVA_REMNANT: //Supernova remnant // FIXME: Why is SNR drawn different from a gaseous nebula?
0928             save();
0929             translate(x, y);
0930             rotate(positionAngle); //rotate the coordinate system
0931             color = pen().color().name();
0932             lambdaDrawLine(0., dy1, dx2, 0.);
0933             lambdaDrawLine(dx2, 0., 0., dy2);
0934             lambdaDrawLine(0., dy2, dx1, 0.);
0935             lambdaDrawLine(dx1, 0., 0., dy1);
0936             restore(); //reset coordinate system
0937             break;
0938         case SkyObject::GALAXY:  //Galaxy
0939         case SkyObject::QUASAR: // Quasar
0940             color = pen().color().name();
0941             if (size < 1. && zoom > 20 * MINZOOM)
0942                 size = 3.; //force ellipse above zoomFactor 20
0943             if (size < 1. && zoom > 5 * MINZOOM)
0944                 size = 1.; //force points above zoomFactor 5
0945             if (size > 2.)
0946             {
0947                 save();
0948                 translate(x, y);
0949                 rotate(positionAngle); //rotate the coordinate system
0950                 lambdaDrawEllipse(dx1, dy1, size, e * size);
0951                 restore(); //reset coordinate system
0952             }
0953             else if (size > 0.)
0954             {
0955                 drawPoint(QPointF(x, y));
0956             }
0957             break;
0958         case SkyObject::GALAXY_CLUSTER: // Galaxy cluster - draw a dashed circle
0959         {
0960             tempBrush = brush();
0961             setBrush(QBrush());
0962             color = pen().color().name();
0963             save();
0964             translate(x, y);
0965             rotate(positionAngle); //rotate the coordinate system
0966             QPen newPen = pen();
0967             newPen.setStyle(Qt::DashLine);
0968             setPen(newPen);
0969             lambdaDrawEllipse(dx1, dy1, size, e * size);
0970             restore();
0971             setBrush(tempBrush);
0972             break;
0973         }
0974             default
0975 : // Unknown object or something we don't know how to draw. Just draw an ellipse with a ?-mark
0976             color = pen().color().name();
0977             if (size < 1. && zoom > 20 * MINZOOM)
0978                 size = 3.; //force ellipse above zoomFactor 20
0979             if (size < 1. && zoom > 5 * MINZOOM)
0980                 size = 1.; //force points above zoomFactor 5
0981             if (size > 2.)
0982             {
0983                 save();
0984                 QFont f             = font();
0985                 const QString qMark = " ? ";
0986 
0987 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
0988                 double scaleFactor = 0.8 * size / fontMetrics().horizontalAdvance(qMark);
0989 #else
0990                 double scaleFactor = 0.8 * size / fontMetrics().width(qMark);
0991 #endif
0992 
0993                 f.setPointSizeF(f.pointSizeF() * scaleFactor);
0994                 setFont(f);
0995                 translate(x, y);
0996                 rotate(positionAngle); //rotate the coordinate system
0997                 lambdaDrawEllipse(dx1, dy1, size, e * size);
0998                 if (Options::useAntialias())
0999                     drawText(QRectF(dx1, dy1, size, e * size), Qt::AlignCenter, qMark);
1000                 else
1001                 {
1002                     int idx1 = int(dx1);
1003                     int idy1 = int(dy1);
1004                     drawText(QRect(idx1, idy1, isize, int(e * size)), Qt::AlignCenter,
1005                              qMark);
1006                 }
1007                 restore(); //reset coordinate system (and font?)
1008             }
1009             else if (size > 0.)
1010             {
1011                 if (Options::useAntialias())
1012                     drawPoint(QPointF(x, y));
1013                 else
1014                     drawPoint(QPoint(x, y));
1015             }
1016     }
1017 }
1018 
1019 void SkyQPainter::drawObservingList(const QList<SkyObject *> &obs)
1020 {
1021     foreach (SkyObject *obj, obs)
1022     {
1023         bool visible = false;
1024         QPointF o    = m_proj->toScreen(obj, true, &visible);
1025         if (!visible || !m_proj->onScreen(o))
1026             continue;
1027 
1028         float size = 20.;
1029         float x1   = o.x() - 0.5 * size;
1030         float y1   = o.y() - 0.5 * size;
1031         drawArc(QRectF(x1, y1, size, size), -60 * 16, 120 * 16);
1032         drawArc(QRectF(x1, y1, size, size), 120 * 16, 120 * 16);
1033     }
1034 }
1035 
1036 void SkyQPainter::drawFlags()
1037 {
1038     KStarsData *data = KStarsData::Instance();
1039     std::shared_ptr<SkyPoint> point;
1040     QImage image;
1041     bool visible = false;
1042     QPointF pos;
1043 
1044     for (int i = 0; i < data->skyComposite()->flags()->size(); i++)
1045     {
1046         point = data->skyComposite()->flags()->pointList().at(i);
1047         image = data->skyComposite()->flags()->image(i);
1048 
1049         // Set Horizontal coordinates
1050         point->EquatorialToHorizontal(data->lst(), data->geo()->lat());
1051 
1052         // Get flag position on screen
1053         pos = m_proj->toScreen(point.get(), true, &visible);
1054 
1055         // Return if flag is not visible
1056         if (!visible || !m_proj->onScreen(pos))
1057             continue;
1058 
1059         // Draw flag image
1060         drawImage(pos.x() - 0.5 * image.width(), pos.y() - 0.5 * image.height(), image);
1061 
1062         // Draw flag label
1063         setPen(data->skyComposite()->flags()->labelColor(i));
1064         setFont(QFont("Helvetica", 10, QFont::Bold));
1065         drawText(pos.x() + 10, pos.y() - 10, data->skyComposite()->flags()->label(i));
1066     }
1067 }
1068 
1069 void SkyQPainter::drawHorizon(bool filled, SkyPoint *labelPoint, bool *drawLabel)
1070 {
1071     QVector<Eigen::Vector2f> ground = m_proj->groundPoly(labelPoint, drawLabel);
1072     if (ground.size())
1073     {
1074         QPolygonF groundPoly(ground.size());
1075         for (int i = 0; i < ground.size(); ++i)
1076             groundPoly[i] = KSUtils::vecToPoint(ground[i]);
1077         if (filled)
1078             drawPolygon(groundPoly);
1079         else
1080         {
1081             groundPoly.append(groundPoly.first());
1082             drawPolyline(groundPoly);
1083         }
1084     }
1085 }
1086 
1087 bool SkyQPainter::drawSatellite(Satellite *sat)
1088 {
1089     if (!m_proj->checkVisibility(sat))
1090         return false;
1091 
1092     QPointF pos;
1093     bool visible = false;
1094 
1095     //sat->HorizontalToEquatorial( data->lst(), data->geo()->lat() );
1096 
1097     pos = m_proj->toScreen(sat, true, &visible);
1098 
1099     if (!visible || !m_proj->onScreen(pos))
1100         return false;
1101 
1102     if (Options::drawSatellitesLikeStars())
1103     {
1104         drawPointSource(pos, 3.5, 'B');
1105     }
1106     else
1107     {
1108         if (sat->isVisible())
1109             drawPixmap(QPoint(pos.x() - 15, pos.y() - 11), *visibleSatPixmap);
1110         else
1111             drawPixmap(QPoint(pos.x() - 15, pos.y() - 11), *invisibleSatPixmap);
1112 
1113         //drawPixmap(pos, *genericSatPixmap);
1114         /*drawLine( QPoint( pos.x() - 0.5, pos.y() - 0.5 ), QPoint( pos.x() + 0.5, pos.y() - 0.5 ) );
1115         drawLine( QPoint( pos.x() + 0.5, pos.y() - 0.5 ), QPoint( pos.x() + 0.5, pos.y() + 0.5 ) );
1116         drawLine( QPoint( pos.x() + 0.5, pos.y() + 0.5 ), QPoint( pos.x() - 0.5, pos.y() + 0.5 ) );
1117         drawLine( QPoint( pos.x() - 0.5, pos.y() + 0.5 ), QPoint( pos.x() - 0.5, pos.y() - 0.5 ) );*/
1118     }
1119 
1120     return true;
1121 
1122     //if ( Options::showSatellitesLabels() )
1123     //data->skyComposite()->satellites()->drawLabel( sat, pos );
1124 }
1125 
1126 bool SkyQPainter::drawSupernova(Supernova *sup)
1127 {
1128     KStarsData *data = KStarsData::Instance();
1129     if (!m_proj->checkVisibility(sup))
1130     {
1131         return false;
1132     }
1133 
1134     bool visible = false;
1135     QPointF pos  = m_proj->toScreen(sup, true, &visible);
1136     //qDebug()<<"sup->ra() = "<<(sup->ra()).toHMSString()<<"sup->dec() = "<<sup->dec().toDMSString();
1137     //qDebug()<<"pos = "<<pos<<"m_proj->onScreen(pos) = "<<m_proj->onScreen(pos);
1138     if (!visible || !m_proj->onScreen(pos))
1139         return false;
1140 
1141     setPen(data->colorScheme()->colorNamed("SupernovaColor"));
1142     //qDebug()<<"Here";
1143     drawLine(QPoint(pos.x() - 2.0, pos.y()), QPoint(pos.x() + 2.0, pos.y()));
1144     drawLine(QPoint(pos.x(), pos.y() - 2.0), QPoint(pos.x(), pos.y() + 2.0));
1145     return true;
1146 }