Kstars

fitsview.cpp
1/*
2 SPDX-FileCopyrightText: 2003-2017 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 SPDX-FileCopyrightText: 2016-2017 Robert Lancaster <rlancaste@gmail.com>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "config-kstars.h"
9#include "fitsview.h"
10
11#include "fitsdata.h"
12#include "fitslabel.h"
13#include "hips/hipsfinder.h"
14#include "kstarsdata.h"
15
16#include "ksutils.h"
17#include "Options.h"
18#include "skymap.h"
19
20#include "stretch.h"
21
22#ifdef HAVE_STELLARSOLVER
23#include "ekos/auxiliary/stellarsolverprofileeditor.h"
24#endif
25
26#ifdef HAVE_INDI
27#include "basedevice.h"
28#include "indi/indilistener.h"
29#endif
30
31#include <KActionCollection>
32#include <KLocalizedString>
33
34#include <QtConcurrent>
35#include <QScrollBar>
36#include <QToolBar>
37#include <QGraphicsOpacityEffect>
38#include <QApplication>
39#include <QImageReader>
40#include <QGestureEvent>
41#include <QMutexLocker>
42#include <QElapsedTimer>
43
44#ifndef _WIN32
45#include <unistd.h>
46#endif
47
48#define BASE_OFFSET 0
49#define ZOOM_DEFAULT 100.0f
50#define ZOOM_MIN 10
51// ZOOM_MAX is adjusted in the constructor if the amount of physical memory is known.
52#define ZOOM_MAX 300
53#define ZOOM_LOW_INCR 10
54#define ZOOM_HIGH_INCR 50
55#define FONT_SIZE 14
56
57namespace
58{
59
60// Derive the Green and Blue stretch parameters from their previous values and the
61// changes made to the Red parameters. We apply the same offsets used for Red to the
62// other channels' parameters, but clip them.
63void ComputeGBStretchParams(const StretchParams &newParams, StretchParams* params)
64{
65 float shadow_diff = newParams.grey_red.shadows - params->grey_red.shadows;
66 float highlight_diff = newParams.grey_red.highlights - params->grey_red.highlights;
67 float midtones_diff = newParams.grey_red.midtones - params->grey_red.midtones;
68
69 params->green.shadows = params->green.shadows + shadow_diff;
70 params->green.shadows = KSUtils::clamp(params->green.shadows, 0.0f, 1.0f);
71 params->green.highlights = params->green.highlights + highlight_diff;
72 params->green.highlights = KSUtils::clamp(params->green.highlights, 0.0f, 1.0f);
73 params->green.midtones = params->green.midtones + midtones_diff;
74 params->green.midtones = std::max(params->green.midtones, 0.0f);
75
76 params->blue.shadows = params->blue.shadows + shadow_diff;
77 params->blue.shadows = KSUtils::clamp(params->blue.shadows, 0.0f, 1.0f);
78 params->blue.highlights = params->blue.highlights + highlight_diff;
79 params->blue.highlights = KSUtils::clamp(params->blue.highlights, 0.0f, 1.0f);
80 params->blue.midtones = params->blue.midtones + midtones_diff;
81 params->blue.midtones = std::max(params->blue.midtones, 0.0f);
82}
83
84} // namespace
85
86// Runs the stretch checking the variables to see which parameters to use.
87// We call stretch even if we're not stretching, as the stretch code still
88// converts the image to the uint8 output image which will be displayed.
89// In that case, it will use an identity stretch.
90void FITSView::doStretch(QImage *outputImage)
91{
92 if (outputImage->isNull() || m_ImageData.isNull())
93 return;
94 Stretch stretch(static_cast<int>(m_ImageData->width()),
95 static_cast<int>(m_ImageData->height()),
96 m_ImageData->channels(), m_ImageData->dataType());
97
98 StretchParams tempParams;
99 if (!stretchImage)
100 tempParams = StretchParams(); // Keeping it linear
101 else if (autoStretch)
102 {
103 // Compute new auto-stretch params.
104 stretchParams = stretch.computeParams(m_ImageData->getImageBuffer());
105 emit newStretch(stretchParams);
106 tempParams = stretchParams;
107 }
108 else
109 // Use the existing stretch params.
110 tempParams = stretchParams;
111
112 stretch.setParams(tempParams);
113 stretch.run(m_ImageData->getImageBuffer(), outputImage, m_PreviewSampling);
114}
115
116// Store stretch parameters, and turn on stretching if it isn't already on.
117void FITSView::setStretchParams(const StretchParams &params)
118{
119 if (m_ImageData->channels() == 3)
120 ComputeGBStretchParams(params, &stretchParams);
121
122 stretchParams.grey_red = params.grey_red;
123 stretchParams.grey_red.shadows = std::max(stretchParams.grey_red.shadows, 0.0f);
124 stretchParams.grey_red.highlights = std::max(stretchParams.grey_red.highlights, 0.0f);
125 stretchParams.grey_red.midtones = std::max(stretchParams.grey_red.midtones, 0.0f);
126
127 autoStretch = false;
128 stretchImage = true;
129
130 if (m_ImageFrame && rescale(ZOOM_KEEP_LEVEL))
131 {
132 m_QueueUpdate = true;
133 updateFrame(true);
134 }
135}
136
137// Turn on or off stretching, and if on, use whatever parameters are currently stored.
138void FITSView::setStretch(bool onOff)
139{
140 if (stretchImage != onOff)
141 {
142 stretchImage = onOff;
143 if (m_ImageFrame && rescale(ZOOM_KEEP_LEVEL))
144 {
145 m_QueueUpdate = true;
146 updateFrame(true);
147 }
148 }
149}
150
151// Turn on stretching, using automatically generated parameters.
152void FITSView::setAutoStretchParams()
153{
154 stretchImage = true;
155 autoStretch = true;
156 if (m_ImageFrame && rescale(ZOOM_KEEP_LEVEL))
157 {
158 m_QueueUpdate = true;
159 updateFrame(true);
160 }
161}
162
163FITSView::FITSView(QWidget * parent, FITSMode fitsMode, FITSScale filterType) : QScrollArea(parent), m_ZoomFactor(1.2)
164{
165 static const QRegularExpression re("[-{}]");
166
167 // Set UUID for each view
169 uuid = uuid.remove(re);
170 setObjectName(uuid);
171
172 // stretchImage is whether to stretch or not--the stretch may or may not use automatically generated parameters.
173 // The user may enter his/her own.
174 stretchImage = Options::autoStretch();
175 // autoStretch means use automatically-generated parameters. This is the default, unless the user overrides
176 // by adjusting the stretchBar's sliders.
177 autoStretch = true;
178
179 // Adjust the maximum zoom according to the amount of memory.
180 // There have been issues with users running out system memory because of zoom memory.
181 // Note: this is not currently image dependent. It's possible, but not implemented,
182 // to allow for more zooming on smaller images.
183 zoomMax = ZOOM_MAX;
184
185#if defined (Q_OS_LINUX) || defined (Q_OS_MACOS)
186 const long numPages = sysconf(_SC_PAGESIZE);
187 const long pageSize = sysconf(_SC_PHYS_PAGES);
188
189 // _SC_PHYS_PAGES "may not be standard" http://man7.org/linux/man-pages/man3/sysconf.3.html
190 // If an OS doesn't support it, sysconf should return -1.
191 if (numPages > 0 && pageSize > 0)
192 {
193 // (numPages * pageSize) will likely overflow a 32bit int, so use floating point calculations.
194 const int memoryMb = numPages * (static_cast<double>(pageSize) / 1e6);
195 if (memoryMb < 2000)
196 zoomMax = 100;
197 else if (memoryMb < 4000)
198 zoomMax = 200;
199 else if (memoryMb < 8000)
200 zoomMax = 300;
201 else if (memoryMb < 16000)
202 zoomMax = 400;
203 else
204 zoomMax = 600;
205 }
206#endif
207
208 grabGesture(Qt::PinchGesture);
209
210 filter = filterType;
211 mode = fitsMode;
212
213 setBackgroundRole(QPalette::Dark);
214
215 markerCrosshair.setX(0);
216 markerCrosshair.setY(0);
217
218 setBaseSize(740, 530);
219
220 m_ImageFrame = new FITSLabel(this);
221 m_ImageFrame->setMouseTracking(true);
222 connect(m_ImageFrame, &FITSLabel::newStatus, this, &FITSView::newStatus);
223 connect(m_ImageFrame, &FITSLabel::mouseOverPixel, this, &FITSView::mouseOverPixel);
224 connect(m_ImageFrame, &FITSLabel::pointSelected, this, &FITSView::processPointSelection);
225 connect(m_ImageFrame, &FITSLabel::markerSelected, this, &FITSView::processMarkerSelection);
226 connect(m_ImageFrame, &FITSLabel::rectangleSelected, this, &FITSView::processRectangle);
227 connect(this, &FITSView::setRubberBand, m_ImageFrame, &FITSLabel::setRubberBand);
228 connect(this, &FITSView::showRubberBand, m_ImageFrame, &FITSLabel::showRubberBand);
229 connect(this, &FITSView::zoomRubberBand, m_ImageFrame, &FITSLabel::zoomRubberBand);
230
231 connect(Options::self(), &Options::HIPSOpacityChanged, this, [this]()
232 {
233 if (showHiPSOverlay)
234 {
235 m_QueueUpdate = true;
236 updateFrame();
237 }
238 });
239 connect(Options::self(), &Options::HIPSOffsetXChanged, this, [this]()
240 {
241 if (showHiPSOverlay)
242 {
243 m_QueueUpdate = true;
244 m_HiPSOverlayPixmap = QPixmap();
245 updateFrame();
246 }
247 });
248 connect(Options::self(), &Options::HIPSOffsetYChanged, this, [this]()
249 {
250 if (showHiPSOverlay)
251 {
252 m_QueueUpdate = true;
253 m_HiPSOverlayPixmap = QPixmap();
254 updateFrame();
255 }
256 });
257
258 connect(&wcsWatcher, &QFutureWatcher<bool>::finished, this, &FITSView::syncWCSState);
259
260 m_UpdateFrameTimer.setInterval(50);
261 m_UpdateFrameTimer.setSingleShot(true);
262 connect(&m_UpdateFrameTimer, &QTimer::timeout, this, [this]()
263 {
264 this->updateFrame(true);
265 });
266
267 connect(&fitsWatcher, &QFutureWatcher<bool>::finished, this, &FITSView::loadInFrame);
268
269 setCursorMode(
270 selectCursor); //This is the default mode because the Focus and Align FitsViews should not be in dragMouse mode
271
272 noImageLabel = new QLabel();
273 noImage.load(":/images/noimage.png");
274 noImageLabel->setPixmap(noImage);
275 noImageLabel->setAlignment(Qt::AlignCenter);
276 setWidget(noImageLabel);
277
278 redScopePixmap = QPixmap(":/icons/center_telescope_red.svg").scaled(32, 32, Qt::KeepAspectRatio, Qt::FastTransformation);
279 magentaScopePixmap = QPixmap(":/icons/center_telescope_magenta.svg").scaled(32, 32, Qt::KeepAspectRatio,
281
282 // Hack! This is same initialization as roiRB in FITSLabel.
283 // Otherwise initial ROI selection wouldn't have stats.
284 selectionRectangleRaw = QRect(QPoint(1, 1), QPoint(100, 100));
285}
286
287FITSView::~FITSView()
288{
289 QMutexLocker locker(&updateMutex);
290 m_UpdateFrameTimer.stop();
291 m_Suspended = true;
292 fitsWatcher.waitForFinished();
293 wcsWatcher.waitForFinished();
294}
295
296/**
297This method looks at what mouse mode is currently selected and updates the cursor to match.
298 */
299
300void FITSView::updateMouseCursor()
301{
302 if (cursorMode == dragCursor)
303 {
304 if (horizontalScrollBar()->maximum() > 0 || verticalScrollBar()->maximum() > 0)
305 {
306 if (!m_ImageFrame->getMouseButtonDown())
308 else
310 }
311 else
313 }
314 else if (cursorMode == selectCursor)
315 {
317 }
318 else if (cursorMode == scopeCursor)
319 {
320 viewport()->setCursor(QCursor(redScopePixmap, 10, 10));
321 }
322 else if (cursorMode == crosshairCursor)
323 {
324 viewport()->setCursor(QCursor(magentaScopePixmap, 10, 10));
325 }
326}
327
328/**
329This is how the mouse mode gets set.
330The default for a FITSView in a FITSViewer should be the dragMouse
331The default for a FITSView in the Focus or Align module should be the selectMouse
332The different defaults are accomplished by putting making the actual default mouseMode
333the selectMouse, but when a FITSViewer loads an image, it immediately makes it the dragMouse.
334 */
335
336void FITSView::setCursorMode(CursorMode mode)
337{
338 cursorMode = mode;
339 updateMouseCursor();
340
341 if (mode == scopeCursor && imageHasWCS())
342 {
343 if (m_ImageData->getWCSState() == FITSData::Idle && !wcsWatcher.isRunning())
344 {
345#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
346 QFuture<bool> future = QtConcurrent::run(&FITSData::loadWCS, m_ImageData.data());
347#else
348 QFuture<bool> future = QtConcurrent::run(m_ImageData.data(), &FITSData::loadWCS);
349#endif
350 wcsWatcher.setFuture(future);
351 }
352 }
353}
354
355void FITSView::resizeEvent(QResizeEvent * event)
356{
357 if (m_ImageData == nullptr && noImageLabel != nullptr)
358 {
359 noImageLabel->setPixmap(
361 noImageLabel->setFixedSize(width() - 5, height() - 5);
362 }
363
365}
366
367
368void FITSView::loadFile(const QString &inFilename)
369{
370 if (floatingToolBar != nullptr)
371 {
372 floatingToolBar->setVisible(true);
373 }
374
375 bool setBayerParams = false;
376
377 BayerParams param;
378 if ((m_ImageData != nullptr) && m_ImageData->hasDebayer())
379 {
380 setBayerParams = true;
381 m_ImageData->getBayerParams(&param);
382 }
383
384 // In case image is still loading, wait until it is done.
385 fitsWatcher.waitForFinished();
386 // In case loadWCS is still running for previous image data, let's wait until it's over
387 wcsWatcher.waitForFinished();
388
389 // delete m_ImageData;
390 // m_ImageData = nullptr;
391
392 filterStack.clear();
393 filterStack.push(FITS_NONE);
394 if (filter != FITS_NONE)
395 filterStack.push(filter);
396
397 m_ImageData.reset(new FITSData(mode), &QObject::deleteLater);
398
399 if (setBayerParams)
400 m_ImageData->setBayerParams(&param);
401
402 fitsWatcher.setFuture(m_ImageData->loadFromFile(inFilename));
403}
404
405void FITSView::clearData()
406{
407 if (!noImageLabel)
408 {
409 noImageLabel = new QLabel();
410 noImage.load(":/images/noimage.png");
411 noImageLabel->setPixmap(noImage);
412 noImageLabel->setAlignment(Qt::AlignCenter);
413 }
414
415 setWidget(noImageLabel);
416
417 m_ImageData.clear();
418}
419
420bool FITSView::loadData(const QSharedPointer<FITSData> &data)
421{
422 if (floatingToolBar != nullptr)
423 {
424 floatingToolBar->setVisible(true);
425 }
426
427 // In case loadWCS is still running for previous image data, let's wait until it's over
428 wcsWatcher.waitForFinished();
429
430 filterStack.clear();
431 filterStack.push(FITS_NONE);
432 if (filter != FITS_NONE)
433 filterStack.push(filter);
434
435 m_HiPSOverlayPixmap = QPixmap();
436
437 // Takes control of the objects passed in.
438 m_ImageData = data;
439 // set the image mask geometry
440 if (m_ImageMask != nullptr)
441 m_ImageMask->setImageGeometry(data->width(), data->height());
442
443 if (processData())
444 {
445 emit loaded();
446 return true;
447 }
448 else
449 {
450 emit failed(m_LastError);
451 return false;
452 }
453}
454
455bool FITSView::processData()
456{
457 // Set current width and height
458 if (!m_ImageData)
459 return false;
460
461 connect(m_ImageData.data(), &FITSData::dataChanged, this, [this]()
462 {
463 rescale(ZOOM_KEEP_LEVEL);
464 updateFrame();
465 });
466
467 currentWidth = m_ImageData->width();
468 currentHeight = m_ImageData->height();
469
470 int image_width = currentWidth;
471 int image_height = currentHeight;
472
473 if (!m_ImageFrame)
474 {
475 m_ImageFrame = new FITSLabel(this);
476 m_ImageFrame->setMouseTracking(true);
477 connect(m_ImageFrame, &FITSLabel::newStatus, this, &FITSView::newStatus);
478 connect(m_ImageFrame, &FITSLabel::pointSelected, this, &FITSView::processPointSelection);
479 connect(m_ImageFrame, &FITSLabel::markerSelected, this, &FITSView::processMarkerSelection);
480 }
481 m_ImageFrame->setSize(image_width, image_height);
482
483 // Init the display image
484 // JM 2020.01.08: Disabling as proposed by Hy
485 //initDisplayImage();
486
487 m_ImageData->applyFilter(filter);
488
489 double availableRAM = 0;
490 if (Options::adaptiveSampling() && (availableRAM = KSUtils::getAvailableRAM()) > 0)
491 {
492 // Possible color maximum image size
493 double max_size = image_width * image_height * 4;
494 // Ratio of image size to available RAM size
495 double ratio = max_size / availableRAM;
496
497 // Increase adaptive sampling with more limited RAM
498 if (ratio < 0.1)
499 m_AdaptiveSampling = 1;
500 else if (ratio < 0.2)
501 m_AdaptiveSampling = 2;
502 else
503 m_AdaptiveSampling = 4;
504
505 m_PreviewSampling = m_AdaptiveSampling;
506 }
507
508 // Rescale to fits window on first load
509 if (firstLoad)
510 {
511 currentZoom = 100;
512
513 if (rescale(ZOOM_FIT_WINDOW) == false)
514 {
515 m_LastError = i18n("Rescaling image failed.");
516 return false;
517 }
518
519 firstLoad = false;
520 }
521 else
522 {
523 if (rescale(ZOOM_KEEP_LEVEL) == false)
524 {
525 m_LastError = i18n("Rescaling image failed.");
526 return false;
527 }
528 }
529
531
532 // Load WCS data now if selected and image contains valid WCS header
533 if ((mode == FITS_NORMAL || mode == FITS_ALIGN) &&
534 m_ImageData->hasWCS() && m_ImageData->getWCSState() == FITSData::Idle &&
535 Options::autoWCS() &&
536 !wcsWatcher.isRunning())
537 {
538#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
539 QFuture<bool> future = QtConcurrent::run(&FITSData::loadWCS, m_ImageData.data());
540#else
541 QFuture<bool> future = QtConcurrent::run(m_ImageData.data(), &FITSData::loadWCS);
542#endif
543 wcsWatcher.setFuture(future);
544 }
545 else
546 syncWCSState();
547
548 if (isVisible())
549 emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION);
550
551 if (showStarProfile)
552 {
553 if(floatingToolBar != nullptr)
554 toggleProfileAction->setChecked(true);
555 //Need to wait till the Focus module finds stars, if its the Focus module.
556 QTimer::singleShot(100, this, SLOT(viewStarProfile()));
557 }
558
559 // Fore immediate load of frame for first load.
560 m_QueueUpdate = true;
561 updateFrame(true);
562 return true;
563}
564
565void FITSView::loadInFrame()
566{
567 // It can wind up being null if the file is manually deleted.
568 if (m_ImageData.isNull())
569 {
570 emit failed("No image file.");
571 return;
572 }
573
574 m_LastError = m_ImageData->getLastError();
575
576 // Check if the loading was OK
577 if (fitsWatcher.result() == false)
578 {
579 emit failed(m_LastError);
580 return;
581 }
582
583 // Notify if there is debayer data.
584 emit debayerToggled(m_ImageData->hasDebayer());
585
586 if (processData())
587 emit loaded();
588 else
589 emit failed(m_LastError);
590}
591
592bool FITSView::saveImage(const QString &newFilename)
593{
594 const QString ext = QFileInfo(newFilename).suffix();
595 if (QImageReader::supportedImageFormats().contains(ext.toLatin1()))
596 {
597 rawImage.save(newFilename, ext.toLatin1().constData());
598 return true;
599 }
600
601 return m_ImageData->saveImage(newFilename);
602}
603
604FITSView::CursorMode FITSView::getCursorMode()
605{
606 return cursorMode;
607}
608
609#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
610void FITSView::enterEvent(QEnterEvent *event)
611#else
612void FITSView::enterEvent(QEvent * event)
613#endif
614{
615 Q_UNUSED(event)
616
617 if (floatingToolBar && m_ImageData)
618 {
620 floatingToolBar->setGraphicsEffect(eff);
621 QPointer<QPropertyAnimation> a = new QPropertyAnimation(eff, "opacity");
622 a->setDuration(500);
623 a->setStartValue(0.2);
624 a->setEndValue(1);
625 a->setEasingCurve(QEasingCurve::InBack);
627 }
628}
629
630void FITSView::leaveEvent(QEvent * event)
631{
632 Q_UNUSED(event)
633
634 if (floatingToolBar && m_ImageData)
635 {
637 floatingToolBar->setGraphicsEffect(eff);
638 QPointer<QPropertyAnimation> a = new QPropertyAnimation(eff, "opacity");
639 a->setDuration(500);
640 a->setStartValue(1);
641 a->setEndValue(0.2);
642 a->setEasingCurve(QEasingCurve::OutBack);
644 }
645}
646
647bool FITSView::rescale(FITSZoom type)
648{
649 if (!m_ImageData)
650 return false;
651
652 int image_width = m_ImageData->width();
653 int image_height = m_ImageData->height();
654 currentWidth = image_width;
655 currentHeight = image_height;
656
657 if (isVisible())
658 emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION);
659
660 switch (type)
661 {
662 case ZOOM_FIT_WINDOW:
663 if ((image_width > width() || image_height > height()))
664 {
665 double w = baseSize().width() - BASE_OFFSET;
666 double h = baseSize().height() - BASE_OFFSET;
667
668 if (!firstLoad)
669 {
670 w = viewport()->rect().width() - BASE_OFFSET;
671 h = viewport()->rect().height() - BASE_OFFSET;
672 }
673
674 // Find the zoom level which will enclose the current FITS in the current window size
675 double zoomX = (w / static_cast<double>(currentWidth)) * 100.0;
676 double zoomY = (h / static_cast<double>(currentHeight)) * 100.0;
677
678 (zoomX < zoomY) ? currentZoom = zoomX : currentZoom = zoomY;
679
680 currentWidth = image_width * (currentZoom / ZOOM_DEFAULT);
681 currentHeight = image_height * (currentZoom / ZOOM_DEFAULT);
682
683 if (currentZoom <= ZOOM_MIN)
684 emit actionUpdated("view_zoom_out", false);
685 }
686 else
687 {
688 currentZoom = 100;
689 currentWidth = image_width;
690 currentHeight = image_height;
691 }
692 break;
693
694 case ZOOM_KEEP_LEVEL:
695 {
696 currentWidth = image_width * (currentZoom / ZOOM_DEFAULT);
697 currentHeight = image_height * (currentZoom / ZOOM_DEFAULT);
698 }
699 break;
700
701 default:
702 currentZoom = 100;
703
704 break;
705 }
706
707 initDisplayImage();
708 m_ImageFrame->setScaledContents(true);
709 doStretch(&rawImage);
710 setWidget(m_ImageFrame);
711
712 // This is needed by fitstab, even if the zoom doesn't change, to change the stretch UI.
713 emitZoom();
714 return true;
715}
716
717void FITSView::emitZoom()
718{
719 // Don't need to display full float precision. Limit to 2 decimal places at most.
720 double zoom = std::round(currentZoom * 100.0) / 100.0;
721 emit newStatus(i18nc("%1 is the value, % is the percent sign", "%1%", zoom), FITS_ZOOM);
722}
723
724void FITSView::ZoomIn()
725{
726 if (!m_ImageData)
727 return;
728
729 if (currentZoom >= ZOOM_DEFAULT && Options::limitedResourcesMode())
730 {
731 emit newStatus(i18n("Cannot zoom in further due to active limited resources mode."), FITS_MESSAGE);
732 return;
733 }
734
735 if (currentZoom < ZOOM_DEFAULT)
736 currentZoom += ZOOM_LOW_INCR;
737 else
738 currentZoom += ZOOM_HIGH_INCR;
739
740 emit actionUpdated("view_zoom_out", true);
741 if (currentZoom >= zoomMax)
742 {
743 currentZoom = zoomMax;
744 emit actionUpdated("view_zoom_in", false);
745 }
746
747 currentWidth = m_ImageData->width() * (currentZoom / ZOOM_DEFAULT);
748 currentHeight = m_ImageData->height() * (currentZoom / ZOOM_DEFAULT);
749
750 cleanUpZoom();
751
752 updateFrame(true);
753
754 emitZoom();
755 emit zoomRubberBand(getCurrentZoom() / ZOOM_DEFAULT);
756}
757
758void FITSView::ZoomOut()
759{
760 if (!m_ImageData)
761 return;
762
763 if (currentZoom <= ZOOM_DEFAULT)
764 currentZoom -= ZOOM_LOW_INCR;
765 else
766 currentZoom -= ZOOM_HIGH_INCR;
767
768 if (currentZoom <= ZOOM_MIN)
769 {
770 currentZoom = ZOOM_MIN;
771 emit actionUpdated("view_zoom_out", false);
772 }
773
774 emit actionUpdated("view_zoom_in", true);
775
776 currentWidth = m_ImageData->width() * (currentZoom / ZOOM_DEFAULT);
777 currentHeight = m_ImageData->height() * (currentZoom / ZOOM_DEFAULT);
778
779 cleanUpZoom();
780
781 updateFrame(true);
782
783 emitZoom();
784 emit zoomRubberBand(getCurrentZoom() / ZOOM_DEFAULT);
785}
786
787void FITSView::ZoomToFit()
788{
789 if (!m_ImageData)
790 return;
791
792 if (rawImage.isNull() == false)
793 {
794 rescale(ZOOM_FIT_WINDOW);
795 updateFrame(true);
796 }
797 emit zoomRubberBand(getCurrentZoom() / ZOOM_DEFAULT);
798}
799
800
801
802int FITSView::filterStars()
803{
804 return ((m_ImageMask.isNull() == false
805 && m_ImageMask->active()) ? m_ImageData->filterStars(m_ImageMask) : m_ImageData->getStarCenters().count());
806}
807
808void FITSView::setImageMask(ImageMask *mask)
809{
810 if (m_ImageMask.isNull() == false)
811 {
812 // copy image geometry from the old mask before deleting it
813 if (mask != nullptr)
814 mask->setImageGeometry(m_ImageMask->width(), m_ImageMask->height());
815 }
816
817 m_ImageMask.reset(mask);
818}
819
820// isImageLarge() returns whether we use the large-image rendering strategy or the small-image strategy.
821// See the comment below in getScale() for details.
822bool FITSView::isLargeImage()
823{
824 constexpr int largeImageNumPixels = 1000 * 1000;
825 return rawImage.width() * rawImage.height() >= largeImageNumPixels;
826}
827
828// getScale() is related to the image and overlay rendering strategy used.
829// If we're using a pixmap appropriate for a large image, where we draw and render on a pixmap that's the image size
830// and we let the QLabel deal with scaling and zooming, then the scale is 1.0.
831// With smaller images, where memory use is not as severe, we create a pixmap that's the size of the scaled image
832// and get scale returns the ratio of that pixmap size to the image size.
833double FITSView::getScale()
834{
835 return (isLargeImage() ? 1.0 : currentZoom / ZOOM_DEFAULT) / m_PreviewSampling;
836}
837
838// scaleSize() is only used with the large-image rendering strategy. It may increase the line
839// widths or font sizes, as we draw lines and render text on the full image and when zoomed out,
840// these sizes may be too small.
841double FITSView::scaleSize(double size)
842{
843 if (!isLargeImage())
844 return size;
845 return (currentZoom > 100.0 ? size : std::round(size * 100.0 / currentZoom)) / m_PreviewSampling;
846}
847
848void FITSView::updateFrame(bool now)
849{
850 QMutexLocker locker(&updateMutex);
851
852 // Do not process if suspended.
853 if (m_Suspended)
854 return;
855
856 // JM 2021-03-13: This timer is used to throttle updateFrame calls to improve performance
857 // If after 250ms no further update frames are called, then the actual update is triggered.
858 // JM 2021-03-16: When stretching in progress, immediately execute so that the user see the changes
859 // in real time
860 if (now)
861 {
862 if (toggleStretchAction)
863 toggleStretchAction->setChecked(stretchImage);
864
865 // We employ two schemes for managing the image and its overlays, depending on the size of the image
866 // and whether we need to therefore conserve memory. The small-image strategy explicitly scales up
867 // the image, and writes overlays on the scaled pixmap. The large-image strategy uses a pixmap that's
868 // the size of the image itself, never scaling that up.
869 if (isLargeImage())
870 updateFrameLargeImage();
871 else
872 updateFrameSmallImage();
873
874 if (m_QueueUpdate && m_StretchingInProgress == false)
875 {
876 m_QueueUpdate = false;
877 emit updated();
878 }
879 }
880 else
881 m_UpdateFrameTimer.start();
882}
883
884
885bool FITSView::initDisplayPixmap(QImage &image, float scale)
886{
887 ImageMosaicMask *mask = dynamic_cast<ImageMosaicMask *>(m_ImageMask.get());
888
889 // if no mosaic should be created, simply convert the original image
890 if (mask == nullptr)
891 return displayPixmap.convertFromImage(image);
892
893 // check image geometry, sincd scaling could have changed it
894 // create the 3x3 mosaic
895 int width = mask->tileWidth() * mask->width() / 100;
896 int space = mask->space();
897 // create a new all black pixmap with mosaic size
898 displayPixmap = QPixmap((3 * width + 2 * space) * scale, (3 * width + 2 * space) * scale);
899 displayPixmap.fill(Qt::black);
900
901 QPainter painter(&displayPixmap);
902 int pos = 0;
903 // paint tiles
904 for (QRect tile : mask->tiles())
905 {
906 const int posx = pos % 3;
907 const int posy = pos++ / 3;
908 const int tilewidth = width * scale;
909 QRectF source(tile.x() * scale, tile.y()*scale, tilewidth, tilewidth);
910 QRectF target((posx * (width + space)) * scale, (posy * (width + space)) * scale, width * scale, width * scale);
911 painter.drawImage(target, image, source);
912 }
913 return true;
914}
915
916void FITSView::updateFrameLargeImage()
917{
918 if (!initDisplayPixmap(rawImage, 1.0 / m_PreviewSampling))
919 return;
920 QPainter painter(&displayPixmap);
921 // Possibly scale the fonts as we're drawing on the full image, not just the visible part of the scroll window.
922 QFont font = painter.font();
923 font.setPixelSize(scaleSize(FONT_SIZE));
924 painter.setFont(font);
925
926 drawStarRingFilter(&painter, 1.0 / m_PreviewSampling, dynamic_cast<ImageRingMask *>(m_ImageMask.get()));
927 drawOverlay(&painter, 1.0 / m_PreviewSampling);
928 m_ImageFrame->setPixmap(displayPixmap);
929 m_ImageFrame->resize(((m_PreviewSampling * currentZoom) / 100.0) * displayPixmap.size());
930}
931
932void FITSView::updateFrameSmallImage()
933{
934 QImage scaledImage = rawImage.scaled(currentWidth, currentHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
935 if (!initDisplayPixmap(scaledImage, currentZoom / ZOOM_DEFAULT))
936 return;
937
938 QPainter painter(&displayPixmap);
939 // Possibly scale the fonts as we're drawing on the full image, not just the visible part of the scroll window.
940 QFont font = painter.font();
941 drawStarRingFilter(&painter, currentZoom / ZOOM_DEFAULT, dynamic_cast<ImageRingMask *>(m_ImageMask.get()));
942 drawOverlay(&painter, currentZoom / ZOOM_DEFAULT);
943 m_ImageFrame->setPixmap(displayPixmap);
944 m_ImageFrame->resize(currentWidth, currentHeight);
945}
946
947void FITSView::drawStarRingFilter(QPainter *painter, double scale, ImageRingMask *ringMask)
948{
949 if (ringMask == nullptr || !ringMask->active())
950 return;
951
952 const double w = m_ImageData->width() * scale;
953 const double h = m_ImageData->height() * scale;
954 double const diagonal = std::sqrt(w * w + h * h) / 2;
955 int const innerRadius = std::lround(diagonal * ringMask->innerRadius());
956 int const outerRadius = std::lround(diagonal * ringMask->outerRadius());
957 QPoint const center(w / 2, h / 2);
958 painter->save();
959 painter->setPen(QPen(Qt::blue, scaleSize(1), Qt::DashLine));
960 painter->setOpacity(0.7);
962 painter->drawEllipse(center, outerRadius, outerRadius);
964 painter->drawEllipse(center, innerRadius, innerRadius);
965 painter->restore();
966}
967
968namespace
969{
970
971template <typename T>
972int drawClippingOneChannel(T *inputBuffer, QPainter *painter, int width, int height, double clipVal, double scale)
973{
974 int numClipped = 0;
975 painter->save();
976 painter->setPen(QPen(Qt::red, scale, Qt::SolidLine));
977 const T clipping = clipVal;
978 constexpr int timeoutMilliseconds = 3 * 1000;
979 QElapsedTimer timer;
980 timer.start();
981 QPoint p;
982 for (int y = 0; y < height; y++)
983 {
984 auto inputLine = inputBuffer + y * width;
985 p.setY(y);
986 for (int x = 0; x < width; x++)
987 {
988 if (*inputLine++ > clipping)
989 {
990 numClipped++;
991 const int start = x;
992 // Use this inner loop to recognize strings of clipped pixels
993 // and draw lines instead of multiple calls to drawPoints.
994 while (true)
995 {
996 if (++x >= width)
997 {
998 painter->drawLine(start, y, width - 1, y);
999 break;
1000 }
1001 if (*inputLine++ > clipping)
1002 numClipped++;
1003 else
1004 {
1005 if (x == start + 1)
1006 {
1007 p.setX(start);
1008 painter->drawPoints(&p, 1);
1009 }
1010 else
1011 painter->drawLine(start, y, x - 1, y);
1012 break;
1013 }
1014 }
1015 }
1016 }
1017 if (timer.elapsed() > timeoutMilliseconds)
1018 {
1019 painter->restore();
1020 return -1;
1021 }
1022 }
1023 painter->restore();
1024 return numClipped;
1025}
1026
1027template <typename T>
1028int drawClippingThreeChannels(T *inputBuffer, QPainter *painter, int width, int height, double clipVal, double scale)
1029{
1030 int numClipped = 0;
1031 painter->save();
1032 painter->setPen(QPen(Qt::red, scale, Qt::SolidLine));
1033 const T clipping = clipVal;
1034 constexpr int timeoutMilliseconds = 3 * 1000;
1035 QElapsedTimer timer;
1036 timer.start();
1037 QPoint p;
1038 const int size = width * height;
1039 for (int y = 0; y < height; y++)
1040 {
1041 // R, G, B input images are stored one after another.
1042 const T * inputLineR = inputBuffer + y * width;
1043 const T * inputLineG = inputLineR + size;
1044 const T * inputLineB = inputLineG + size;
1045 p.setY(y);
1046
1047 for (int x = 0; x < width; x++)
1048 {
1049 T inputR = inputLineR[x];
1050 T inputG = inputLineG[x];
1051 T inputB = inputLineB[x];
1052
1053 if (inputR > clipping || inputG > clipping || inputB > clipping)
1054 {
1055 numClipped++;
1056 const int start = x;
1057
1058 // Use this inner loop to recognize strings of clipped pixels
1059 // and draw lines instead of multiple calls to drawPoints.
1060 while (true)
1061 {
1062 if (++x >= width)
1063 {
1064 painter->drawLine(start, y, width - 1, y);
1065 break;
1066 }
1067 T inputR2 = inputLineR[x];
1068 T inputG2 = inputLineG[x];
1069 T inputB2 = inputLineB[x];
1070 if (inputR2 > clipping || inputG2 > clipping || inputB2 > clipping)
1071 numClipped++;
1072 else
1073 {
1074 if (x == start + 1)
1075 {
1076 p.setX(start);
1077 painter->drawPoints(&p, 1);
1078 }
1079 else
1080 painter->drawLine(start, y, x - 1, y);
1081 break;
1082 }
1083 }
1084 }
1085 }
1086 if (timer.elapsed() > timeoutMilliseconds)
1087 {
1088 painter->restore();
1089 return -1;
1090 }
1091 }
1092 painter->restore();
1093 return numClipped;
1094}
1095
1096template <typename T>
1097int drawClip(T *input_buffer, int num_channels, QPainter *painter, int width, int height, double clipVal, double scale)
1098{
1099 if (num_channels == 1)
1100 return drawClippingOneChannel(input_buffer, painter, width, height, clipVal, scale);
1101 else if (num_channels == 3)
1102 return drawClippingThreeChannels(input_buffer, painter, width, height, clipVal, scale);
1103 else return 0;
1104}
1105
1106} // namespace
1107
1108void FITSView::drawClipping(QPainter *painter)
1109{
1110 auto input = m_ImageData->getImageBuffer();
1111 const int height = m_ImageData->height();
1112 const int width = m_ImageData->width();
1113 const double FLOAT_CLIP = Options::clipping64KValue();
1114 const double SHORT_CLIP = Options::clipping64KValue();
1115 const double USHORT_CLIP = Options::clipping64KValue();
1116 const double BYTE_CLIP = Options::clipping256Value();
1117 switch (m_ImageData->dataType())
1118 {
1119 case TBYTE:
1120 m_NumClipped = drawClip(reinterpret_cast<uint8_t const*>(input), m_ImageData->channels(), painter, width, height, BYTE_CLIP,
1121 scaleSize(1));
1122 break;
1123 case TSHORT:
1124 m_NumClipped = drawClip(reinterpret_cast<short const*>(input), m_ImageData->channels(), painter, width, height, SHORT_CLIP,
1125 scaleSize(1));
1126 break;
1127 case TUSHORT:
1128 m_NumClipped = drawClip(reinterpret_cast<unsigned short const*>(input), m_ImageData->channels(), painter, width, height,
1129 USHORT_CLIP,
1130 scaleSize(1));
1131 break;
1132 case TLONG:
1133 m_NumClipped = drawClip(reinterpret_cast<long const*>(input), m_ImageData->channels(), painter, width, height, USHORT_CLIP,
1134 scaleSize(1));
1135 break;
1136 case TFLOAT:
1137 m_NumClipped = drawClip(reinterpret_cast<float const*>(input), m_ImageData->channels(), painter, width, height, FLOAT_CLIP,
1138 scaleSize(1));
1139 break;
1140 case TLONGLONG:
1141 m_NumClipped = drawClip(reinterpret_cast<long long const*>(input), m_ImageData->channels(), painter, width, height,
1142 USHORT_CLIP,
1143 scaleSize(1));
1144 break;
1145 case TDOUBLE:
1146 m_NumClipped = drawClip(reinterpret_cast<double const*>(input), m_ImageData->channels(), painter, width, height, FLOAT_CLIP,
1147 scaleSize(1));
1148 break;
1149 default:
1150 m_NumClipped = 0;
1151 break;
1152 }
1153 if (m_NumClipped < 0)
1154 emit newStatus(QString("Clip:failed"), FITS_CLIP);
1155 else
1156 emit newStatus(QString("Clip:%1").arg(m_NumClipped), FITS_CLIP);
1157}
1158
1159void FITSView::ZoomDefault()
1160{
1161 if (m_ImageFrame)
1162 {
1163 emit actionUpdated("view_zoom_out", true);
1164 emit actionUpdated("view_zoom_in", true);
1165
1166 currentZoom = ZOOM_DEFAULT;
1167 currentWidth = m_ImageData->width();
1168 currentHeight = m_ImageData->height();
1169
1170 updateFrame();
1171
1172 emitZoom();
1173
1174 update();
1175 }
1176}
1177
1178void FITSView::drawOverlay(QPainter * painter, double scale)
1179{
1180 painter->setRenderHint(QPainter::Antialiasing, Options::useAntialias());
1181
1182#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
1183 if (showHiPSOverlay)
1184 drawHiPSOverlay(painter, scale);
1185#endif
1186
1187 if (trackingBoxEnabled && getCursorMode() != FITSView::scopeCursor)
1188 drawTrackingBox(painter, scale);
1189
1190 if (!markerCrosshair.isNull())
1191 drawMarker(painter, scale);
1192
1193 if (showCrosshair)
1194 drawCrosshair(painter, scale);
1195
1196 if (showObjects)
1197 drawObjectNames(painter, scale);
1198
1199#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
1200 if (showEQGrid)
1201 drawEQGrid(painter, scale);
1202#endif
1203
1204 if (showPixelGrid)
1205 drawPixelGrid(painter, scale);
1206
1207 if (markStars)
1208 drawStarCentroid(painter, scale);
1209
1210 if (showClipping)
1211 drawClipping(painter);
1212
1213 if (showMagnifyingGlass)
1214 drawMagnifyingGlass(painter, scale);
1215}
1216
1217// Draws a 100% resolution image rectangle around the mouse position.
1218void FITSView::drawMagnifyingGlass(QPainter *painter, double scale)
1219{
1220 if (magnifyingGlassX >= 0 && magnifyingGlassY >= 0 &&
1221 magnifyingGlassX < m_ImageData->width() &&
1222 magnifyingGlassY < m_ImageData->height())
1223 {
1224 // Amount of magnification.
1225 constexpr double magAmount = 8;
1226 // Desired size in pixels of the magnification window.
1227 constexpr int magWindowSize = 130;
1228 // The distance from the mouse position to the magnifying glass rectangle, in the source image coordinates.
1229 const int winXOffset = magWindowSize * 10.0 / currentZoom;
1230 const int winYOffset = magWindowSize * 10.0 / currentZoom;
1231 // Size of a side of the square of input to make a window that size.
1232 const int inputDimension = magWindowSize * 100 / currentZoom;
1233 // Size of the square drawn. Not the same, necessarily as the magWindowSize,
1234 // since the output may be scaled (if isLargeImage()==true) to become screen pixels.
1235 const int outputDimension = inputDimension * scale + .99;
1236
1237 // Where the source data (to be magnified) comes from.
1238 int imgLeft = magnifyingGlassX - inputDimension / (2 * magAmount);
1239 int imgTop = magnifyingGlassY - inputDimension / (2 * magAmount);
1240
1241 // Where we'll draw the magnifying glass rectangle.
1242 int winLeft = magnifyingGlassX + winXOffset;
1243 int winTop = magnifyingGlassY + winYOffset;
1244
1245 // Normally we place the magnifying glass rectangle to the right and below the mouse curson.
1246 // However, if it would be rendered outside the image, put it on the other side.
1247 int w = rawImage.width();
1248 int h = rawImage.height();
1249 const int rightLimit = std::min(w, static_cast<int>((horizontalScrollBar()->value() + width()) * 100 / currentZoom));
1250 const int bottomLimit = std::min(h, static_cast<int>((verticalScrollBar()->value() + height()) * 100 / currentZoom));
1251 if (winLeft + winXOffset + inputDimension > rightLimit)
1252 winLeft -= (2 * winXOffset + inputDimension);
1253 if (winTop + winYOffset + inputDimension > bottomLimit)
1254 winTop -= (2 * winYOffset + inputDimension);
1255
1256 // Blacken the output where magnifying outside the source image.
1257 if ((imgLeft < 0 ) ||
1258 (imgLeft + inputDimension / magAmount >= w) ||
1259 (imgTop < 0) ||
1260 (imgTop + inputDimension / magAmount > h))
1261 {
1262 painter->setBrush(QBrush(Qt::black));
1263 painter->drawRect(winLeft * scale, winTop * scale, outputDimension, outputDimension);
1264 painter->setBrush(QBrush(Qt::transparent));
1265 }
1266
1267 // Finally, draw the magnified image.
1268 painter->drawImage(QRect(winLeft * scale, winTop * scale, outputDimension, outputDimension),
1269 rawImage,
1270 QRect(imgLeft, imgTop, inputDimension / magAmount, inputDimension / magAmount));
1271 // Draw a white border.
1272 painter->setPen(QPen(Qt::white, scaleSize(1)));
1273 painter->drawRect(winLeft * scale, winTop * scale, outputDimension, outputDimension);
1274 }
1275}
1276
1277// x,y are the image coordinates where the magnifying glass is positioned.
1278void FITSView::updateMagnifyingGlass(int x, int y)
1279{
1280 if (!m_ImageData)
1281 return;
1282
1283 magnifyingGlassX = x;
1284 magnifyingGlassY = y;
1285 if (magnifyingGlassX == -1 && magnifyingGlassY == -1)
1286 {
1287 if (showMagnifyingGlass)
1288 updateFrame(true);
1289 showMagnifyingGlass = false;
1290 }
1291 else
1292 {
1293 showMagnifyingGlass = true;
1294 updateFrame(true);
1295 }
1296}
1297
1298void FITSView::updateMode(FITSMode fmode)
1299{
1300 mode = fmode;
1301}
1302
1303void FITSView::drawMarker(QPainter * painter, double scale)
1304{
1305 painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")),
1306 scaleSize(2)));
1307 painter->setBrush(Qt::NoBrush);
1308 const float pxperdegree = scale * (57.3 / 1.8);
1309
1310 const float s1 = 0.5 * pxperdegree;
1311 const float s2 = pxperdegree;
1312 const float s3 = 2.0 * pxperdegree;
1313
1314 const float x0 = scale * markerCrosshair.x();
1315 const float y0 = scale * markerCrosshair.y();
1316 const float x1 = x0 - 0.5 * s1;
1317 const float y1 = y0 - 0.5 * s1;
1318 const float x2 = x0 - 0.5 * s2;
1319 const float y2 = y0 - 0.5 * s2;
1320 const float x3 = x0 - 0.5 * s3;
1321 const float y3 = y0 - 0.5 * s3;
1322
1323 //Draw radial lines
1324 painter->drawLine(QPointF(x1, y0), QPointF(x3, y0));
1325 painter->drawLine(QPointF(x0 + s2, y0), QPointF(x0 + 0.5 * s1, y0));
1326 painter->drawLine(QPointF(x0, y1), QPointF(x0, y3));
1327 painter->drawLine(QPointF(x0, y0 + 0.5 * s1), QPointF(x0, y0 + s2));
1328 //Draw circles at 0.5 & 1 degrees
1329 painter->drawEllipse(QRectF(x1, y1, s1, s1));
1330 painter->drawEllipse(QRectF(x2, y2, s2, s2));
1331}
1332
1333bool FITSView::drawHFR(QPainter * painter, const QString &hfr, int x, int y)
1334{
1335 QRect const boundingRect(0, 0, painter->device()->width(), painter->device()->height());
1336 QSize const hfrSize = painter->fontMetrics().size(Qt::TextSingleLine, hfr);
1337
1338 // Store the HFR text in a rect
1339 QPoint const hfrBottomLeft(x, y);
1340 QRect const hfrRect(hfrBottomLeft.x(), hfrBottomLeft.y() - hfrSize.height(), hfrSize.width(), hfrSize.height());
1341
1342 // Render the HFR text only if it can be displayed entirely
1343 if (boundingRect.contains(hfrRect))
1344 {
1345 painter->setPen(QPen(Qt::red, scaleSize(3)));
1346 painter->drawText(hfrBottomLeft, hfr);
1347 painter->setPen(QPen(Qt::red, scaleSize(2)));
1348 return true;
1349 }
1350 return false;
1351}
1352
1353
1354void FITSView::drawStarCentroid(QPainter * painter, double scale)
1355{
1356 QFont painterFont;
1357 double fontSize = painterFont.pointSizeF() * 2;
1359 if (showStarsHFR)
1360 {
1361 // If we need to print the HFR out, give an arbitrarily sized font to the painter
1362 if (isLargeImage())
1363 fontSize = scaleSize(painterFont.pointSizeF());
1364 painterFont.setPointSizeF(fontSize);
1365 painter->setFont(painterFont);
1366 }
1367
1368 painter->setPen(QPen(Qt::red, scaleSize(2)));
1369 ImageMosaicMask *mask = dynamic_cast<ImageMosaicMask *>(m_ImageMask.get());
1370
1371 for (auto const &starCenter : m_ImageData->getStarCenters())
1372 {
1373 int const w = std::round(starCenter->width) * scale;
1374
1375 // translate if a mosaic mask is present
1376 const QPointF center = (mask == nullptr) ? QPointF(starCenter->x, starCenter->y) : mask->translate(QPointF(starCenter->x,
1377 starCenter->y));
1378 // Draw a circle around the detected star.
1379 // SEP coordinates are in the center of pixels, and Qt at the boundary.
1380 const double xCoord = center.x() - 0.5;
1381 const double yCoord = center.y() - 0.5;
1382 const int xc = std::round((xCoord - starCenter->width / 2.0f) * scale);
1383 const int yc = std::round((yCoord - starCenter->width / 2.0f) * scale);
1384 const int hw = w / 2;
1385
1386 BahtinovEdge* bEdge = dynamic_cast<BahtinovEdge*>(starCenter);
1387 if (bEdge != nullptr)
1388 {
1389 // Draw lines of diffraction pattern
1390 painter->setPen(QPen(Qt::red, scaleSize(2)));
1391 painter->drawLine(bEdge->line[0].x1() * scale, bEdge->line[0].y1() * scale,
1392 bEdge->line[0].x2() * scale, bEdge->line[0].y2() * scale);
1393 painter->setPen(QPen(Qt::green, scaleSize(2)));
1394 painter->drawLine(bEdge->line[1].x1() * scale, bEdge->line[1].y1() * scale,
1395 bEdge->line[1].x2() * scale, bEdge->line[1].y2() * scale);
1396 painter->setPen(QPen(Qt::darkGreen, scaleSize(2)));
1397 painter->drawLine(bEdge->line[2].x1() * scale, bEdge->line[2].y1() * scale,
1398 bEdge->line[2].x2() * scale, bEdge->line[2].y2() * scale);
1399
1400 // Draw center circle
1401 painter->setPen(QPen(Qt::white, scaleSize(2)));
1402 painter->drawEllipse(xc, yc, w, w);
1403
1404 // Draw offset circle
1405 double factor = 15.0;
1406 QPointF offsetVector = (bEdge->offset - QPointF(center.x(), center.y())) * factor;
1407 int const xo = std::round((center.x() + offsetVector.x() - starCenter->width / 2.0f) * scale);
1408 int const yo = std::round((center.y() + offsetVector.y() - starCenter->width / 2.0f) * scale);
1409 painter->setPen(QPen(Qt::red, scaleSize(2)));
1410 painter->drawEllipse(xo, yo, w, w);
1411
1412 // Draw line between center circle and offset circle
1413 painter->setPen(QPen(Qt::red, scaleSize(2)));
1414 painter->drawLine(xc + hw, yc + hw, xo + hw, yo + hw);
1415 }
1416 else
1417 {
1418 if (!showStarsHFR)
1419 {
1420 const double radius = starCenter->HFR > 0 ? 2.0f * starCenter->HFR * scale : w;
1421 painter->drawEllipse(QPointF(xCoord * scale, yCoord * scale), radius, radius);
1422 }
1423 }
1424
1425 if (showStarsHFR)
1426 {
1427 // Ask the painter how large will the HFR text be
1428 QString const hfr = QString("%1").arg(starCenter->HFR, 0, 'f', 2);
1429 if (!drawHFR(painter, hfr, xc + w + 5, yc + w / 2))
1430 {
1431 // Try a few more time with smaller fonts;
1432 for (int i = 0; i < 10; ++i)
1433 {
1434 const double tempFontSize = painterFont.pointSizeF() - 2;
1435 if (tempFontSize <= 0) break;
1436 painterFont.setPointSizeF(tempFontSize);
1437 painter->setFont(painterFont);
1438 if (drawHFR(painter, hfr, xc + w + 5, yc + w / 2))
1439 break;
1440 }
1441 // Reset the font size.
1442 painterFont.setPointSize(fontSize);
1443 painter->setFont(painterFont);
1444 }
1445 }
1446 }
1447}
1448
1449void FITSView::drawTrackingBox(QPainter * painter, double scale)
1450{
1451 painter->setPen(QPen(Qt::green, scaleSize(2)));
1452
1453 if (trackingBox.isNull())
1454 return;
1455
1456 const int x1 = trackingBox.x() * scale;
1457 const int y1 = trackingBox.y() * scale;
1458 const int w = trackingBox.width() * scale;
1459 const int h = trackingBox.height() * scale;
1460
1461 painter->drawRect(x1, y1, w, h);
1462}
1463
1464/**
1465This Method draws a large Crosshair in the center of the image, it is like a set of axes.
1466 */
1467
1468void FITSView::drawCrosshair(QPainter * painter, double scale)
1469{
1470 if (!m_ImageData) return;
1471 const int image_width = m_ImageData->width();
1472 const int image_height = m_ImageData->height();
1473 const QPointF c = QPointF((qreal)image_width / 2 * scale, (qreal)image_height / 2 * scale);
1474 const float midX = (float)image_width / 2 * scale;
1475 const float midY = (float)image_height / 2 * scale;
1476 const float maxX = (float)image_width * scale;
1477 const float maxY = (float)image_height * scale;
1478 const float r = 50 * scale;
1479
1480 painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")), scaleSize(1)));
1481
1482 //Horizontal Line to Circle
1483 painter->drawLine(0, midY, midX - r, midY);
1484
1485 //Horizontal Line past Circle
1486 painter->drawLine(midX + r, midY, maxX, midY);
1487
1488 //Vertical Line to Circle
1489 painter->drawLine(midX, 0, midX, midY - r);
1490
1491 //Vertical Line past Circle
1492 painter->drawLine(midX, midY + r, midX, maxY);
1493
1494 //Circles
1495 painter->drawEllipse(c, r, r);
1496 painter->drawEllipse(c, r / 2, r / 2);
1497}
1498
1499/**
1500This method is intended to draw a pixel grid onto the image. It first determines useful information
1501from the image. Then it draws the axes on the image if the crosshairs are not displayed.
1502Finally it draws the gridlines so that there will be 4 Gridlines on either side of the axes.
1503Note: This has to start drawing at the center not at the edges because the center axes must
1504be in the center of the image.
1505 */
1506
1507void FITSView::drawPixelGrid(QPainter * painter, double scale)
1508{
1509 const float width = m_ImageData->width() * scale;
1510 const float height = m_ImageData->height() * scale;
1511 const float cX = width / 2;
1512 const float cY = height / 2;
1513 const float deltaX = width / 10;
1514 const float deltaY = height / 10;
1515 QFontMetrics fm(painter->font());
1516
1517 //draw the Axes
1518 painter->setPen(QPen(Qt::red, scaleSize(1)));
1519 painter->drawText(cX - 30, height - 5, QString::number((int)((cX) / scale)));
1520 QString str = QString::number((int)((cY) / scale));
1521#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1522 painter->drawText(width - (fm.width(str) + 10), cY - 5, str);
1523#else
1524 painter->drawText(width - (fm.horizontalAdvance(str) + 10), cY - 5, str);
1525#endif
1526 if (!showCrosshair)
1527 {
1528 painter->drawLine(cX, 0, cX, height);
1529 painter->drawLine(0, cY, width, cY);
1530 }
1531 painter->setPen(QPen(Qt::gray, scaleSize(1)));
1532 //Start one iteration past the Center and draw 4 lines on either side of 0
1533 for (int x = deltaX; x < cX - deltaX; x += deltaX)
1534 {
1535 painter->drawText(cX + x - 30, height - 5, QString::number((int)(cX + x) / scale));
1536 painter->drawText(cX - x - 30, height - 5, QString::number((int)(cX - x) / scale));
1537 painter->drawLine(cX - x, 0, cX - x, height);
1538 painter->drawLine(cX + x, 0, cX + x, height);
1539 }
1540 //Start one iteration past the Center and draw 4 lines on either side of 0
1541 for (int y = deltaY; y < cY - deltaY; y += deltaY)
1542 {
1543 QString str = QString::number((int)((cY + y) / scale));
1544#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1545 painter->drawText(width - (fm.width(str) + 10), cY + y - 5, str);
1546#else
1547 painter->drawText(width - (fm.horizontalAdvance(str) + 10), cY + y - 5, str);
1548#endif
1549 str = QString::number((int)((cY - y) / scale));
1550#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1551 painter->drawText(width - (fm.width(str) + 10), cY - y - 5, str);
1552#else
1553 painter->drawText(width - (fm.horizontalAdvance(str) + 10), cY - y - 5, str);
1554#endif
1555 painter->drawLine(0, cY + y, width, cY + y);
1556 painter->drawLine(0, cY - y, width, cY - y);
1557 }
1558}
1559bool FITSView::imageHasWCS()
1560{
1561 if (m_ImageData != nullptr)
1562 return m_ImageData->hasWCS();
1563 return false;
1564}
1565
1566void FITSView::drawObjectNames(QPainter * painter, double scale)
1567{
1568 painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("FITSObjectLabelColor"))));
1569 for (const auto &listObject : m_ImageData->getSkyObjects())
1570 {
1571 painter->drawRect(listObject->x() * scale - 5, listObject->y() * scale - 5, 10, 10);
1572 painter->drawText(listObject->x() * scale + 10, listObject->y() * scale + 10, listObject->skyObject()->name());
1573 }
1574}
1575
1576#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
1577void FITSView::drawHiPSOverlay(QPainter * painter, double scale)
1578{
1579 if (m_HiPSOverlayPixmap.isNull())
1580 {
1581 auto width = m_ImageData->width();
1582 auto height = m_ImageData->height();
1584 SkyPoint startPoint;
1585 SkyPoint endPoint;
1586 SkyPoint centerPoint;
1587
1588 m_ImageData->pixelToWCS(QPointF(0, 0), startPoint);
1589 m_ImageData->pixelToWCS(QPointF(width - 1, height - 1), endPoint);
1590 m_ImageData->pixelToWCS(QPointF( (width - Options::hIPSOffsetX()) / 2.0, (height - Options::hIPSOffsetY()) / 2.0),
1591 centerPoint);
1592
1593 startPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
1594 endPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
1595 centerPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
1596
1597 auto fov_radius = startPoint.angularDistanceTo(&endPoint).Degrees() / 2;
1598 QVariant PA (0.0);
1599 m_ImageData->getRecordValue("CROTA1", PA);
1600
1601 auto rotation = 180 - PA.toDouble();
1602 if (rotation > 360)
1603 rotation -= 360;
1604
1605 if (HIPSFinder::Instance()->renderFOV(&centerPoint, fov_radius, rotation, &image) == false)
1606 return;
1607 m_HiPSOverlayPixmap = QPixmap::fromImage(image);
1608 }
1609
1610 Q_UNUSED(scale);
1611 painter->setOpacity(Options::hIPSOpacity());
1612 painter->drawPixmap(0, 0, m_HiPSOverlayPixmap);
1613 painter->setOpacity(1);
1614}
1615#endif
1616
1617/**
1618This method will paint EQ Gridlines in an overlay if there is WCS data present.
1619It determines the minimum and maximum RA and DEC, then it uses that information to
1620judge which gridLines to draw. Then it calls the drawEQGridlines methods below
1621to draw gridlines at those specific RA and Dec values.
1622 */
1623
1624#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
1625void FITSView::drawEQGrid(QPainter * painter, double scale)
1626{
1627 const int image_width = m_ImageData->width();
1628 const int image_height = m_ImageData->height();
1629
1630 if (m_ImageData->hasWCS())
1631 {
1632 double maxRA = -1000;
1633 double minRA = 1000;
1634 double maxDec = -1000;
1635 double minDec = 1000;
1636 m_ImageData->findWCSBounds(minRA, maxRA, minDec, maxDec);
1637
1638 auto minDecMinutes = (int)(minDec * 12); //This will force the Dec Scale to 5 arc minutes in the loop
1639 auto maxDecMinutes = (int)(maxDec * 12);
1640
1641 auto minRAMinutes =
1642 (int)(minRA / 15.0 *
1643 120.0); //This will force the scale to 1/2 minutes of RA in the loop from 0 to 50 degrees
1644 auto maxRAMinutes = (int)(maxRA / 15.0 * 120.0);
1645
1646 double raConvert = 15 / 120.0; //This will undo the calculation above to retrieve the actual RA.
1647 double decConvert = 1.0 / 12.0; //This will undo the calculation above to retrieve the actual DEC.
1648
1649 if (maxDec > 50 || minDec < -50)
1650 {
1651 minRAMinutes =
1652 (int)(minRA / 15.0 * 60.0); //This will force the scale to 1 min of RA from 50 to 80 degrees
1653 maxRAMinutes = (int)(maxRA / 15.0 * 60.0);
1654 raConvert = 15 / 60.0;
1655 }
1656
1657 if (maxDec > 80 || minDec < -80)
1658 {
1659 minRAMinutes =
1660 (int)(minRA / 15.0 * 30); //This will force the scale to 2 min of RA from 80 to 85 degrees
1661 maxRAMinutes = (int)(maxRA / 15.0 * 30);
1662 raConvert = 15 / 30.0;
1663 }
1664 if (maxDec > 85 || minDec < -85)
1665 {
1666 minRAMinutes =
1667 (int)(minRA / 15.0 * 6); //This will force the scale to 10 min of RA from 85 to 89 degrees
1668 maxRAMinutes = (int)(maxRA / 15.0 * 6);
1669 raConvert = 15 / 6.0;
1670 }
1671 if (maxDec >= 89.25 || minDec <= -89.25)
1672 {
1673 minRAMinutes =
1674 (int)(minRA /
1675 15); //This will force the scale to whole hours of RA in the loop really close to the poles
1676 maxRAMinutes = (int)(maxRA / 15);
1677 raConvert = 15;
1678 }
1679
1680 painter->setPen(QPen(Qt::yellow));
1681
1682 QPointF pixelPoint, imagePoint, pPoint;
1683
1684 //This section draws the RA Gridlines
1685
1686 for (int targetRA = minRAMinutes; targetRA <= maxRAMinutes; targetRA++)
1687 {
1688 painter->setPen(QPen(Qt::yellow));
1689 double target = targetRA * raConvert;
1690
1691 if (eqGridPoints.count() != 0)
1692 eqGridPoints.clear();
1693
1694 double increment = std::abs((maxDec - minDec) /
1695 100.0); //This will determine how many points to use to create the RA Line
1696
1697 for (double targetDec = minDec; targetDec <= maxDec; targetDec += increment)
1698 {
1699 SkyPoint pointToGet(target / 15.0, targetDec);
1700 bool inImage = m_ImageData->wcsToPixel(pointToGet, pixelPoint, imagePoint);
1701 if (inImage)
1702 {
1703 QPointF pt(pixelPoint.x() * scale, pixelPoint.y() * scale);
1704 eqGridPoints.append(pt);
1705 }
1706 }
1707
1708 if (eqGridPoints.count() > 1)
1709 {
1710 for (int i = 1; i < eqGridPoints.count(); i++)
1711 painter->drawLine(eqGridPoints.value(i - 1), eqGridPoints.value(i));
1712 QString str = QString::number(dms(target).hour()) + "h " +
1713 QString::number(dms(target).minute()) + '\'';
1714 if (maxDec <= 50 && maxDec >= -50)
1715 str = str + " " + QString::number(dms(target).second()) + "''";
1716 QPointF pt = getPointForGridLabel(painter, str, scale);
1717 if (pt.x() != -100)
1718 painter->drawText(pt.x(), pt.y(), str);
1719 }
1720 }
1721
1722 //This section draws the DEC Gridlines
1723
1724 for (int targetDec = minDecMinutes; targetDec <= maxDecMinutes; targetDec++)
1725 {
1726 if (eqGridPoints.count() != 0)
1727 eqGridPoints.clear();
1728
1729 double increment = std::abs((maxRA - minRA) /
1730 100.0); //This will determine how many points to use to create the Dec Line
1731 double target = targetDec * decConvert;
1732
1733 for (double targetRA = minRA; targetRA <= maxRA; targetRA += increment)
1734 {
1735 SkyPoint pointToGet(targetRA / 15, targetDec * decConvert);
1736 bool inImage = m_ImageData->wcsToPixel(pointToGet, pixelPoint, imagePoint);
1737 if (inImage)
1738 {
1739 QPointF pt(pixelPoint.x() * scale, pixelPoint.y() * scale);
1740 eqGridPoints.append(pt);
1741 }
1742 }
1743 if (eqGridPoints.count() > 1)
1744 {
1745 for (int i = 1; i < eqGridPoints.count(); i++)
1746 painter->drawLine(eqGridPoints.value(i - 1), eqGridPoints.value(i));
1747 QString str = QString::number(dms(target).degree()) + "° " + QString::number(dms(target).arcmin()) + '\'';
1748 QPointF pt = getPointForGridLabel(painter, str, scale);
1749 if (pt.x() != -100)
1750 painter->drawText(pt.x(), pt.y(), str);
1751 }
1752 }
1753
1754 //This Section Draws the North Celestial Pole if present
1755 SkyPoint NCP(0, 90);
1756
1757 bool NCPtest = m_ImageData->wcsToPixel(NCP, pPoint, imagePoint);
1758 if (NCPtest)
1759 {
1760 bool NCPinImage =
1761 (pPoint.x() > 0 && pPoint.x() < image_width) && (pPoint.y() > 0 && pPoint.y() < image_height);
1762 if (NCPinImage)
1763 {
1764 painter->fillRect(pPoint.x() * scale - 2, pPoint.y() * scale - 2, 4, 4,
1765 KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"));
1766 painter->drawText(pPoint.x() * scale + 15, pPoint.y() * scale + 15,
1767 i18nc("North Celestial Pole", "NCP"));
1768 }
1769 }
1770
1771 //This Section Draws the South Celestial Pole if present
1772 SkyPoint SCP(0, -90);
1773
1774 bool SCPtest = m_ImageData->wcsToPixel(SCP, pPoint, imagePoint);
1775 if (SCPtest)
1776 {
1777 bool SCPinImage =
1778 (pPoint.x() > 0 && pPoint.x() < image_width) && (pPoint.y() > 0 && pPoint.y() < image_height);
1779 if (SCPinImage)
1780 {
1781 painter->fillRect(pPoint.x() * scale - 2, pPoint.y() * scale - 2, 4, 4,
1782 KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"));
1783 painter->drawText(pPoint.x() * scale + 15, pPoint.y() * scale + 15,
1784 i18nc("South Celestial Pole", "SCP"));
1785 }
1786 }
1787 }
1788}
1789#endif
1790
1791bool FITSView::pointIsInImage(QPointF pt, double scale)
1792{
1793 int image_width = m_ImageData->width();
1794 int image_height = m_ImageData->height();
1795 return pt.x() < image_width * scale && pt.y() < image_height * scale && pt.x() > 0 && pt.y() > 0;
1796}
1797
1798QPointF FITSView::getPointForGridLabel(QPainter *painter, const QString &str, double scale)
1799{
1800 QFontMetrics fm(painter->font());
1801#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1802 int strWidth = fm.width(str);
1803#else
1804 int strWidth = fm.horizontalAdvance(str);
1805#endif
1806 int strHeight = fm.height();
1807 int image_width = m_ImageData->width();
1808 int image_height = m_ImageData->height();
1809
1810 //These get the maximum X and Y points in the list that are in the image
1811 QPointF maxXPt(image_width * scale / 2, image_height * scale / 2);
1812 for (auto &p : eqGridPoints)
1813 {
1814 if (p.x() > maxXPt.x() && pointIsInImage(p, scale))
1815 maxXPt = p;
1816 }
1817 QPointF maxYPt(image_width * scale / 2, image_height * scale / 2);
1818
1819 for (auto &p : eqGridPoints)
1820 {
1821 if (p.y() > maxYPt.y() && pointIsInImage(p, scale))
1822 maxYPt = p;
1823 }
1824 QPointF minXPt(image_width * scale / 2, image_height * scale / 2);
1825
1826 for (auto &p : eqGridPoints)
1827 {
1828 if (p.x() < minXPt.x() && pointIsInImage(p, scale))
1829 minXPt = p;
1830 }
1831 QPointF minYPt(image_width * scale / 2, image_height * scale / 2);
1832
1833 for (auto &p : eqGridPoints)
1834 {
1835 if (p.y() < minYPt.y() && pointIsInImage(p, scale))
1836 minYPt = p;
1837 }
1838
1839 //This gives preference to points that are on the right hand side and bottom.
1840 //But if the line doesn't intersect the right or bottom, it then tries for the top and left.
1841 //If no points are found in the image, it returns a point off the screen
1842 //If all else fails, like in the case of a circle on the image, it returns the far right point.
1843
1844 if (image_width * scale - maxXPt.x() < strWidth)
1845 {
1846 return QPointF(
1847 image_width * scale - (strWidth + 10),
1848 maxXPt.y() -
1849 strHeight); //This will draw the text on the right hand side, up and to the left of the point where the line intersects
1850 }
1851 if (image_height * scale - maxYPt.y() < strHeight)
1852 return QPointF(
1853 maxYPt.x() - (strWidth + 10),
1854 image_height * scale -
1855 (strHeight + 10)); //This will draw the text on the bottom side, up and to the left of the point where the line intersects
1856 if (minYPt.y() < strHeight)
1857 return QPointF(
1858 minYPt.x() * scale + 10,
1859 strHeight + 20); //This will draw the text on the top side, down and to the right of the point where the line intersects
1860 if (minXPt.x() < strWidth)
1861 return QPointF(
1862 10,
1863 minXPt.y() * scale +
1864 strHeight +
1865 20); //This will draw the text on the left hand side, down and to the right of the point where the line intersects
1866 if (maxXPt.x() == image_width * scale / 2 && maxXPt.y() == image_height * scale / 2)
1867 return QPointF(-100, -100); //All of the points were off the screen
1868
1869 return QPoint(maxXPt.x() - (strWidth + 10), maxXPt.y() - (strHeight + 10));
1870}
1871
1872void FITSView::setFirstLoad(bool value)
1873{
1874 firstLoad = value;
1875}
1876
1877QPixmap &FITSView::getTrackingBoxPixmap(uint8_t margin)
1878{
1879 if (trackingBox.isNull())
1880 return trackingBoxPixmap;
1881
1882 // We need to know which rendering strategy updateFrame used to determine the scaling.
1883 const float scale = getScale();
1884
1885 int x1 = (trackingBox.x() - margin) * scale;
1886 int y1 = (trackingBox.y() - margin) * scale;
1887 int w = (trackingBox.width() + margin * 2) * scale;
1888 int h = (trackingBox.height() + margin * 2) * scale;
1889
1890 trackingBoxPixmap = m_ImageFrame->grab(QRect(x1, y1, w, h));
1891 return trackingBoxPixmap;
1892}
1893
1894void FITSView::setTrackingBox(const QRect &rect)
1895{
1896 if (rect != trackingBox)
1897 {
1898 trackingBox = rect;
1899 updateFrame();
1900 if(showStarProfile)
1901 viewStarProfile();
1902 }
1903}
1904
1905void FITSView::resizeTrackingBox(int newSize)
1906{
1907 int x = trackingBox.x() + trackingBox.width() / 2;
1908 int y = trackingBox.y() + trackingBox.height() / 2;
1909 int delta = newSize / 2;
1910 setTrackingBox(QRect( x - delta, y - delta, newSize, newSize));
1911}
1912
1913void FITSView::processRectangleFixed(int s)
1914{
1915 int w = m_ImageData->width();
1916 int h = m_ImageData->height();
1917
1918 QPoint c = selectionRectangleRaw.center();
1919 c.setX(qMax((int)round(s / 2.0), c.x()));
1920 c.setX(qMin(w - (int)round(s / 2.0), c.x()));
1921 c.setY(qMax((int)round(s / 2.0), c.y()));
1922 c.setY(qMin(h - (int)round(s / 2.0), c.y()));
1923
1924 QPoint topLeft, botRight;
1925 topLeft = QPoint(c.x() - round(s / 2.0), c.y() - round(s / 2.0));
1926 botRight = QPoint(c.x() + round(s / 2.0), c.y() + round(s / 2.0));
1927
1928 emit setRubberBand(QRect(topLeft, botRight));
1929 processRectangle(topLeft, botRight, true);
1930}
1931
1932void FITSView::processRectangle(QPoint p1, QPoint p2, bool calculate)
1933{
1934 if(!isSelectionRectShown())
1935 return;
1936 //the user can draw a rectangle by dragging the mouse to any direction
1937 //but we need to feed Rectangle(topleft, topright)
1938 //hence we calculate topleft and topright for each case
1939
1940 //p1 is the point where the user presses the mouse
1941 //p2 is the point where the user releases the mouse
1942 selectionRectangleRaw = QRect(p1, p2).normalized();
1943 //Index out of bounds Check for raw Rectangle, this effectively works when user does shift + drag, other wise becomes redundant
1944
1945 QPoint topLeft = selectionRectangleRaw.topLeft();
1946 QPoint botRight = selectionRectangleRaw.bottomRight();
1947
1948 topLeft.setX(qMax(1, topLeft.x()));
1949 topLeft.setY(qMax(1, topLeft.y()));
1950 botRight.setX(qMin((int)m_ImageData->width(), botRight.x()));
1951 botRight.setY(qMin((int)m_ImageData->height(), botRight.y()));
1952
1953 selectionRectangleRaw.setTopLeft(topLeft);
1954 selectionRectangleRaw.setBottomRight(botRight);
1955
1956 if(calculate)
1957 {
1958 if(m_ImageData)
1959 {
1960 m_ImageData->makeRoiBuffer(selectionRectangleRaw);
1961 emit rectangleUpdated(selectionRectangleRaw);
1962 }
1963 }
1964 //updateFrameRoi();
1965
1966 //emit raw rectangle for calculation
1967 //update the stats pane after calculation; there should be ample time for calculation before showing the values
1968}
1969
1970bool FITSView::isImageStretched()
1971{
1972 return stretchImage;
1973}
1974
1975bool FITSView::isClippingShown()
1976{
1977 return showClipping;
1978}
1979
1980bool FITSView::isCrosshairShown()
1981{
1982 return showCrosshair;
1983}
1984
1985bool FITSView::isEQGridShown()
1986{
1987 return showEQGrid;
1988}
1989
1990bool FITSView::isSelectionRectShown()
1991{
1992 return showSelectionRect;
1993}
1994bool FITSView::areObjectsShown()
1995{
1996 return showObjects;
1997}
1998
1999bool FITSView::isPixelGridShown()
2000{
2001 return showPixelGrid;
2002}
2003
2004bool FITSView::isHiPSOverlayShown()
2005{
2006 return showHiPSOverlay;
2007}
2008
2009void FITSView::toggleCrosshair()
2010{
2011 showCrosshair = !showCrosshair;
2012 updateFrame();
2013}
2014
2015void FITSView::toggleClipping()
2016{
2017 showClipping = !showClipping;
2018 updateFrame();
2019}
2020
2021void FITSView::toggleEQGrid()
2022{
2023 showEQGrid = !showEQGrid;
2024
2025 if (m_ImageData->getWCSState() == FITSData::Idle && !wcsWatcher.isRunning())
2026 {
2027#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2028 QFuture<bool> future = QtConcurrent::run(&FITSData::loadWCS, m_ImageData.data());
2029#else
2030 QFuture<bool> future = QtConcurrent::run(m_ImageData.data(), &FITSData::loadWCS);
2031#endif
2032 wcsWatcher.setFuture(future);
2033 return;
2034 }
2035
2036 if (m_ImageFrame)
2037 updateFrame();
2038}
2039
2040void FITSView::toggleHiPSOverlay()
2041{
2042 showHiPSOverlay = !showHiPSOverlay;
2043
2044 if (m_ImageData->getWCSState() == FITSData::Idle && !wcsWatcher.isRunning())
2045 {
2046#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2047 QFuture<bool> future = QtConcurrent::run(&FITSData::loadWCS, m_ImageData.data());
2048#else
2049 QFuture<bool> future = QtConcurrent::run(m_ImageData.data(), &FITSData::loadWCS);
2050#endif
2051 wcsWatcher.setFuture(future);
2052 return;
2053 }
2054
2055 if (m_ImageFrame)
2056 {
2057 m_QueueUpdate = true;
2058 updateFrame();
2059 }
2060}
2061
2062void FITSView::toggleSelectionMode()
2063{
2064 showSelectionRect = !showSelectionRect;
2065 if (!showSelectionRect)
2066 emit rectangleUpdated(QRect());
2067 else if (m_ImageData)
2068 {
2069 m_ImageData->makeRoiBuffer(selectionRectangleRaw);
2070 emit rectangleUpdated(selectionRectangleRaw);
2071 }
2072
2073 emit showRubberBand(showSelectionRect);
2074 if (m_ImageFrame)
2075 updateFrame();
2076
2077}
2078void FITSView::toggleObjects()
2079{
2080 showObjects = !showObjects;
2081
2082 if (m_ImageData->getWCSState() == FITSData::Idle && !wcsWatcher.isRunning())
2083 {
2084#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2085 QFuture<bool> future = QtConcurrent::run(&FITSData::loadWCS, m_ImageData.data());
2086#else
2087 QFuture<bool> future = QtConcurrent::run(m_ImageData.data(), &FITSData::loadWCS);
2088#endif
2089 wcsWatcher.setFuture(future);
2090 return;
2091 }
2092
2093 if (m_ImageFrame)
2094 {
2095#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
2096 m_ImageData->searchObjects();
2097#endif
2098 updateFrame();
2099 }
2100}
2101
2102void FITSView::toggleStars()
2103{
2104 toggleStars(!markStars);
2105 if (m_ImageFrame)
2106 updateFrame();
2107}
2108
2109void FITSView::toggleStretch()
2110{
2111 stretchImage = !stretchImage;
2112 if (m_ImageFrame && rescale(ZOOM_KEEP_LEVEL))
2113 updateFrame();
2114}
2115
2116void FITSView::toggleStarProfile()
2117{
2118#ifdef HAVE_DATAVISUALIZATION
2119 showStarProfile = !showStarProfile;
2120 if(showStarProfile && trackingBoxEnabled)
2121 viewStarProfile();
2122 if(toggleProfileAction)
2123 toggleProfileAction->setChecked(showStarProfile);
2124
2125 if(showStarProfile)
2126 {
2127 //The tracking box is already on for Guide and Focus Views, but off for Normal and Align views.
2128 //So for Normal and Align views, we need to set up the tracking box.
2129 if(mode == FITS_NORMAL || mode == FITS_ALIGN)
2130 {
2131 setCursorMode(selectCursor);
2132 connect(this, SIGNAL(trackingStarSelected(int, int)), this, SLOT(move3DTrackingBox(int, int)));
2133 trackingBox = QRect(0, 0, 128, 128);
2134 setTrackingBoxEnabled(true);
2135 if(starProfileWidget)
2136 connect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)), this, SLOT(resizeTrackingBox(int)));
2137 }
2138 if(starProfileWidget)
2139 connect(starProfileWidget, SIGNAL(rejected()), this, SLOT(toggleStarProfile()));
2140 }
2141 else
2142 {
2143 //This shuts down the tracking box for Normal and Align Views
2144 //It doesn't touch Guide and Focus Views because they still need a tracking box
2145 if(mode == FITS_NORMAL || mode == FITS_ALIGN)
2146 {
2147 if(getCursorMode() == selectCursor)
2148 setCursorMode(dragCursor);
2149 disconnect(this, SIGNAL(trackingStarSelected(int, int)), this, SLOT(move3DTrackingBox(int, int)));
2150 setTrackingBoxEnabled(false);
2151 if(starProfileWidget)
2152 disconnect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)), this, SLOT(resizeTrackingBox(int)));
2153 }
2154 if(starProfileWidget)
2155 {
2156 disconnect(starProfileWidget, SIGNAL(rejected()), this, SLOT(toggleStarProfile()));
2157 starProfileWidget->close();
2158 starProfileWidget = nullptr;
2159 }
2160 emit starProfileWindowClosed();
2161 }
2162 updateFrame();
2163#endif
2164}
2165
2166void FITSView::move3DTrackingBox(int x, int y)
2167{
2168 int boxSize = trackingBox.width();
2169 QRect starRect = QRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize);
2170 setTrackingBox(starRect);
2171}
2172
2173void FITSView::viewStarProfile()
2174{
2175#ifdef HAVE_DATAVISUALIZATION
2176 if(!trackingBoxEnabled)
2177 {
2178 setTrackingBoxEnabled(true);
2179 setTrackingBox(QRect(0, 0, 128, 128));
2180 }
2181 if(!starProfileWidget)
2182 {
2183 starProfileWidget = new StarProfileViewer(this);
2184
2185 //This is a band-aid to fix a QT bug with createWindowContainer
2186 //It will set the cursor of the Window containing the view that called the Star Profile method to the Arrow Cursor
2187 //Note that Ekos Manager is a QDialog and FitsViewer is a KXmlGuiWindow
2188 QWidget * superParent = this->parentWidget();
2189 while(superParent->parentWidget() != 0 && !superParent->inherits("QDialog") && !superParent->inherits("KXmlGuiWindow"))
2190 superParent = superParent->parentWidget();
2191 superParent->setCursor(Qt::ArrowCursor);
2192 //This is the end of the band-aid
2193
2194 connect(starProfileWidget, SIGNAL(rejected()), this, SLOT(toggleStarProfile()));
2195 if(mode == FITS_ALIGN || mode == FITS_NORMAL)
2196 {
2197 starProfileWidget->enableTrackingBox(true);
2198 m_ImageData->setStarAlgorithm(ALGORITHM_CENTROID);
2199 connect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)), this, SLOT(resizeTrackingBox(int)));
2200 }
2201 }
2202 QList<Edge *> starCenters = m_ImageData->getStarCentersInSubFrame(trackingBox);
2203 if(starCenters.size() == 0)
2204 {
2205 // FIXME, the following does not work anymore.
2206 //m_ImageData->findStars(&trackingBox, true);
2207 // FIXME replacing it with this
2208 m_ImageData->findStars(ALGORITHM_CENTROID, trackingBox).waitForFinished();
2209 starCenters = m_ImageData->getStarCentersInSubFrame(trackingBox);
2210 }
2211
2212 starProfileWidget->loadData(m_ImageData, trackingBox, starCenters);
2213 starProfileWidget->show();
2214 starProfileWidget->raise();
2215 if(markStars)
2216 updateFrame(); //this is to update for the marked stars
2217
2218#endif
2219}
2220
2221void FITSView::togglePixelGrid()
2222{
2223 showPixelGrid = !showPixelGrid;
2224 updateFrame();
2225}
2226
2227QFuture<bool> FITSView::findStars(StarAlgorithm algorithm, const QRect &searchBox)
2228{
2229 if(trackingBoxEnabled)
2230 return m_ImageData->findStars(algorithm, trackingBox);
2231 else
2232 return m_ImageData->findStars(algorithm, searchBox);
2233}
2234
2235void FITSView::toggleStars(bool enable)
2236{
2237 markStars = enable;
2238
2239 if (markStars)
2240 searchStars();
2241}
2242
2243void FITSView::searchStars()
2244{
2245 QVariant frameType;
2246 if (m_ImageData->areStarsSearched() || !m_ImageData || (m_ImageData->getRecordValue("FRAME", frameType)
2247 && frameType.toString() != "Light"))
2248 return;
2249
2251 emit newStatus(i18n("Finding stars..."), FITS_MESSAGE);
2252 qApp->processEvents();
2253
2254#ifdef HAVE_STELLARSOLVER
2255 QVariantMap extractionSettings;
2256 extractionSettings["optionsProfileIndex"] = Options::hFROptionsProfile();
2257 extractionSettings["optionsProfileGroup"] = static_cast<int>(Ekos::HFRProfiles);
2258 imageData()->setSourceExtractorSettings(extractionSettings);
2259#endif
2260
2261 QFuture<bool> result = findStars(ALGORITHM_SEP);
2262 result.waitForFinished();
2263 if (result.result() && isVisible())
2264 {
2265 emit newStatus("", FITS_MESSAGE);
2266 }
2268}
2269
2270void FITSView::processPointSelection(int x, int y)
2271{
2272 emit trackingStarSelected(x, y);
2273}
2274
2275void FITSView::processMarkerSelection(int x, int y)
2276{
2277 markerCrosshair.setX(x);
2278 markerCrosshair.setY(y);
2279
2280 updateFrame();
2281}
2282
2283void FITSView::setTrackingBoxEnabled(bool enable)
2284{
2285 if (enable != trackingBoxEnabled)
2286 {
2287 trackingBoxEnabled = enable;
2288 //updateFrame();
2289 }
2290}
2291
2292void FITSView::wheelEvent(QWheelEvent * event)
2293{
2294 //This attempts to send the wheel event back to the Scroll Area if it was taken from a trackpad
2295 //It should still do the zoom if it is a mouse wheel
2297 {
2299 }
2300 else
2301 {
2302#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
2303 QPoint mouseCenter = getImagePoint(event->pos());
2304#else
2305 QPoint mouseCenter = getImagePoint(event->position().toPoint());
2306#endif
2307 if (event->angleDelta().y() > 0)
2308 ZoomIn();
2309 else
2310 ZoomOut();
2311 event->accept();
2312 cleanUpZoom(mouseCenter);
2313 }
2314 emit zoomRubberBand(getCurrentZoom() / ZOOM_DEFAULT);
2315}
2316
2317/**
2318This method is intended to keep key locations in an image centered on the screen while zooming.
2319If there is a marker or tracking box, it centers on those. If not, it uses the point called
2320viewCenter that was passed as a parameter.
2321 */
2322
2323void FITSView::cleanUpZoom(QPoint viewCenter)
2324{
2325 int x0 = 0;
2326 int y0 = 0;
2327 double scale = (currentZoom / ZOOM_DEFAULT);
2328 if (!markerCrosshair.isNull())
2329 {
2330 x0 = markerCrosshair.x() * scale;
2331 y0 = markerCrosshair.y() * scale;
2332 }
2333 else if (trackingBoxEnabled)
2334 {
2335 x0 = trackingBox.center().x() * scale;
2336 y0 = trackingBox.center().y() * scale;
2337 }
2338 else if (!viewCenter.isNull())
2339 {
2340 x0 = viewCenter.x() * scale;
2341 y0 = viewCenter.y() * scale;
2342 }
2343 if ((x0 != 0) || (y0 != 0))
2344 ensureVisible(x0, y0, width() / 2, height() / 2);
2345 updateMouseCursor();
2346}
2347
2348/**
2349This method converts a point from the ViewPort Coordinate System to the
2350Image Coordinate System.
2351 */
2352
2353QPoint FITSView::getImagePoint(QPoint viewPortPoint)
2354{
2355 QWidget * w = widget();
2356
2357 if (w == nullptr)
2358 return QPoint(0, 0);
2359
2360 double scale = (currentZoom / ZOOM_DEFAULT);
2361 QPoint widgetPoint = w->mapFromParent(viewPortPoint);
2362 QPoint imagePoint = QPoint(widgetPoint.x() / scale, widgetPoint.y() / scale);
2363 return imagePoint;
2364}
2365
2366void FITSView::initDisplayImage()
2367{
2368 // Account for leftover when sampling. Thus a 5-wide image sampled by 2
2369 // would result in a width of 3 (samples 0, 2 and 4).
2370 int w = (m_ImageData->width() + m_PreviewSampling - 1) / m_PreviewSampling;
2371 int h = (m_ImageData->height() + m_PreviewSampling - 1) / m_PreviewSampling;
2372
2373 if (m_ImageData->channels() == 1)
2374 {
2375 rawImage = QImage(w, h, QImage::Format_Indexed8);
2376
2377 rawImage.setColorCount(256);
2378 for (int i = 0; i < 256; i++)
2379 rawImage.setColor(i, qRgb(i, i, i));
2380 }
2381 else
2382 {
2383 rawImage = QImage(w, h, QImage::Format_RGB32);
2384 }
2385}
2386
2387/**
2388The Following two methods allow gestures to work with trackpads.
2389Specifically, we are targeting the pinch events, so that if one is generated,
2390Then the pinchTriggered method will be called. If the event is not a pinch gesture,
2391then the event is passed back to the other event handlers.
2392 */
2393
2394bool FITSView::event(QEvent * event)
2395{
2396 if (event->type() == QEvent::Gesture)
2397 return gestureEvent(dynamic_cast<QGestureEvent *>(event));
2398 return QScrollArea::event(event);
2399}
2400
2401bool FITSView::gestureEvent(QGestureEvent * event)
2402{
2403 if (QGesture * pinch = event->gesture(Qt::PinchGesture))
2404 pinchTriggered(dynamic_cast<QPinchGesture *>(pinch));
2405 return true;
2406}
2407
2408/**
2409This Method works with Trackpads to use the pinch gesture to scroll in and out
2410It stores a point to keep track of the location where the gesture started so that
2411while you are zooming, it tries to keep that initial point centered in the view.
2412**/
2413void FITSView::pinchTriggered(QPinchGesture * gesture)
2414{
2415 if (!zooming)
2416 {
2417 zoomLocation = getImagePoint(mapFromGlobal(QCursor::pos()));
2418 zooming = true;
2419 }
2420 if (gesture->state() == Qt::GestureFinished)
2421 {
2422 zooming = false;
2423 }
2424 zoomTime++; //zoomTime is meant to slow down the zooming with a pinch gesture.
2425 if (zoomTime > 10000) //This ensures zoomtime never gets too big.
2426 zoomTime = 0;
2427 if (zooming && (zoomTime % 10 == 0)) //zoomTime is set to slow it by a factor of 10.
2428 {
2429 if (gesture->totalScaleFactor() > 1)
2430 ZoomIn();
2431 else
2432 ZoomOut();
2433 }
2434 cleanUpZoom(zoomLocation);
2435}
2436
2437/*void FITSView::handleWCSCompletion()
2438{
2439 //bool hasWCS = wcsWatcher.result();
2440 if(m_ImageData->hasWCS())
2441 this->updateFrame();
2442 emit wcsToggled(m_ImageData->hasWCS());
2443}*/
2444
2445void FITSView::syncWCSState()
2446{
2447 bool hasWCS = m_ImageData->hasWCS();
2448 bool wcsLoaded = m_ImageData->getWCSState() == FITSData::Success;
2449
2450#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
2451 if (showObjects)
2452 m_ImageData->searchObjects();
2453#endif
2454
2455 if (hasWCS && wcsLoaded)
2456 this->updateFrame();
2457
2458 emit wcsToggled(hasWCS);
2459
2460 if (toggleEQGridAction != nullptr)
2461 toggleEQGridAction->setEnabled(hasWCS);
2462 if (toggleObjectsAction != nullptr)
2463 toggleObjectsAction->setEnabled(hasWCS);
2464 if (centerTelescopeAction != nullptr)
2465 centerTelescopeAction->setEnabled(hasWCS);
2466 if (toggleHiPSOverlayAction != nullptr)
2467 toggleHiPSOverlayAction->setEnabled(hasWCS);
2468}
2469
2470void FITSView::createFloatingToolBar()
2471{
2472 if (floatingToolBar != nullptr)
2473 return;
2474
2475 floatingToolBar = new QToolBar(this);
2476 auto * eff = new QGraphicsOpacityEffect(this);
2477 floatingToolBar->setGraphicsEffect(eff);
2478 eff->setOpacity(0.2);
2479 floatingToolBar->setVisible(false);
2480 floatingToolBar->setStyleSheet(
2481 "QToolBar{background: rgba(150, 150, 150, 210); border:none; color: yellow}"
2482 "QToolButton{background: transparent; border:none; color: yellow}"
2483 "QToolButton:hover{background: rgba(200, 200, 200, 255);border:solid; color: yellow}"
2484 "QToolButton:checked{background: rgba(110, 110, 110, 255);border:solid; color: yellow}");
2485 floatingToolBar->setFloatable(true);
2486 floatingToolBar->setIconSize(QSize(25, 25));
2487 //floatingToolBar->setMovable(true);
2488
2489 QAction * action = nullptr;
2490
2491 floatingToolBar->addAction(QIcon::fromTheme("zoom-in"),
2492 i18n("Zoom In"), this, SLOT(ZoomIn()));
2493
2494 floatingToolBar->addAction(QIcon::fromTheme("zoom-out"),
2495 i18n("Zoom Out"), this, SLOT(ZoomOut()));
2496
2497 floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-best"),
2498 i18n("Default Zoom"), this, SLOT(ZoomDefault()));
2499
2500 floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-width"),
2501 i18n("Zoom to Fit"), this, SLOT(ZoomToFit()));
2502
2503 toggleStretchAction = floatingToolBar->addAction(QIcon::fromTheme("transform-move"),
2504 i18n("Toggle Stretch"),
2505 this, SLOT(toggleStretch()));
2506 toggleStretchAction->setCheckable(true);
2507
2508
2509 floatingToolBar->addSeparator();
2510
2511 action = floatingToolBar->addAction(QIcon::fromTheme("crosshairs"),
2512 i18n("Show Cross Hairs"), this, SLOT(toggleCrosshair()));
2513 action->setCheckable(true);
2514
2515 action = floatingToolBar->addAction(QIcon::fromTheme("map-flat"),
2516 i18n("Show Pixel Gridlines"), this, SLOT(togglePixelGrid()));
2517 action->setCheckable(true);
2518
2519 toggleStarsAction =
2520 floatingToolBar->addAction(QIcon::fromTheme("kstars_stars"),
2521 i18n("Detect Stars in Image"), this, SLOT(toggleStars()));
2522 toggleStarsAction->setCheckable(true);
2523
2524#ifdef HAVE_DATAVISUALIZATION
2525 toggleProfileAction =
2526 floatingToolBar->addAction(QIcon::fromTheme("star-profile", QIcon(":/icons/star_profile.svg")),
2527 i18n("View Star Profile..."), this, SLOT(toggleStarProfile()));
2528 toggleProfileAction->setCheckable(true);
2529#endif
2530
2531 if (mode == FITS_NORMAL || mode == FITS_ALIGN)
2532 {
2533 floatingToolBar->addSeparator();
2534
2535 toggleEQGridAction =
2536 floatingToolBar->addAction(QIcon::fromTheme("kstars_grid"),
2537 i18n("Show Equatorial Gridlines"), this, &FITSView::toggleEQGrid);
2538 toggleEQGridAction->setCheckable(true);
2539 toggleEQGridAction->setEnabled(false);
2540
2541 toggleObjectsAction =
2542 floatingToolBar->addAction(QIcon::fromTheme("help-hint"),
2543 i18n("Show Objects in Image"), this, &FITSView::toggleObjects);
2544 toggleObjectsAction->setCheckable(true);
2545 toggleObjectsAction->setEnabled(false);
2546
2547 centerTelescopeAction =
2548 floatingToolBar->addAction(QIcon::fromTheme("center_telescope", QIcon(":/icons/center_telescope.svg")),
2549 i18n("Center Telescope"), this, &FITSView::centerTelescope);
2550 centerTelescopeAction->setCheckable(true);
2551 centerTelescopeAction->setEnabled(false);
2552
2553 toggleHiPSOverlayAction =
2554 floatingToolBar->addAction(QIcon::fromTheme("pixelate"),
2555 i18n("Show HiPS Overlay"), this, &FITSView::toggleHiPSOverlay);
2556 toggleHiPSOverlayAction->setCheckable(true);
2557 toggleHiPSOverlayAction->setEnabled(false);
2558 }
2559}
2560
2561/**
2562 This method either enables or disables the scope mouse mode so you can slew your scope to coordinates
2563 just by clicking the mouse on a spot in the image.
2564 */
2565
2566void FITSView::centerTelescope()
2567{
2568 if (imageHasWCS())
2569 {
2570 if (getCursorMode() == FITSView::scopeCursor)
2571 {
2572 setCursorMode(lastMouseMode);
2573 }
2574 else
2575 {
2576 lastMouseMode = getCursorMode();
2577 setCursorMode(FITSView::scopeCursor);
2578 }
2579 updateFrame();
2580 }
2581 updateScopeButton();
2582}
2583
2584void FITSView::updateScopeButton()
2585{
2586 if (centerTelescopeAction != nullptr)
2587 {
2588 if (getCursorMode() == FITSView::scopeCursor)
2589 {
2590 centerTelescopeAction->setChecked(true);
2591 }
2592 else
2593 {
2594 centerTelescopeAction->setChecked(false);
2595 }
2596 }
2597}
2598
2599/**
2600This method just verifies if INDI is online, a telescope present, and is connected
2601 */
2602
2603bool FITSView::isTelescopeActive()
2604{
2605#ifdef HAVE_INDI
2606 if (INDIListener::Instance()->size() == 0)
2607 {
2608 return false;
2609 }
2610
2611 for (auto &oneDevice : INDIListener::devices())
2612 {
2613 if (!(oneDevice->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE))
2614 continue;
2615 return oneDevice->isConnected();
2616 }
2617 return false;
2618#else
2619 return false;
2620#endif
2621}
2622
2623void FITSView::setStarsEnabled(bool enable)
2624{
2625 markStars = enable;
2626 if (floatingToolBar != nullptr)
2627 {
2628 foreach (QAction * action, floatingToolBar->actions())
2629 {
2630 if (action->text() == i18n("Detect Stars in Image"))
2631 {
2632 action->setChecked(markStars);
2633 break;
2634 }
2635 }
2636 }
2637}
2638
2639void FITSView::setStarsHFREnabled(bool enable)
2640{
2641 showStarsHFR = enable;
2642}
2643
2644void FITSView::setStretchValues(double shadows, double midtones, double highlights)
2645{
2646 StretchParams params = getStretchParams();
2647 params.grey_red.shadows = shadows;
2648 params.grey_red.midtones = midtones;
2649 params.grey_red.highlights = highlights;
2650 //setPreviewSampling(0);
2651 setStretchParams(params);
2652}
2653
2654void FITSView::setAutoStretch()
2655{
2656 if (!getAutoStretch())
2657 setAutoStretchParams();
2658}
2659
The sky coordinates of a point in the sky.
Definition skypoint.h:45
virtual void updateCoordsNow(const KSNumbers *num)
updateCoordsNow Shortcut for updateCoords( const KSNumbers *num, false, nullptr, nullptr,...
Definition skypoint.h:391
dms angularDistanceTo(const SkyPoint *sp, double *const positionAngle=nullptr) const
Computes the angular distance between two SkyObjects.
Definition skypoint.cpp:899
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const double & Degrees() const
Definition dms.h:141
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KREPORT_EXPORT QPageSize::PageSizeId pageSize(const QString &key)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
QScrollBar * horizontalScrollBar() const const
QScrollBar * verticalScrollBar() const const
QWidget * viewport() const const
virtual void wheelEvent(QWheelEvent *e) override
void setCheckable(bool)
void setChecked(bool)
void setEnabled(bool)
const char * constData() const const
QPoint pos()
QString suffix() const const
qreal pointSizeF() const const
void setPixelSize(int pixelSize)
void setPointSize(int pointSize)
void setPointSizeF(qreal pointSize)
QSize size(int flags, const QString &text, int tabStops, int *tabArray) const const
T result() const const
void waitForFinished()
bool isRunning() const const
T result() const const
void setFuture(const QFuture< T > &future)
void waitForFinished()
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QIcon fromTheme(const QString &name)
Format_ARGB32_Premultiplied
int height() const const
bool isNull() const const
bool save(QIODevice *device, const char *format, int quality) const const
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
void setColor(int index, QRgb colorValue)
void setColorCount(int colorCount)
int width() const const
QList< QByteArray > supportedImageFormats()
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
bool inherits(const char *className) const const
int height() const const
int width() const const
QPaintDevice * device() const const
void drawEllipse(const QPoint &center, int rx, int ry)
void drawImage(const QPoint &point, const QImage &image)
void drawLine(const QLine &line)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawPoints(const QPoint *points, int pointCount)
void drawRect(const QRect &rectangle)
void drawText(const QPoint &position, const QString &text)
void fillRect(const QRect &rectangle, QGradient::Preset preset)
const QFont & font() const const
QFontMetrics fontMetrics() const const
void restore()
void save()
void setBrush(Qt::BrushStyle style)
void setFont(const QFont &font)
void setOpacity(qreal opacity)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
bool convertFromImage(const QImage &image, Qt::ImageConversionFlags flags)
void fill(const QColor &color)
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
bool isNull() const const
bool load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QSize size() const const
bool isNull() const const
void setX(int x)
void setY(int y)
int x() const const
int y() const const
bool isNull() const const
void setX(qreal x)
void setY(qreal y)
qreal x() const const
qreal y() const const
QPoint bottomRight() const const
QPoint center() const const
int height() const const
bool isNull() const const
QRect normalized() const const
void setBottomRight(const QPoint &position)
void setTopLeft(const QPoint &position)
QPoint topLeft() const const
int width() const const
int x() const const
int y() const const
void translate(const QPoint &point)
void setAlignment(Qt::Alignment)
void ensureVisible(int x, int y, int xmargin, int ymargin)
virtual bool event(QEvent *e) override
virtual void resizeEvent(QResizeEvent *) override
void setWidget(QWidget *widget)
QWidget * widget() const const
T * data() const const
T * get() const const
bool isNull() const const
int height() const const
int width() const const
void push(const T &t)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QByteArray toLatin1() const const
AlignCenter
KeepAspectRatio
FDiagPattern
PointingHandCursor
GestureFinished
PinchGesture
MouseEventSynthesizedBySystem
DashLine
TextSingleLine
FastTransformation
QTextStream & center(QTextStream &stream)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void stop()
void timeout()
QAction * addSeparator()
void setFloatable(bool floatable)
void setIconSize(const QSize &iconSize)
QUuid createUuid()
QString toString(StringFormat mode) const const
QString toString() const const
QList< QAction * > actions() const const
QAction * addAction(const QIcon &icon, const QString &text)
void setCursor(const QCursor &)
QPoint mapFromGlobal(const QPoint &pos) const const
QPoint mapFromParent(const QPoint &pos) const const
QRegion mask() const const
QWidget * parentWidget() const const
void setGraphicsEffect(QGraphicsEffect *effect)
void setStyleSheet(const QString &styleSheet)
void update()
virtual void setVisible(bool visible)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:15:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.