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

KDE's Doxygen guidelines are available online.