File indexing completed on 2024-04-21 14:47:14

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