Changeset View
Changeset View
Standalone View
Standalone View
kstars/fitsviewer/fitsview.cpp
Show All 13 Lines | |||||
14 | #include "fitsdata.h" | 14 | #include "fitsdata.h" | ||
15 | #include "fitslabel.h" | 15 | #include "fitslabel.h" | ||
16 | #include "kspopupmenu.h" | 16 | #include "kspopupmenu.h" | ||
17 | #include "kstarsdata.h" | 17 | #include "kstarsdata.h" | ||
18 | #include "ksutils.h" | 18 | #include "ksutils.h" | ||
19 | #include "Options.h" | 19 | #include "Options.h" | ||
20 | #include "skymap.h" | 20 | #include "skymap.h" | ||
21 | #include "fits_debug.h" | 21 | #include "fits_debug.h" | ||
22 | #include "stretch.h" | ||||
22 | 23 | | |||
23 | #ifdef HAVE_INDI | 24 | #ifdef HAVE_INDI | ||
24 | #include "basedevice.h" | 25 | #include "basedevice.h" | ||
25 | #include "indi/indilistener.h" | 26 | #include "indi/indilistener.h" | ||
26 | #endif | 27 | #endif | ||
27 | 28 | | |||
28 | #include <KActionCollection> | 29 | #include <KActionCollection> | ||
29 | 30 | | |||
30 | #include <QtConcurrent> | 31 | #include <QtConcurrent> | ||
31 | #include <QScrollBar> | 32 | #include <QScrollBar> | ||
32 | #include <QToolBar> | 33 | #include <QToolBar> | ||
33 | #include <QGraphicsOpacityEffect> | 34 | #include <QGraphicsOpacityEffect> | ||
34 | #include <QApplication> | 35 | #include <QApplication> | ||
35 | #include <QGestureEvent> | 36 | #include <QGestureEvent> | ||
36 | 37 | | |||
37 | #define BASE_OFFSET 50 | 38 | #define BASE_OFFSET 50 | ||
38 | #define ZOOM_DEFAULT 100.0 | 39 | #define ZOOM_DEFAULT 100.0 | ||
39 | #define ZOOM_MIN 10 | 40 | #define ZOOM_MIN 10 | ||
40 | #define ZOOM_MAX 400 | 41 | #define ZOOM_MAX 400 | ||
41 | #define ZOOM_LOW_INCR 10 | 42 | #define ZOOM_LOW_INCR 10 | ||
42 | #define ZOOM_HIGH_INCR 50 | 43 | #define ZOOM_HIGH_INCR 50 | ||
43 | 44 | | |||
45 | namespace | ||||
46 | { | ||||
47 | | ||||
48 | void doStretch(FITSData *data, QImage *outputImage, bool stretchOn) | ||||
49 | { | ||||
50 | if (outputImage->isNull()) | ||||
51 | return; | ||||
52 | Stretch stretch(static_cast<int>(data->width()), | ||||
53 | static_cast<int>(data->height()), | ||||
54 | data->channels(), data->property("dataType").toInt()); | ||||
55 | if (stretchOn) | ||||
56 | stretch.setParams(stretch.computeParams(data->getImageBuffer())); | ||||
57 | stretch.run(data->getImageBuffer(), outputImage); | ||||
58 | } | ||||
59 | | ||||
60 | } // namespace | ||||
61 | | ||||
62 | | ||||
44 | FITSView::FITSView(QWidget * parent, FITSMode fitsMode, FITSScale filterType) : QScrollArea(parent), zoomFactor(1.2) | 63 | FITSView::FITSView(QWidget * parent, FITSMode fitsMode, FITSScale filterType) : QScrollArea(parent), zoomFactor(1.2) | ||
45 | { | 64 | { | ||
65 | stretchImage = Options::autoStretch(); | ||||
66 | | ||||
46 | grabGesture(Qt::PinchGesture); | 67 | grabGesture(Qt::PinchGesture); | ||
47 | 68 | | |||
48 | image_frame.reset(new FITSLabel(this)); | 69 | image_frame.reset(new FITSLabel(this)); | ||
49 | filter = filterType; | 70 | filter = filterType; | ||
50 | mode = fitsMode; | 71 | mode = fitsMode; | ||
51 | 72 | | |||
52 | setBackgroundRole(QPalette::Dark); | 73 | setBackgroundRole(QPalette::Dark); | ||
53 | 74 | | |||
▲ Show 20 Lines • Show All 325 Lines • ▼ Show 20 Line(s) | 387 | { | |||
379 | } | 400 | } | ||
380 | } | 401 | } | ||
381 | 402 | | |||
382 | template <typename T> | 403 | template <typename T> | ||
383 | bool FITSView::rescale(FITSZoom type) | 404 | bool FITSView::rescale(FITSZoom type) | ||
384 | { | 405 | { | ||
385 | if (rawImage.isNull()) | 406 | if (rawImage.isNull()) | ||
386 | return false; | 407 | return false; | ||
387 | 408 | if (true || image_height != imageData->height() || image_width != imageData->width()) | |||
388 | uint8_t * imageBuffer = imageData->getImageBuffer(); | | |||
389 | uint8_t * displayBuffer = nullptr; | | |||
390 | uint32_t size = imageData->width() * imageData->height(); | | |||
391 | | ||||
392 | QVector<double> min(3), max(3); | | |||
393 | | ||||
394 | if (Options::autoStretch()) | | |||
395 | { | | |||
396 | displayBuffer = new uint8_t[size * imageData->channels() * imageData->getBytesPerPixel()]; | | |||
397 | memcpy(displayBuffer, imageBuffer, size * imageData->channels() * imageData->getBytesPerPixel()); | | |||
398 | imageData->applyFilter(FITS_AUTO_STRETCH, displayBuffer, &min, &max); | | |||
399 | } | | |||
400 | else | | |||
401 | { | | |||
402 | displayBuffer = imageBuffer; | | |||
403 | for (int i = 0; i < 3; i++) | | |||
404 | { | | |||
405 | min[i] = imageData->getMin(i); | | |||
406 | max[i] = imageData->getMax(i); | | |||
407 | } | | |||
408 | } | | |||
409 | | ||||
410 | scaledImage = QImage(); | | |||
411 | | ||||
412 | auto * buffer = reinterpret_cast<T *>(displayBuffer); | | |||
413 | | ||||
414 | if (min[0] == max[0]) | | |||
415 | { | | |||
416 | rawImage.fill(Qt::white); | | |||
417 | emit newStatus(i18n("Image is saturated."), FITS_MESSAGE); | | |||
418 | } | | |||
419 | else | | |||
420 | { | | |||
421 | if (image_height != imageData->height() || image_width != imageData->width()) | | |||
422 | { | 409 | { | ||
423 | image_width = imageData->width(); | 410 | image_width = imageData->width(); | ||
424 | image_height = imageData->height(); | 411 | image_height = imageData->height(); | ||
425 | 412 | | |||
426 | initDisplayImage(); | 413 | initDisplayImage(); | ||
427 | 414 | | |||
428 | if (isVisible()) | 415 | if (isVisible()) | ||
429 | emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); | 416 | emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); | ||
430 | } | 417 | } | ||
431 | 418 | | |||
432 | image_frame->setScaledContents(true); | 419 | image_frame->setScaledContents(true); | ||
433 | currentWidth = rawImage.width(); | 420 | currentWidth = rawImage.width(); | ||
434 | currentHeight = rawImage.height(); | 421 | currentHeight = rawImage.height(); | ||
435 | 422 | | |||
436 | if (imageData->channels() == 1) | 423 | doStretch(imageData, &rawImage, stretchImage); | ||
437 | { | | |||
438 | double range = max[0] - min[0]; | | |||
439 | double bscale = 255. / range; | | |||
440 | double bzero = (-min[0]) * (255. / range); | | |||
441 | | ||||
442 | QVector<QFuture<void>> futures; | | |||
443 | | ||||
444 | /* Fill in pixel values using indexed map, linear scale */ | | |||
445 | for (uint32_t j = 0; j < image_height; j++) | | |||
446 | { | | |||
447 | futures.append(QtConcurrent::run([ = ]() | | |||
448 | { | | |||
449 | T * runningBuffer = buffer + j * image_width; | | |||
450 | uint8_t * scanLine = rawImage.scanLine(j); | | |||
451 | for (uint32_t i = 0; i < image_width; i++) | | |||
452 | { | | |||
453 | //scanLine[i] = qBound(0, static_cast<uint8_t>(runningBuffer[i] * bscale + bzero), 255); | | |||
454 | scanLine[i] = qBound(0.0, runningBuffer[i] * bscale + bzero, 255.0); | | |||
455 | } | | |||
456 | })); | | |||
457 | } | | |||
458 | | ||||
459 | for(QFuture<void> future : futures) | | |||
460 | future.waitForFinished(); | | |||
461 | } | | |||
462 | else | | |||
463 | { | | |||
464 | QVector<QFuture<void>> futures; | | |||
465 | double bscale[3], bzero[3]; | | |||
466 | for (int i = 0; i < 3; i++) | | |||
467 | { | | |||
468 | bscale[i] = 255. / (max[i] - min[i]); | | |||
469 | bzero[i] = (-min[i]) * (255. / (max[i] - min[i])); | | |||
470 | } | | |||
471 | | ||||
472 | /* Fill in pixel values using indexed map, linear scale */ | | |||
473 | for (uint32_t j = 0; j < image_height; j++) | | |||
474 | { | | |||
475 | futures.append(QtConcurrent::run([ = ]() | | |||
476 | { | | |||
477 | auto * scanLine = reinterpret_cast<QRgb *>((rawImage.scanLine(j))); | | |||
478 | T * runningBufferR = buffer + j * image_width; | | |||
479 | T * runningBufferG = buffer + j * image_width + size; | | |||
480 | T * runningBufferB = buffer + j * image_width + size * 2; | | |||
481 | | ||||
482 | for (uint32_t i = 0; i < image_width; i++) | | |||
483 | { | | |||
484 | scanLine[i] = qRgb(runningBufferR[i] * bscale[0] + bzero[0], | | |||
485 | runningBufferG[i] * bscale[1] + bzero[1], | | |||
486 | runningBufferB[i] * bscale[2] + bzero[2]); | | |||
487 | } | | |||
488 | })); | | |||
489 | } | | |||
490 | | ||||
491 | for(QFuture<void> future : futures) | | |||
492 | future.waitForFinished(); | | |||
493 | } | | |||
494 | | ||||
495 | } | | |||
496 | 424 | | |||
497 | // Clear memory if it was allocated. | 425 | scaledImage = QImage(); | ||
498 | if (displayBuffer != imageBuffer) | | |||
499 | delete [] displayBuffer; | | |||
500 | 426 | | |||
501 | switch (type) | 427 | switch (type) | ||
502 | { | 428 | { | ||
503 | case ZOOM_FIT_WINDOW: | 429 | case ZOOM_FIT_WINDOW: | ||
504 | if ((rawImage.width() > width() || rawImage.height() > height())) | 430 | if ((rawImage.width() > width() || rawImage.height() > height())) | ||
505 | { | 431 | { | ||
506 | double w = baseSize().width() - BASE_OFFSET; | 432 | double w = baseSize().width() - BASE_OFFSET; | ||
507 | double h = baseSize().height() - BASE_OFFSET; | 433 | double h = baseSize().height() - BASE_OFFSET; | ||
▲ Show 20 Lines • Show All 114 Lines • ▼ Show 20 Line(s) | |||||
622 | { | 548 | { | ||
623 | return starFilter.used() ? imageData->filterStars(starFilter.innerRadius, starFilter.outerRadius) : imageData->getStarCenters().count(); | 549 | return starFilter.used() ? imageData->filterStars(starFilter.innerRadius, starFilter.outerRadius) : imageData->getStarCenters().count(); | ||
624 | } | 550 | } | ||
625 | 551 | | |||
626 | void FITSView::updateFrame() | 552 | void FITSView::updateFrame() | ||
627 | { | 553 | { | ||
628 | bool ok = false; | 554 | bool ok = false; | ||
629 | 555 | | |||
556 | if (toggleStretchAction) | ||||
557 | toggleStretchAction->setChecked(stretchImage); | ||||
558 | | ||||
630 | if (currentZoom != ZOOM_DEFAULT) | 559 | if (currentZoom != ZOOM_DEFAULT) | ||
631 | { | 560 | { | ||
632 | // Only scale when necessary | 561 | // Only scale when necessary | ||
633 | if (scaledImage.isNull() || currentWidth != lastWidth || currentHeight != lastHeight) | 562 | if (scaledImage.isNull() || currentWidth != lastWidth || currentHeight != lastHeight) | ||
634 | { | 563 | { | ||
635 | scaledImage = rawImage.scaled(currentWidth, currentHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); | 564 | scaledImage = rawImage.scaled(currentWidth, currentHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); | ||
636 | lastWidth = currentWidth; | 565 | lastWidth = currentWidth; | ||
637 | lastHeight = currentHeight; | 566 | lastHeight = currentHeight; | ||
▲ Show 20 Lines • Show All 572 Lines • ▼ Show 20 Line(s) | |||||
1210 | void FITSView::resizeTrackingBox(int newSize) | 1139 | void FITSView::resizeTrackingBox(int newSize) | ||
1211 | { | 1140 | { | ||
1212 | int x = trackingBox.x() + trackingBox.width() / 2; | 1141 | int x = trackingBox.x() + trackingBox.width() / 2; | ||
1213 | int y = trackingBox.y() + trackingBox.height() / 2; | 1142 | int y = trackingBox.y() + trackingBox.height() / 2; | ||
1214 | int delta = newSize / 2; | 1143 | int delta = newSize / 2; | ||
1215 | setTrackingBox(QRect( x - delta, y - delta, newSize, newSize)); | 1144 | setTrackingBox(QRect( x - delta, y - delta, newSize, newSize)); | ||
1216 | } | 1145 | } | ||
1217 | 1146 | | |||
1147 | bool FITSView::isImageStretched() | ||||
1148 | { | ||||
1149 | return stretchImage; | ||||
1150 | } | ||||
1151 | | ||||
1218 | bool FITSView::isCrosshairShown() | 1152 | bool FITSView::isCrosshairShown() | ||
1219 | { | 1153 | { | ||
1220 | return showCrosshair; | 1154 | return showCrosshair; | ||
1221 | } | 1155 | } | ||
1222 | 1156 | | |||
1223 | bool FITSView::isEQGridShown() | 1157 | bool FITSView::isEQGridShown() | ||
1224 | { | 1158 | { | ||
1225 | return showEQGrid; | 1159 | return showEQGrid; | ||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Line(s) | |||||
1273 | 1207 | | |||
1274 | void FITSView::toggleStars() | 1208 | void FITSView::toggleStars() | ||
1275 | { | 1209 | { | ||
1276 | toggleStars(!markStars); | 1210 | toggleStars(!markStars); | ||
1277 | if (image_frame != nullptr) | 1211 | if (image_frame != nullptr) | ||
1278 | updateFrame(); | 1212 | updateFrame(); | ||
1279 | } | 1213 | } | ||
1280 | 1214 | | |||
1215 | void FITSView::toggleStretch() | ||||
1216 | { | ||||
1217 | stretchImage = !stretchImage; | ||||
1218 | if (image_frame != nullptr && rescale(ZOOM_KEEP_LEVEL)) | ||||
1219 | updateFrame(); | ||||
1220 | } | ||||
1221 | | ||||
1281 | void FITSView::toggleStarProfile() | 1222 | void FITSView::toggleStarProfile() | ||
1282 | { | 1223 | { | ||
1283 | #ifdef HAVE_DATAVISUALIZATION | 1224 | #ifdef HAVE_DATAVISUALIZATION | ||
1284 | showStarProfile = !showStarProfile; | 1225 | showStarProfile = !showStarProfile; | ||
1285 | if(showStarProfile && trackingBoxEnabled) | 1226 | if(showStarProfile && trackingBoxEnabled) | ||
1286 | viewStarProfile(); | 1227 | viewStarProfile(); | ||
1287 | if(toggleProfileAction) | 1228 | if(toggleProfileAction) | ||
1288 | toggleProfileAction->setChecked(showStarProfile); | 1229 | toggleProfileAction->setChecked(showStarProfile); | ||
▲ Show 20 Lines • Show All 341 Lines • ▼ Show 20 Line(s) | 1570 | floatingToolBar->addAction(QIcon::fromTheme("zoom-out"), | |||
1630 | i18n("Zoom Out"), this, SLOT(ZoomOut())); | 1571 | i18n("Zoom Out"), this, SLOT(ZoomOut())); | ||
1631 | 1572 | | |||
1632 | floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-best"), | 1573 | floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-best"), | ||
1633 | i18n("Default Zoom"), this, SLOT(ZoomDefault())); | 1574 | i18n("Default Zoom"), this, SLOT(ZoomDefault())); | ||
1634 | 1575 | | |||
1635 | floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-width"), | 1576 | floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-width"), | ||
1636 | i18n("Zoom to Fit"), this, SLOT(ZoomToFit())); | 1577 | i18n("Zoom to Fit"), this, SLOT(ZoomToFit())); | ||
1637 | 1578 | | |||
1579 | toggleStretchAction = floatingToolBar->addAction(QIcon::fromTheme("transform-move"), | ||||
1580 | i18n("Toggle Stretch"), | ||||
1581 | this, SLOT(toggleStretch())); | ||||
1582 | toggleStretchAction->setCheckable(true); | ||||
1583 | | ||||
1584 | | ||||
1638 | floatingToolBar->addSeparator(); | 1585 | floatingToolBar->addSeparator(); | ||
1639 | 1586 | | |||
1640 | action = floatingToolBar->addAction(QIcon::fromTheme("crosshairs"), | 1587 | action = floatingToolBar->addAction(QIcon::fromTheme("crosshairs"), | ||
1641 | i18n("Show Cross Hairs"), this, SLOT(toggleCrosshair())); | 1588 | i18n("Show Cross Hairs"), this, SLOT(toggleCrosshair())); | ||
1642 | action->setCheckable(true); | 1589 | action->setCheckable(true); | ||
1643 | 1590 | | |||
1644 | action = floatingToolBar->addAction(QIcon::fromTheme("map-flat"), | 1591 | action = floatingToolBar->addAction(QIcon::fromTheme("map-flat"), | ||
1645 | i18n("Show Pixel Gridlines"), this, SLOT(togglePixelGrid())); | 1592 | i18n("Show Pixel Gridlines"), this, SLOT(togglePixelGrid())); | ||
▲ Show 20 Lines • Show All 126 Lines • Show Last 20 Lines |