File indexing completed on 2025-04-27 06:43:16
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 }