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

KDE's Doxygen guidelines are available online.