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

KDE's Doxygen guidelines are available online.