Compare commits

...

36 Commits

Author SHA1 Message Date
nou 50c070b169 Fix stack size problem on MacOS 2023-11-16 22:13:32 +01:00
nou cfee287bfa Update metainfo 2023-11-16 22:13:25 +01:00
nou 61e0c542f5 Update translations and help 2023-11-16 19:11:36 +01:00
nou a42abb05ea Add scaling for float images that are out of 0.0-1.0 range 2023-11-15 10:14:25 +01:00
nou 5c6df4a59f Optimize calculating stast for debayer 2023-11-15 10:13:49 +01:00
nou 35d5934227 Better stretch non full range images like CR2 2023-11-15 10:12:55 +01:00
nou 8e3c1b35db Remove dead code 2023-11-15 10:02:53 +01:00
nou 544e4abf92 Made unlinked stretch white balancing 2023-11-14 23:47:47 +01:00
nou e97e10fb5b Make STF slider wider 2023-11-14 16:30:57 +01:00
nou 2608a1bc79 Add tooltip with filenames 2023-11-14 16:30:38 +01:00
nou fa69f17e51 Support for unlinked stretch 2023-11-14 12:08:28 +01:00
nou 4a9d720343 Sum all channels histograms 2023-11-14 12:04:55 +01:00
nou d462ece7c9 Improve histogram for 8 bit images 2023-11-13 23:15:34 +01:00
nou 46b0210078 Add histogram 2023-11-13 21:12:23 +01:00
nou 0a803ace10 Prepare for three channels STF 2023-10-10 22:48:40 +02:00
nou 0c2c5f908c Hide fsanitizer behind option 2023-10-10 21:42:26 +02:00
nou c2197298a7 Hide analyze menu 2023-09-30 23:13:11 +02:00
nou e8630330b2 Show stats for each channel 2023-09-30 23:13:02 +02:00
nou 5955a02175 Fix loading RAW files 2023-09-10 22:14:27 +02:00
nou c0b9194ecc Add header files to cmakelists.txt 2023-09-09 17:39:09 +02:00
nou 5f27acbfd1 Add test files 2023-08-29 23:17:16 +02:00
nou f1a2aae9b6 Update LibXISF, fixes in RawImage 2023-08-28 20:38:35 +02:00
nou 9ffbdcee30 Get rid of raw pointers 2023-06-17 21:47:06 +02:00
nou d9b1c253db Move initialization of member variables 2023-06-17 21:45:35 +02:00
nou 7e39304799 Get rid of some explicit new allocation 2023-06-17 20:32:58 +02:00
nou 31cf1ee2b1 Getting rid of opencv 2023-06-16 23:40:11 +02:00
nou ab245f0484 Add false color rendering 2023-06-02 20:41:34 +02:00
nou 77c312800a Update metainfo 2023-04-19 19:07:15 +02:00
nou 21e90b92dc Fix assert lo < hi in std::clamp 2023-04-19 14:33:29 +02:00
nou 2817d3c7c9 Update libXISF 2023-04-12 22:24:45 +02:00
nou a51b0ef02c Floor offset to to prevent half pixel drawing 2023-03-13 22:49:34 +01:00
nou 7b19230366 Fix disrepancy between wheel scroll and bar 2023-03-12 16:44:18 +01:00
nou 26666ee36d Improved zoom and scrolling 2023-03-12 13:45:07 +01:00
nou 74aee15f80 Specify layout index in shader 2023-03-11 13:38:36 +01:00
nou fde1594086 Add file list sorting 2023-03-11 10:41:26 +01:00
nou a9783f6030 Update LibXISF 2023-03-09 18:35:38 +01:00
60 changed files with 4352 additions and 877 deletions
+25 -19
View File
@@ -12,8 +12,12 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
option(SANITIZE_ADDRESS_LEAK "Enable -fsanitize=address -fsanitize=leak" OFF)
if(SANITIZE_ADDRESS_LEAK)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
endif(SANITIZE_ADDRESS_LEAK)
find_package(Qt5 COMPONENTS Widgets Sql OpenGL REQUIRED)
find_package(OpenCV REQUIRED)
find_library(GSL_LIB gsl REQUIRED)
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
find_library(EXIF_LIB exif REQUIRED)
@@ -24,25 +28,27 @@ find_library(WCS_LIB wcs wcslib PATHS REQUIRED)
add_subdirectory(libXISF)
set(TENMON_SRC
about.cpp
database.cpp
databaseview.cpp
about.cpp about.h
database.cpp database.h
databaseview.cpp databaseview.h
delete.cpp
filesystemwidget.cpp
imageinfo.cpp
imageringlist.cpp
filesystemwidget.cpp filesystemwidget.h
histogram.cpp histogram.h
imageinfo.cpp imageinfo.h
imageringlist.cpp imageringlist.h
imagescrollarea.cpp
imagescrollareagl.cpp
loadrunable.cpp
imagescrollareagl.cpp imagescrollareagl.h
loadrunable.cpp loadrunable.h
main.cpp
mainwindow.cpp
markedfiles.cpp
rawimage.cpp
settingsdialog.cpp
starfit.cpp
statusbar.cpp
stfslider.cpp
stretchtoolbar.cpp
mainwindow.cpp mainwindow.h
markedfiles.cpp markedfiles.h
rawimage.cpp rawimage.h
rawimage_sse.cpp
settingsdialog.cpp settingsdialog.h
starfit.cpp starfit.h
statusbar.cpp statusbar.h
stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h
)
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
@@ -70,13 +76,13 @@ endif()
add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
target_include_directories(tenmon PRIVATE ${OpenCV_INCLUDE_DIRS} ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR})
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR})
if(UNIX AND NOT APPLE)
target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS})
endif()
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${OpenCV_LIBS} ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} XISF)
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} XISF)
if(APPLE)
target_link_libraries(tenmon "-framework CoreFoundation")
else()
+6 -4
View File
@@ -11,8 +11,8 @@ img { margin: 5px; }
<p>Tenmon is intended primarily for viewing astro photos and images. It supports the following formats:
<ul>
<li>FITS 8, 16 bit integer and 32 bit float</li>
<li>XISF 8, 16 bit integer and 32 bit float</li>
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
<li>CR2, NEF, DNG raw images</li>
</ul>
@@ -54,12 +54,14 @@ To open an image, you can also drag and drop it to main window.</p>
<li>mid point - defines the value to be stretched to 50% intensity</li>
<li>white point - all pixels with higher value (brighter) than this will be clipped white</li>
</ul>
Following the slider are 5 buttons for automatic stretching:
Following the slider are 7 buttons for automatic stretching:
<ul>
<li><i>Linked channels</i> toggle stretching each RGB channel individually.</li>
<li><i>Auto Stretch</i> automatically apply black and mid points to render the image with optimal brightness.</li>
<li><i>Reset</i> reset three values for black, mid and white point to default.</li>
<li><i>Invert</i> invert colors to display the image as negative.</li>
<li><i>Super pixel CFA </i> average 2x2 pixels into one (suitable for images from colour camera).</li>
<li><i>False colors</i> show black and white in false colour palette.</li>
<li><i>Debayer CFA</i> Demosaicing black and white image from CFA sensor to color one.</li>
<li><i>Apply Auto stretch on load</i> toggle automatically applying Autostretch for each image when loaded.</p>
</ul>
+6 -3
View File
@@ -10,8 +10,8 @@ p { padding:0px; margin:5px 5px 10px 5px; }
<p>Tenmon slúži primárne na zobrazenie astronomických fotiek a obrázkov. Dokáže otvoriť nasledovné formáty:
<ul>
<li>FITS 8, 16 bitové celočíselné a 32 bitové s plávajúcou čiarkou</li>
<li>XISF 8, 16 bitové celočíselné a 32 bitové s plávajúcou čiarkou</li>
<li>FITS 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
<li>XISF 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM a SVG obrázky</li>
<li>CR2, NEF, DNG raw obrázky</li>
</ul>
@@ -49,9 +49,12 @@ na ktorej sa dajú nastaviť tri body.
<li>stredný bod - pixeli s touto hodnotou budú zobrazené ako 50% šedá</li>
<li>biely bod - pixeli nad touto hodnotou budú zobrazené ako biele</li>
</ul>
Prvé tlačidlo prepína prepojenie nastavenia čierneho, stretdného a bieleho bodu. Po prepnutí sa dá každý farebný kanál nastaviť
samostatne.
Nasleduje tlačidlo ktoré nastaví hodnoty čierneho a stredného bodu tak aby bol obrázok zobrazený optimálnym jasom.
Druhé tlačidlo resetuje hodnoty pre čierny, stredný a biely bod na východzie hodnoty. Invertovanie farieb zobrazí obrázok ako negatív.
Super pixel CFA spriemeruje dva krát dva pixeli do jedného čo je vhodné pri prezeraní surových obrázkov z farebných kamier.
Falošné farby zobraí čiernobiele obrázky vo farebnej škále.
Prevoď CFA na farbu prevedie demozaikovanie čiernobieleho obrázku na farebný.
Posledné tlačidlo zapína a vypína nastavovanie optimálnych hodnôt úrovní pre každý obrázok zvlášť.</p>
<h3>Označovanie obrázkov</h3>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

+4
View File
@@ -123,6 +123,10 @@ QVariant FITSFileModel::data(const QModelIndex &index, int role) const
font.setBold(m_markedFiles.contains(file));
return font;
}
if(role == Qt::ToolTipRole && index.column() == 0)
{
return QSqlQueryModel::data(index, Qt::DisplayRole);
}
return QSqlQueryModel::data(index, role);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

+19 -1
View File
@@ -20,6 +20,18 @@ FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) :
connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FilesystemWidget::fileClicked);
}
void FilesystemWidget::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu;
menu.addAction(tr("Sort by filename"), [this](){ emit sortChanged(QDir::Name); });
menu.addAction(tr("Sort by time"), [this](){ emit sortChanged(QDir::Time); });
menu.addAction(tr("Sort by size"), [this](){ emit sortChanged(QDir::Size); });
menu.addAction(tr("Sort by type"), [this](){ emit sortChanged(QDir::Type); });
menu.addAction(tr("Reverse"), [this](){ emit reverseSort(); });
menu.exec(event->globalPos());
}
void FilesystemWidget::selectFile(int row)
{
QModelIndex index = m_model->index(row, 0);
@@ -33,11 +45,17 @@ void FilesystemWidget::fileClicked(const QModelIndex &index, const QModelIndex &
emit fileSelected(index.row());
}
QVariant FileSystemModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::ToolTipRole && index.column() == 0)role = Qt::DisplayRole;
return QFileSystemModel::data(index, role);
}
Filetree::Filetree(QWidget *parent) : QTreeView(parent)
{
QSettings settings;
m_rootDir = settings.value("filetree/rootDir", QDir::homePath()).toString();
m_fileSystemModel = new QFileSystemModel(this);
m_fileSystemModel = new FileSystemModel(this);
m_fileSystemModel->setRootPath(m_rootDir);
m_fileSystemModel->setNameFilters({"*.fits", "*.fit", "*.xisf", "*.jpg", "*.jpeg", "*.png", "*.cr2", "*.nef", "*.dng"});
m_fileSystemModel->setNameFilterDisables(false);
+11 -1
View File
@@ -13,17 +13,27 @@ class FilesystemWidget : public QWidget
QAbstractItemModel *m_model;
public:
explicit FilesystemWidget(QAbstractItemModel *model, QWidget *parent = nullptr);
void contextMenuEvent(QContextMenuEvent *event) override;
private slots:
void selectFile(int row);
void fileClicked(const QModelIndex &index, const QModelIndex &);
signals:
void fileSelected(int row);
void sortChanged(QDir::SortFlag sort);
void reverseSort();
};
class FileSystemModel : public QFileSystemModel
{
public:
explicit FileSystemModel(QObject *parent) : QFileSystemModel(parent){}
QVariant data(const QModelIndex &index, int role) const override;
};
class Filetree : public QTreeView
{
Q_OBJECT
QFileSystemModel *m_fileSystemModel;
FileSystemModel *m_fileSystemModel;
QString m_rootDir;
public:
explicit Filetree(QWidget *parent = nullptr);
+84
View File
@@ -0,0 +1,84 @@
#include "histogram.h"
#include <cmath>
#include <algorithm>
#include <QPainter>
#include <QDebug>
Histogram::Histogram(QWidget *parent) : QWidget(parent)
{
setStyleSheet("QWidget { background: white; color: black; } ");
}
void Histogram::imageLoaded(Image *img)
{
if(img && img->rawImage())
{
m_histogram = img->rawImage()->imageStats().m_histogram;
m_points.clear();
update();
}
}
void Histogram::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(rect(), Qt::black);
uint h = height();
uint w = width();
if(m_histogram.size())
{
if(m_points.size() != w)
{
m_points.clear();
for(uint64_t i = 0; i < w; i++)
{
uint32_t sum = 0;
uint64_t start = i * m_histogram.size() / w;
uint64_t end =(i+1) * m_histogram.size() / w;
for(uint64_t o = start; o < end; o++)
sum += m_histogram[o];
if(start != end)
m_points.push_back(sum);
}
float scale = *std::max_element(m_points.begin(), m_points.end());
if(m_log)
{
scale = std::log(scale);
std::for_each(m_points.begin(), m_points.end(), [scale](float &x){ x = (x > 0 ? std::log(x) : 0.0f) / scale; });
}
else
{
std::for_each(m_points.begin(), m_points.end(), [scale](float &x){ x /= scale; });
}
}
std::vector<QPointF> points;
points.push_back(QPointF(0, h));
for(size_t i = 0; i < m_points.size(); i++)
{
points.push_back(QPointF((float)i * w / m_points.size(), h - m_points[i] * h));
}
points.push_back(QPoint(w, h));
painter.setBrush(Qt::gray);
painter.setPen(Qt::white);
painter.drawPolygon(&points[0], points.size());
}
QStyleOptionButton button;
button.initFrom(this);
button.state = m_log ? QStyle::State_On : QStyle::State_Off;
button.text = tr("Logarithmic scale");
button.rect = style()->subElementRect(QStyle::SE_CheckBoxClickRect, &button, this);
button.rect.moveTop(0);
button.rect.moveRight(w);
style()->drawControl(QStyle::CE_CheckBox, &button, &painter, this);
}
void Histogram::mouseReleaseEvent(QMouseEvent *)
{
m_log = !m_log;
m_points.clear();
update();
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef HISTOGRAM_H
#define HISTOGRAM_H
#include <QWidget>
#include "imageringlist.h"
class Histogram : public QWidget
{
Q_OBJECT
std::vector<uint32_t> m_histogram;
std::vector<float> m_points;
bool m_log = false;
public:
explicit Histogram(QWidget *parent = nullptr);
public slots:
void imageLoaded(Image *img);
protected:
void paintEvent(QPaintEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
};
#endif // HISTOGRAM_H
+6 -6
View File
@@ -20,10 +20,10 @@ FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByte
FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
{
key = record.name.toUtf8();
comment = record.comment.toUtf8();
key = record.name.c_str();
comment = record.comment.c_str();
QString string = record.value;
QString string = record.value.c_str();
if(string.startsWith('\'') && string.endsWith('\''))
{
string.chop(1);
@@ -45,9 +45,9 @@ FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
FITSRecord::FITSRecord(const LibXISF::Property &property)
{
key = property.id.toUtf8();
value = property.value;
comment = property.comment.toUtf8();
key = property.id.c_str();
value = QString::fromStdString(property.value.toString());
comment = property.comment.c_str();
}
QByteArray FITSRecord::valueToByteArray() const
+30 -9
View File
@@ -81,24 +81,20 @@ void Image::clearThumbnail()
m_thumbnail.reset();
}
void Image::imageLoaded(void *rawImage, ImageInfoData info)
void Image::imageLoaded(std::shared_ptr<RawImage> rawImage, ImageInfoData info)
{
m_loading = false;
if(!m_released)
{
m_rawImage.reset(static_cast<RawImage*>(rawImage));
m_rawImage = rawImage;
m_info = info;
emit pixmapLoaded(this);
}
else
{
delete static_cast<RawImage*>(rawImage);
}
}
void Image::thumbnailLoadFinish(void *rawImage)
void Image::thumbnailLoadFinish(std::shared_ptr<RawImage> rawImage)
{
m_thumbnail.reset(static_cast<RawImage*>(rawImage));
m_thumbnail = rawImage;
if(m_thumbnail)
emit thumbnailLoaded(this);
}
@@ -129,7 +125,9 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile)
if(dir.exists())
{
QStringList list = dir.entryList(m_nameFilter, QDir::Files | QDir::Readable, m_liveMode ? QDir::Time : QDir::Name | QDir::IgnoreCase);
QDir::SortFlags sortFlags = m_liveMode ? QDir::Time : m_sort | QDir::IgnoreCase;
if(m_reversed)sortFlags |= QDir::Reversed;
QStringList list = dir.entryList(m_nameFilter, QDir::Files | QDir::Readable, sortFlags);
QStringList absolutePaths;
foreach(const QString &file, list)
{
@@ -377,6 +375,29 @@ void ImageRingList::setPreload(int width)
m_width = newWidth;
}
void ImageRingList::setSort(QDir::SortFlag sort)
{
if(m_sort != sort)
{
m_sort = sort;
if(m_images.size())
{
QString path = (*m_currImage)->name();
setFile(path);
}
}
}
void ImageRingList::reverseSort()
{
m_reversed = !m_reversed;
if(m_images.size())
{
QString path = (*m_currImage)->name();
setFile(path);
}
}
void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
{
QThreadPool::globalInstance()->clear();
+8 -3
View File
@@ -5,6 +5,7 @@
#include <QFileSystemWatcher>
#include <QList>
#include <QPixmap>
#include <QDir>
#include <memory>
#include "imageinfo.h"
#include "rawimage.h"
@@ -20,7 +21,7 @@ class Image : public QObject
bool m_current;
int m_number;
std::shared_ptr<RawImage> m_rawImage;
std::unique_ptr<RawImage> m_thumbnail;
std::shared_ptr<RawImage> m_thumbnail;
QString m_name;
ImageInfoData m_info;
ImageRingList *m_ringList;
@@ -40,8 +41,8 @@ signals:
void pixmapLoaded(Image *ptr);
void thumbnailLoaded(Image *ptr);
protected slots:
void imageLoaded(void *rawImage, ImageInfoData info);
void thumbnailLoadFinish(void *rawImage);
void imageLoaded(std::shared_ptr<RawImage> rawImage, ImageInfoData info);
void thumbnailLoadFinish(std::shared_ptr<RawImage> rawImage);
};
typedef std::shared_ptr<Image> ImagePtr;
@@ -58,6 +59,8 @@ class ImageRingList : public QAbstractItemModel
QList<ImagePtr>::iterator m_lastImage;
QFileSystemWatcher m_fileSystemWatcher;
bool m_liveMode;
QDir::SortFlag m_sort = QDir::Name;
bool m_reversed = false;
AnalyzeLevel m_analyzeLevel;
QThreadPool *m_thumbPool;
Database *m_database;
@@ -91,6 +94,8 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
public slots:
void setPreload(int width);
void setSort(QDir::SortFlag sort);
void reverseSort();
protected:
void setFiles(const QStringList files, const QString &currentFile = QString());
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
+181 -232
View File
@@ -12,70 +12,57 @@
#include <QPainter>
#include <QFileInfo>
#include <cmath>
#include <QElapsedTimer>
struct RawImageType
{
QOpenGLTexture::PixelFormat pixelFormat;
QOpenGLTexture::TextureFormat textureFormat;
QOpenGLTexture::PixelType dataType;
bool bw;
};
const RawImageType rawImageTypes[] = {
{QOpenGLTexture::Red, QOpenGLTexture::R8_UNorm, QOpenGLTexture::UInt8, true},
{QOpenGLTexture::Red, QOpenGLTexture::R16_UNorm, QOpenGLTexture::UInt16, true},
{QOpenGLTexture::Red, QOpenGLTexture::R32F, QOpenGLTexture::Float32, true},
#ifdef COLOR_MANAGMENT
{QOpenGLTexture::RGB, QOpenGLTexture::SRGB8, QOpenGLTexture::UInt8, false},
{QOpenGLTexture::RGBA,QOpenGLTexture::SRGB8_Alpha8, QOpenGLTexture::UInt8, false},
#else
{QOpenGLTexture::RGB, QOpenGLTexture::RGB8_UNorm, QOpenGLTexture::UInt8, false},
{QOpenGLTexture::RGBA,QOpenGLTexture::RGBA8_UNorm, QOpenGLTexture::UInt8, false},
#endif
{QOpenGLTexture::RGB, QOpenGLTexture::RGB16_UNorm, QOpenGLTexture::UInt16, false},
{QOpenGLTexture::RGBA, QOpenGLTexture::RGB16_UNorm, QOpenGLTexture::UInt16, false},
{QOpenGLTexture::RGB, QOpenGLTexture::RGB32F, QOpenGLTexture::Float32, false}
};
static bool MANUAL_MIPMAP_GEN = false;
void setScrollRange(QScrollBar *scrollBar, int newRange)
RawImageType getRawImageType(const RawImage *img)
{
int page = scrollBar->pageStep();
int pos = scrollBar->value() + page/2;
int range = scrollBar->maximum() + page;
float relPos = (float)pos/(float)range;
RawImageType type;
switch(img->type())
{
case RawImage::UINT8:
if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::SRGB8_Alpha8;
else
type.textureFormat = QOpenGLTexture::R8_UNorm;
type.dataType = QOpenGLTexture::UInt8;
break;
case RawImage::UINT16:
if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::RGBA16_UNorm;
else
type.textureFormat = QOpenGLTexture::R16_UNorm;
type.dataType = QOpenGLTexture::UInt16;
break;
case RawImage::FLOAT32:
if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::RGBA32F;
else
type.textureFormat = QOpenGLTexture::R32F;
type.dataType = QOpenGLTexture::Float32;
}
if(page >= newRange)
scrollBar->hide();
if(img->channels() >= 3)
type.pixelFormat = QOpenGLTexture::RGBA;
else
scrollBar->show();
type.pixelFormat = QOpenGLTexture::Red;
scrollBar->setRange(0, newRange - page);
scrollBar->setValue(relPos*newRange - page/2);
return type;
}
ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(parent)
, m_database(database)
{
setFocusPolicy(Qt::ClickFocus);
m_range = UINT16_MAX;
m_low = 0;
m_mid = 0.5;
m_high = 1;
m_dx = m_dy = 0;
m_scale = 1.0f;
m_blockRepaint = false;
m_range = UINT16_MAX;
m_imgWidth = m_imgHeight = -1;
m_superpixel = m_invert = false;
m_showThumbnails = false;
m_selecting = false;
m_thumbnailCount = 0;
m_updateTimer = new QTimer(this);
m_updateTimer->setInterval(500);
m_updateTimer->setSingleShot(true);
m_sizesDirty = false;
connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
setAcceptDrops(true);
QTimer::singleShot(1000, [this](){
@@ -104,14 +91,15 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
m_imgWidth = image->width();
m_imgHeight = image->height();
m_currentImg = index;
m_whiteBalance[0] = m_whiteBalance[1] = m_whiteBalance[2] = 1.0f;
if(!m_image)return;
const RawImageType &rawImageType = rawImageTypes[image->type()];
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8 || rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
m_bwImg = rawImageType.bw;
RawImageType rawImageType = getRawImageType(image.get());
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
m_bwImg = image->channels() == 1;
QElapsedTimer timer;
timer.start();
m_image->destroy();
m_image->setAutoMipMapGenerationEnabled(false);
m_image->setFormat(rawImageType.textureFormat);
@@ -122,47 +110,31 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
m_image->setBorderColor(0, 0, 0, 0);
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data(), m_transferOptions.get());
m_image->generateMipMaps();
qDebug() << "setImage" << timer.elapsed();
auto sRGB_linear = [](cv::Point3f &pixel, const int *pos)
m_unit_scale[0] = 1.0f;
m_unit_scale[1] = 0.0f;
auto &stats = image->imageStats();
if(image->type() == RawImage::FLOAT32 || image->type() == RawImage::FLOAT64)
{
pixel.x = pixel.x <= 0.04045f ? pixel.x / 12.92f : std::pow((pixel.x + 0.055) / 1.055f, 2.4f);
pixel.y = pixel.y <= 0.04045f ? pixel.y / 12.92f : std::pow((pixel.y + 0.055) / 1.055f, 2.4f);
pixel.z = pixel.z <= 0.04045f ? pixel.z / 12.92f : std::pow((pixel.z + 0.055) / 1.055f, 2.4f);
};
auto linear_sRGB = [](cv::Point3f &pixel, const int *pos)
{
pixel.x = pixel.x <= 0.0031308f ? pixel.x * 12.92f : 1.055f * std::pow(pixel.x , 1/2.4f) - 0.055f;
pixel.y = pixel.y <= 0.0031308f ? pixel.y * 12.92f : 1.055f * std::pow(pixel.y , 1/2.4f) - 0.055f;
pixel.z = pixel.z <= 0.0031308f ? pixel.z * 12.92f : 1.055f * std::pow(pixel.z , 1/2.4f) - 0.055f;
};
//AMD OpenGL driver on Windows doesn't generate mipmaps for sRGB textures correctly
if(m_srgb && MANUAL_MIPMAP_GEN)
{
cv::Mat img = image->mat();
img.convertTo(img, CV_32FC3, 1/255.0);
img.forEach<cv::Point3f>(sRGB_linear);
cv::Size size(img.cols, img.rows);
for(int i=1; i<m_image->mipLevels(); i++)
float min = *std::min_element(stats.m_min, stats.m_min + 4);
float max = *std::max_element(stats.m_max, stats.m_max + 4);
if(min < 0.0f || max > 1.0f)
{
cv::Mat mip;
size /= 2;
cv::resize(img, mip, size);
mip.copyTo(img);
mip.forEach<cv::Point3f>(linear_sRGB);
mip.convertTo(mip, CV_8UC3, 255);
m_image->setData(i, rawImageType.pixelFormat, rawImageType.dataType, (const void*)mip.ptr(), m_transferOptions.get());
m_unit_scale[0] = 1.0f / (max - min);
m_unit_scale[1] = min * m_unit_scale[0];
}
}
else m_image->generateMipMaps();
if(m_debayerTex)
{
f->glDeleteTextures(1, &m_debayerTex);
m_debayerTex = 0;
}
update();
if(m_bestFit)bestFit();
else setOffset(m_dx, m_dy);
}
void ImageWidget::setWCS(std::shared_ptr<WCSData> wcs)
@@ -170,10 +142,35 @@ void ImageWidget::setWCS(std::shared_ptr<WCSData> wcs)
m_wcs = wcs;
}
void ImageWidget::setScale(float scale)
void ImageWidget::zoom(int zoom, const QPointF &mousePos)
{
m_scale = scale;
update();
m_bestFit = false;
if(zoom != 0)
m_scaleStop = std::clamp(m_scaleStop + (zoom > 0 ? 1 : -1), -10, 10);
else
m_scaleStop = 0;
QPointF focus(m_width * 0.5f, m_height * 0.5f);
if(!mousePos.isNull())
focus = mousePos;
if(width() > m_image->width() * m_scale)
m_dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f;
if(height() > m_image->height() * m_scale)
m_dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
float newScale = std::sqrt(std::pow(2.0f, (float)m_scaleStop));
float r = newScale / m_scale;
m_scale = newScale;
setOffset(m_dx * r + focus.x() * (r - 1), m_dy * r + focus.y() * (r - 1));
}
void ImageWidget::bestFit()
{
m_bestFit = true;
m_scale = std::min((float)m_width/m_imgWidth, (float)m_height/m_imgHeight);
setOffset(0, 0);
}
void ImageWidget::blockRepaint(bool block)
@@ -203,18 +200,33 @@ void ImageWidget::allocateThumbnails(const QStringList &paths)
m_thumbnailTexture->allocateStorage();
}
void ImageWidget::setMTFParams(float low, float mid, float high)
QVector2D ImageWidget::getImagePixelCoord(const QVector2D &pos)
{
m_low = low;
m_mid = mid;
m_high = high;
float dx = m_dx;
float dy = m_dy;
if(m_width > m_image->width()*m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f;
if(m_height > m_image->height()*m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f;
QVector2D offset(dx, dy);
return (pos + offset) / m_scale;
}
void ImageWidget::setMTFParams(const MTFParam &params)
{
m_mtfParams = params;
update();
}
void ImageWidget::setOffset(int dx, int dy)
void ImageWidget::setOffset(float dx, float dy)
{
m_dx = dx;
m_dy = dy;
m_dx = std::clamp(dx, 0.0f, std::max(0.0f, m_imgWidth * m_scale - m_width));
if(m_showThumbnails)
m_dy = std::clamp(dy, 0.0f, std::max(0.0f, (float)((m_thumbnailCount / (m_width / THUMB_SIZE_BORDER) + 2) * THUMB_SIZE_BORDER_Y - m_height)));
else
m_dy = std::clamp(dy, 0.0f, std::max(0.0f, m_imgHeight * m_scale - m_height));
updateScrollBars();
update();
}
@@ -230,6 +242,12 @@ void ImageWidget::invert(bool enable)
update();
}
void ImageWidget::falseColor(bool enable)
{
m_falseColor = enable;
update();
}
QImage ImageWidget::renderToImage()
{
if(m_imgWidth < 0)return QImage();
@@ -258,7 +276,8 @@ void ImageWidget::thumbnailLoaded(const Image *image)
{
makeCurrent();
const RawImage *raw = image->thumbnail();
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGB, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
if(!raw)return;
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
float a = raw->thumbAspect();
int sizes[3] = { std::max(1, a > 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE * a)), std::max(1, a < 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE / a)), image->number() };
m_sizesDirty = true;
@@ -269,7 +288,7 @@ void ImageWidget::thumbnailLoaded(const Image *image)
void ImageWidget::showThumbnail(bool enable)
{
m_showThumbnails = enable;
update();
setOffset(m_dx, m_dy);
}
void ImageWidget::paintGL()
@@ -278,10 +297,10 @@ void ImageWidget::paintGL()
float dx = m_dx;
float dy = m_dy;
if(width() > m_image->width()*m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f;
if(height() > m_image->height()*m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f;
if(m_width > m_image->width() * m_scale)
dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f;
if(m_height > m_image->height() * m_scale)
dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
QBrush highlight = style()->standardPalette().highlight();
if(m_showThumbnails)
@@ -306,7 +325,7 @@ void ImageWidget::paintGL()
m_thumbnailProgram->bind();
f->glUniform3i(m_thumbnailProgram->uniformLocation("viewport_row"), width(), height(), width()/THUMB_SIZE_BORDER);
f->glUniform3i(m_thumbnailProgram->uniformLocation("thumb_size"), THUMB_SIZE_BORDER/2, THUMB_SIZE_BORDER, THUMB_SIZE_BORDER_Y);
m_thumbnailProgram->setUniformValue("mtf_param", m_low, m_mid, m_high);
m_thumbnailProgram->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
m_thumbnailProgram->setUniformValue("invert", m_invert);
m_thumbnailProgram->setUniformValue("offset", 0, m_dy);
QMatrix4x4 mvp;
@@ -354,13 +373,13 @@ void ImageWidget::paintGL()
m_program->bind();
m_program->setUniformValue("viewport", (float)width(), (float)height());
m_program->setUniformValue("offset", dx, dy);
m_program->setUniformValue("mtf_param", m_low, m_mid, m_high);
m_program->setUniformValue("offset", std::floor(dx), std::floor(dy));
m_program->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
m_program->setUniformValue("unit_scale", m_unit_scale[0], m_unit_scale[1]);
m_program->setUniformValue("zoom", 1.0f/m_scale);
m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert);
if(m_superpixel)m_program->setUniformValue("whiteBalance", m_whiteBalance[0], m_whiteBalance[1], m_whiteBalance[2]);
else m_program->setUniformValue("whiteBalance", 1.0f, 1.0f, 1.0f);
#ifdef COLOR_MANAGMENT
m_program->setUniformValue("srgb", m_srgb);
#endif
@@ -374,6 +393,8 @@ void ImageWidget::resizeGL(int w, int h)
m_width = w;
m_height = h;
f->glViewport(0, 0, w, h);
if(m_bestFit)bestFit();
updateScrollBars();
}
void ImageWidget::initializeGL()
@@ -425,7 +446,7 @@ void ImageWidget::initializeGL()
m_program = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/image.vert");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/image.frag");
if(f3)f3->glBindFragDataLocation(m_program->programId(), 0, "color");
if(!m_program->link())
{
qDebug() << "Link failed" << m_program->log();
@@ -442,7 +463,7 @@ void ImageWidget::initializeGL()
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/debayer.vert");
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/debayer.frag");
if(f3)f3->glBindFragDataLocation(m_debayerProgram->programId(), 0, "color");
m_debayerProgram->bind();
m_debayerProgram->enableAttributeArray("qt_Vertex");
m_debayerProgram->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 2, sizeof(float)*4);
@@ -459,7 +480,7 @@ void ImageWidget::initializeGL()
m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/thumb.vert");
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/thumb.frag");
if(f3)f3->glBindFragDataLocation(m_thumbnailProgram->programId(), 0, "color");
m_thumbnailProgram->bind();
m_thumbnailProgram->enableAttributeArray("qt_Vertex");
m_thumbnailProgram->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 2, sizeof(float)*4);
@@ -536,7 +557,10 @@ void ImageWidget::mousePressEvent(QMouseEvent *event)
thumbSelect(event);
}
else
event->ignore();
{
if(event->button() == Qt::LeftButton)
m_lastPos = event->localPos();
}
}
void ImageWidget::mouseMoveEvent(QMouseEvent *event)
@@ -545,22 +569,18 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
{
thumbSelect(event);
}
else
event->ignore();
else if(!m_lastPos.isNull())
{
QPointF off = event->localPos() - m_lastPos;
m_lastPos = event->localPos();
setOffset(m_dx - off.x(), m_dy - off.y());
return;
}
if(!m_showThumbnails && m_rawImage)
{
float dx = m_dx;
float dy = m_dy;
if(width() > m_image->width()*m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f;
if(height() > m_image->height()*m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f;
QVector2D offset(dx, dy);
QVector2D pos = QVector2D(event->pos());
QVector2D pix = (pos + offset) / m_scale;
QVector3D rgb;
QVector2D pix = getImagePixelCoord(QVector2D(event->pos()));
double r,g,b;
SkyPoint sky;
if(m_wcs)
@@ -568,12 +588,12 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
m_wcs->pixelToWorld(QPointF(pix.x(), pix.y()), sky);
}
if(m_rawImage->pixel(pix.x(), pix.y(), rgb))
if(m_rawImage->pixel(pix.x(), pix.y(), r, g, b))
{
if(m_bwImg)
emit status(tr("L:%1").arg(rgb.x()), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
emit status(tr("L:%1").arg(r), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
else
emit status(tr("R:%1 G:%2 B:%3").arg(rgb.x()).arg(rgb.y()).arg(rgb.z()), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
emit status(tr("R:%1 G:%2 B:%3").arg(r).arg(g).arg(b), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
}
}
}
@@ -604,7 +624,22 @@ void ImageWidget::mouseReleaseEvent(QMouseEvent *event)
m_database->unmark(unmark);
}
else
event->ignore();
{
m_lastPos = QPointF();
}
}
void ImageWidget::wheelEvent(QWheelEvent *event)
{
if(m_showThumbnails)
{
setOffset(0, m_dy - event->angleDelta().y());
}
else
{
if(std::abs(event->angleDelta().y()) > 15)
zoom(event->angleDelta().y(), event->modifiers() & Qt::ShiftModifier ? QPointF() : event->posF());
}
}
void ImageWidget::thumbSelect(QMouseEvent *event)
@@ -656,19 +691,14 @@ void ImageWidget::debayer()
f->glGenerateMipmap(GL_TEXTURE_2D);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
int size = std::max(m_imgWidth, m_imgHeight);
int level = 0;
while(size >>= 1)level++;
int w,h;
f3->glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &w);
f3->glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &h);
uint16_t pixel[w*h*4];
f3->glGetTexImage(GL_TEXTURE_2D, level, GL_RGBA, GL_UNSIGNED_SHORT, pixel);
float maxRGB = std::max(std::max(pixel[0], pixel[1]), pixel[2]);
m_whiteBalance[0] = maxRGB / pixel[0];
m_whiteBalance[1] = maxRGB / pixel[1];
m_whiteBalance[2] = maxRGB / pixel[2];
void ImageWidget::updateScrollBars()
{
if(m_showThumbnails)
emit scrollBarsUpdate(0, 0, -1, m_dy, m_height, (m_thumbnailCount / (m_width / THUMB_SIZE_BORDER) + 2) * THUMB_SIZE_BORDER_Y - m_height);
else
emit scrollBarsUpdate(m_dx, m_width, m_imgWidth * m_scale - m_width, m_dy, m_height, m_imgHeight * m_scale - m_height);
}
ImageScrollAreaGL::ImageScrollAreaGL(Database *database, QWidget *parent) : QWidget(parent)
@@ -680,9 +710,6 @@ ImageScrollAreaGL::ImageScrollAreaGL(Database *database, QWidget *parent) : QWid
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
m_scale = 1.0f;
m_bestFit = false;
m_thumbCount = 0;
layout->setSpacing(0);
layout->addWidget(m_imageWidget, 0, 0);
@@ -691,6 +718,7 @@ ImageScrollAreaGL::ImageScrollAreaGL(Database *database, QWidget *parent) : QWid
connect(m_verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
connect(m_horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
connect(m_imageWidget, &ImageWidget::scrollBarsUpdate, this, &ImageScrollAreaGL::updateScrollbars);
}
ImageScrollAreaGL::~ImageScrollAreaGL()
@@ -704,10 +732,6 @@ void ImageScrollAreaGL::setImage(Image *image)
{
m_imageWidget->setImage(image->rawImage(), image->number());
m_imageWidget->setWCS(image->info().wcs);
m_imgWidth = image->rawImage()->width();
m_imgHeight = image->rawImage()->height();
if(m_bestFit)bestFit();
updateScrollbars();
}
}
@@ -716,124 +740,49 @@ ImageWidget *ImageScrollAreaGL::imageWidget()
return m_imageWidget;
}
void ImageScrollAreaGL::setThumbnails(int count)
void ImageScrollAreaGL::updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV)
{
m_thumbCount = count;
if(m_thumbCount)
if(maxH > 0)
{
m_verticalScrollBar->setRange(0, m_thumbCount / (m_imageWidget->width() / THUMB_SIZE_BORDER) * THUMB_SIZE_BORDER_Y);
m_verticalScrollBar->setPageStep(THUMB_SIZE_BORDER_Y);
m_horizontalScrollBar->show();
m_horizontalScrollBar->setRange(0, maxH);
m_horizontalScrollBar->setPageStep(stepH);
m_horizontalScrollBar->setValue(valueH);
}
else
{
m_verticalScrollBar->setPageStep(m_imageWidget->height());
m_horizontalScrollBar->setPageStep(m_imageWidget->width());
}
updateScrollbars();
}
void ImageScrollAreaGL::updateScrollbars(bool zoom)
{
if(m_thumbCount)
{
m_horizontalScrollBar->hide();
if(maxV > 0)
{
m_verticalScrollBar->show();
m_verticalScrollBar->setRange(0, m_thumbCount / (m_imageWidget->width() / THUMB_SIZE_BORDER) * THUMB_SIZE_BORDER_Y);
m_verticalScrollBar->setPageStep(THUMB_SIZE_BORDER_Y);
m_verticalScrollBar->setRange(0, maxV);
m_verticalScrollBar->setPageStep(stepV);
m_verticalScrollBar->setValue(valueV);
}
else
{
if(zoom)
{
setScrollRange(m_verticalScrollBar, m_imgHeight*m_scale);
setScrollRange(m_horizontalScrollBar, m_imgWidth*m_scale);
}
else
{
m_verticalScrollBar->setRange(0, m_imgHeight*m_scale - m_verticalScrollBar->pageStep());
m_horizontalScrollBar->setRange(0, m_imgWidth*m_scale - m_horizontalScrollBar->pageStep());
}
}
}
void ImageScrollAreaGL::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if(m_thumbCount)
{
m_verticalScrollBar->setRange(0, m_thumbCount / (m_imageWidget->width() / THUMB_SIZE_BORDER) * THUMB_SIZE_BORDER_Y);
m_verticalScrollBar->setPageStep(THUMB_SIZE_BORDER_Y);
}
else
{
m_verticalScrollBar->setPageStep(m_imageWidget->height());
m_horizontalScrollBar->setPageStep(m_imageWidget->width());
}
updateScrollbars();
}
void ImageScrollAreaGL::mouseMoveEvent(QMouseEvent *event)
{
QPoint delta = m_lastPos - event->pos();
if(m_thumbCount == 0)m_horizontalScrollBar->setValue(m_horizontalScrollBar->value() + delta.x());
m_verticalScrollBar->setValue(m_verticalScrollBar->value() + delta.y());
m_lastPos = event->pos();
}
void ImageScrollAreaGL::mousePressEvent(QMouseEvent *event)
{
m_lastPos = event->pos();
}
void ImageScrollAreaGL::wheelEvent(QWheelEvent *event)
{
if(m_thumbCount)
{
m_verticalScrollBar->setValue(m_verticalScrollBar->value() - event->angleDelta().y());
}
else
{
m_bestFit = false;
if(event->angleDelta().y() != 0)
zoom(event->angleDelta().y() / 1200.0f);
}
}
void ImageScrollAreaGL::zoom(float delta)
{
if((m_scale >= 8.0f && delta > 0) || (m_scale <= 0.1f && delta < 0))return;
m_scale += delta;
m_imageWidget->blockRepaint(true);
m_imageWidget->setScale(m_scale);
updateScrollbars(true);
m_imageWidget->blockRepaint(false);
m_verticalScrollBar->hide();
}
void ImageScrollAreaGL::zoomIn()
{
zoom(0.1f);
m_bestFit = false;
m_imageWidget->zoom(1);
}
void ImageScrollAreaGL::zoomOut()
{
zoom(-0.1f);
m_bestFit = false;
m_imageWidget->zoom(-1);
}
void ImageScrollAreaGL::bestFit()
{
m_bestFit = true;
m_scale = std::min((float)m_imageWidget->width()/m_imgWidth, (float)m_imageWidget->height()/m_imgHeight);
zoom(0.0f);
m_horizontalScrollBar->hide();
m_verticalScrollBar->hide();
m_imageWidget->bestFit();
}
void ImageScrollAreaGL::oneToOne()
{
m_scale = 1.0f;
zoom(0.0f);
m_bestFit = false;
m_imageWidget->zoom(0);
}
void ImageScrollAreaGL::scrollEvent()
+36 -39
View File
@@ -14,6 +14,7 @@
#include "rawimage.h"
#include "imageringlist.h"
#include "database.h"
#include "stretchtoolbar.h"
struct ImageThumb
{
@@ -27,9 +28,9 @@ struct ImageThumb
class ImageWidget : public QOpenGLWidget
{
Q_OBJECT
QOpenGLFunctions *f;
QOpenGLFunctions_3_3_Core *f3;
QTimer *m_updateTimer;
QOpenGLFunctions *f = nullptr;
QOpenGLFunctions_3_3_Core *f3 = nullptr;
QTimer *m_updateTimer = nullptr;
std::unique_ptr<QOpenGLShaderProgram> m_program;
std::unique_ptr<QOpenGLShaderProgram> m_thumbnailProgram;
std::unique_ptr<QOpenGLShaderProgram> m_debayerProgram;
@@ -44,42 +45,46 @@ class ImageWidget : public QOpenGLWidget
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs;
int m_width, m_height;
int m_imgWidth, m_imgHeight;
int m_currentImg;
float m_low;
float m_mid;
float m_high;
float m_range;
float m_dx, m_dy;
float m_scale;
float m_whiteBalance[3] = {1.0f, 1.0f, 1.0f};
bool m_blockRepaint;
bool m_bwImg;
bool m_invert;
bool m_superpixel;
bool m_showThumbnails;
bool m_selecting;
bool m_sizesDirty;
bool m_srgb;
int m_thumbnailCount;
int m_maxTextureSize;
int m_maxArrayLayers;
int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0;
MTFParam m_mtfParams;
float m_unit_scale[2] = {1.0f, 0.0f}; // scale and offset
float m_dx = 0, m_dy = 0;
float m_scale = 1.0f;
int m_scaleStop = 0;
bool m_bestFit = false;
bool m_blockRepaint = false;
bool m_bwImg = false;
bool m_falseColor = false;
bool m_invert = false;
bool m_superpixel = false;
bool m_showThumbnails = false;
bool m_selecting = false;
bool m_sizesDirty = false;
bool m_srgb = false;
int m_thumbnailCount = 0;
int m_maxTextureSize = 0;
int m_maxArrayLayers = 0;
QVector<ImageThumb> m_thumnails;
Database *m_database;
Database *m_database = nullptr;
QPointF m_lastPos;
public:
explicit ImageWidget(Database *database, QWidget *parent = nullptr);
~ImageWidget() override;
void setImage(std::shared_ptr<RawImage> image, int index);
void setImage(const QPixmap &pixmap);
void setWCS(std::shared_ptr<WCSData> wcs);
void setScale(float scale);
void zoom(int zoom, const QPointF &mousePos = QPointF());
void bestFit();
void blockRepaint(bool block);
void allocateThumbnails(const QStringList &paths);
QVector2D getImagePixelCoord(const QVector2D &pos);
public slots:
void setMTFParams(float low, float mid, float high);
void setOffset(int dx, int dy);
void setMTFParams(const MTFParam &params);
void setOffset(float dx, float dy);
void superPixel(bool enable);
void invert(bool enable);
void falseColor(bool enable);
QImage renderToImage();
void thumbnailLoaded(const Image *image);
void showThumbnail(bool enable);
@@ -92,11 +97,14 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void thumbSelect(QMouseEvent *event);
void debayer();
void updateScrollBars();
signals:
void fileDropped(const QString &path);
void status(const QString &value, const QString &pixelCoords, const QString &celestialCoords);
void scrollBarsUpdate(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
};
class ImageScrollAreaGL : public QWidget
@@ -105,24 +113,13 @@ class ImageScrollAreaGL : public QWidget
QScrollBar *m_verticalScrollBar;
QScrollBar *m_horizontalScrollBar;
ImageWidget *m_imageWidget;
int m_imgWidth, m_imgHeight;
QPoint m_lastPos;
float m_scale;
bool m_bestFit;
int m_thumbCount;
public:
explicit ImageScrollAreaGL(Database *database, QWidget *parent = nullptr);
~ImageScrollAreaGL() override;
void setImage(Image *image);
ImageWidget* imageWidget();
void setThumbnails(int count);
protected:
void updateScrollbars(bool zoom = false);
void resizeEvent(QResizeEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void zoom(float delta);
void updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
public slots:
void zoomIn();
void zoomOut();
+1 -1
Submodule libXISF updated: 2107d68e71...8a1f305cc7
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

+134 -151
View File
@@ -82,11 +82,8 @@ void printStarModel(int radius, const std::vector<double> &data, const Star &sta
std::cout << m.toStdString() << std::endl << std::endl;
}
bool loadRAW(const QString path, ImageInfoData &info, RawImage **image)
bool loadRAW(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
{
if(!image)
return false;
std::unique_ptr<LibRaw> raw = std::make_unique<LibRaw>();
raw->open_file(path.toLocal8Bit().data());
raw->imgdata.params.half_size = true;
@@ -95,37 +92,35 @@ bool loadRAW(const QString path, ImageInfoData &info, RawImage **image)
if(raw->unpack())
return false;
if(image)
libraw_rawdata_t rawdata = raw->imgdata.rawdata;
size_t size = rawdata.sizes.width*rawdata.sizes.height;
std::vector<uint16_t> out;
out.resize(size);
size_t d = 0;
uint h=rawdata.sizes.top_margin+rawdata.sizes.height;
uint w=rawdata.sizes.left_margin+rawdata.sizes.width;
size_t pitch = rawdata.sizes.raw_pitch/sizeof(uint16_t);
for(size_t i=rawdata.sizes.top_margin;i<h;i++)
{
libraw_rawdata_t rawdata = raw->imgdata.rawdata;
size_t size = rawdata.sizes.width*rawdata.sizes.height;
std::vector<uint16_t> out;
out.resize(size);
size_t d = 0;
uint h=rawdata.sizes.top_margin+rawdata.sizes.height;
uint w=rawdata.sizes.left_margin+rawdata.sizes.width;
size_t pitch = rawdata.sizes.raw_pitch/sizeof(uint16_t);
for(size_t i=rawdata.sizes.top_margin;i<h;i++)
for(size_t o=rawdata.sizes.left_margin;o<w;o++)
{
for(size_t o=rawdata.sizes.left_margin;o<w;o++)
{
uint16_t p = rawdata.raw_image[i*pitch+o];
out[d++] = p;
}
uint16_t p = rawdata.raw_image[i*pitch+o];
out[d++] = p;
}
*image = new RawImage(rawdata.sizes.width, rawdata.sizes.height, RawImage::UINT16);
memcpy((*image)->data(), &out[0], sizeof(uint16_t)*d);
}
image = std::make_shared<RawImage>(rawdata.sizes.width, rawdata.sizes.height, 1, RawImage::UINT16);
memcpy(image->data(), &out[0], sizeof(uint16_t)*d);
QString shutterSpeed = QString::number(raw->imgdata.other.shutter);
if(raw->imgdata.other.shutter < 1)
{
shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter);
}
//info.append(StringPair(QObject::tr("Width"), QString::number(rawImg->width)));
//info.append(StringPair(QObject::tr("Height"), QString::number(rawImg->height)));
info.info.append({QObject::tr("Width"), QString::number(raw->imgdata.sizes.width)});
info.info.append({QObject::tr("Height"), QString::number(raw->imgdata.sizes.height)});
info.info.append({QObject::tr("ISO"), QString::number(raw->imgdata.other.iso_speed)});
info.info.append({QObject::tr("Shutter speed"), shutterSpeed});
#if LIBRAW_MINOR_VERSION>=19
@@ -191,11 +186,8 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
return status;
}
bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
{
if(!image)
return false;
fitsfile *file;
int status = 0;
int type;
@@ -212,28 +204,35 @@ bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
if(naxis >= 2 && naxis <= 3 && status == 0)
{
int cvtype;
RawImage::DataType type;
int fitstype;
std::vector<cv::Mat> cvimg;
long fpixel[3] = {1,1,1};
switch(imgtype)
{
case BYTE_IMG:
cvtype = CV_8U;
type = RawImage::UINT8;
fitstype = TBYTE;
break;
case SHORT_IMG:
cvtype = CV_16S;
type = RawImage::UINT16;
fitstype = TSHORT;
break;
case USHORT_IMG:
cvtype = CV_16U;
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG:
cvtype = CV_32F;
type = RawImage::FLOAT32;
fitstype = TFLOAT;
break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default:
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
goto noload;
@@ -247,26 +246,28 @@ bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
info.info.append({QObject::tr("Width"), QString::number(naxes[0])});
info.info.append({QObject::tr("Height"), QString::number(naxes[1])});
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
uint8_t *data = static_cast<uint8_t*>(img.data());
for (int i=1; i==1 || i<=naxes[2]; i++)
{
cv::Mat tmp(h, w, cvtype);
fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, tmp.ptr(), NULL, &status);
if(cvtype == CV_16S)
tmp.convertTo(tmp, CV_16U, 1, 32767);
cvimg.push_back(tmp);
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
}
if(fitstype == TSHORT)
{
uint16_t *s = static_cast<uint16_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
if(cvimg.size() == 1)
{
*image = new RawImage(cvimg[0]);
}
if(cvimg.size() == 3)
{
cv::Mat rgb;
cv::merge(cvimg, rgb);
*image = new RawImage(rgb);
}
if(img.channels() == 1)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
if(image)
image->convertToGLFormat();
}
}
noload:
@@ -285,12 +286,12 @@ bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
return true;
}
bool loadXISF(const QString &path, ImageInfoData &info, RawImage **image)
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(path);
xisf.open(path.toLocal8Bit().data());
const LibXISF::Image &xisfImage = xisf.getImage(0);
@@ -310,51 +311,33 @@ bool loadXISF(const QString &path, ImageInfoData &info, RawImage **image)
info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())});
if(!info.wcs->valid())info.wcs.reset();
if(xisfImage.channelCount() == 1)
RawImage::DataType type;
switch(xisfImage.sampleFormat())
{
switch(xisfImage.sampleFormat())
{
case LibXISF::Image::UInt8:
*image = new RawImage(xisfImage.width(), xisfImage.height(), RawImage::UINT8);
std::memcpy((*image)->data(), xisfImage.imageData(), xisfImage.imageDataSize());
break;
case LibXISF::Image::UInt16:
*image = new RawImage(xisfImage.width(), xisfImage.height(), RawImage::UINT16);
std::memcpy((*image)->data(), xisfImage.imageData(), xisfImage.imageDataSize());
break;
case LibXISF::Image::Float32:
*image = new RawImage(xisfImage.width(), xisfImage.height(), RawImage::FLOAT32);
std::memcpy((*image)->data(), xisfImage.imageData(), xisfImage.imageDataSize());
break;
default:
break;
}
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
default: break;
}
else if(xisfImage.channelCount() == 3)
{
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Normal);
switch(tmpImage.sampleFormat())
{
case LibXISF::Image::UInt8:
*image = new RawImage(tmpImage.width(), tmpImage.height(), RawImage::UINT8C3);
std::memcpy((*image)->data(), tmpImage.imageData(), tmpImage.imageDataSize());
break;
case LibXISF::Image::UInt16:
*image = new RawImage(tmpImage.width(), tmpImage.height(), RawImage::UINT16C3);
std::memcpy((*image)->data(), tmpImage.imageData(), tmpImage.imageDataSize());
break;
case LibXISF::Image::Float32:
*image = new RawImage(tmpImage.width(), tmpImage.height(), RawImage::FLOAT32C3);
std::memcpy((*image)->data(), tmpImage.imageData(), tmpImage.imageDataSize());
break;
default:
break;
}
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
}
if(*image)
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
}
if(image)
{
image->convertToGLFormat();
return true;
}
}
catch (LibXISF::Error &err)
{
@@ -370,7 +353,6 @@ void LoadRunable::run()
{
try
{
if(!m_thumbnail && !m_receiver->isCurrent())
{
return;
@@ -380,23 +362,21 @@ void LoadRunable::run()
QFileInfo finfo(m_file);
info.info.append({QObject::tr("Filename"), finfo.fileName()});
RawImage *rawImage = nullptr;
bool raw = false;
std::shared_ptr<RawImage> rawImage;
timer.start();
if(m_file.endsWith(".CR2", Qt::CaseInsensitive) || m_file.endsWith(".NEF", Qt::CaseInsensitive) || m_file.endsWith(".DNG", Qt::CaseInsensitive))
{
loadRAW(m_file, info, &rawImage);
raw = true;
loadRAW(m_file, info, rawImage);
qDebug() << "LoadRAW" << timer.elapsed();
}
else if(m_file.endsWith(".FIT", Qt::CaseInsensitive) || m_file.endsWith(".FITS", Qt::CaseInsensitive))
{
loadFITS(m_file, info, &rawImage);
loadFITS(m_file, info, rawImage);
qDebug() << "LoadFITS" << timer.elapsed();
}
else if(m_file.endsWith(".XISF", Qt::CaseInsensitive))
{
loadXISF(m_file, info, &rawImage);
loadXISF(m_file, info, rawImage);
qDebug() << "LoadXISF" << timer.elapsed();
}
else
@@ -416,34 +396,43 @@ void LoadRunable::run()
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
exif_data_free(exif);
}
rawImage = new RawImage(img);
rawImage = std::make_shared<RawImage>(img);
qDebug() << "LoadQImage" << timer.elapsed();
}
if(rawImage && m_analyzeLevel >= Statistics && !m_thumbnail)
if(rawImage /*&& m_analyzeLevel >= Statistics*/ && !m_thumbnail)
{
double mean, median, min, max, mad;
double stdDev;
uint32_t saturated;
timer.start();
rawImage->imageStats(&mean, &stdDev, &median, &min, &max, &mad, &saturated);
rawImage->calcStats();
const RawImage::Stats &stats = rawImage->imageStats();
qDebug() << "image stats" << timer.restart();
info.info.append({QObject::tr("Mean"), QString::number(mean)});
info.info.append({QObject::tr("Standart deviation"), QString::number(stdDev)});
info.info.append({QObject::tr("Median"), QString::number(median)});
info.info.append({QObject::tr("Minimum"), QString::number(min)});
info.info.append({QObject::tr("Maximum"), QString::number(max)});
info.info.append({QObject::tr("MAD"), QString::number(mad)});
info.info.append({QObject::tr("Saturated"), QString::number(100.0 * saturated / rawImage->size()) + "%"});
if(rawImage->channels() == 1)
{
info.info.append({QObject::tr("Mean"), QString::number(stats.m_mean[0])});
info.info.append({QObject::tr("Standart deviation"), QString::number(stats.m_stdDev[0])});
info.info.append({QObject::tr("Median"), QString::number(stats.m_median[0])});
info.info.append({QObject::tr("Minimum"), QString::number(stats.m_min[0])});
info.info.append({QObject::tr("Maximum"), QString::number(stats.m_max[0])});
info.info.append({QObject::tr("MAD"), QString::number(stats.m_mad[0])});
info.info.append({QObject::tr("Saturated"), QString::number(100.0 * stats.m_saturated[0] / rawImage->size()) + "%"});
}
else
{
info.info.append({QObject::tr("Mean"), QString("%1 %2 %3").arg(stats.m_mean[0]).arg(stats.m_mean[1]).arg(stats.m_mean[2])});
info.info.append({QObject::tr("Standart deviation"), QString("%1 %2 %3").arg(stats.m_stdDev[0]).arg(stats.m_stdDev[1]).arg(stats.m_stdDev[2])});
info.info.append({QObject::tr("Median"), QString("%1 %2 %3").arg(stats.m_median[0]).arg(stats.m_median[1]).arg(stats.m_median[2])});
info.info.append({QObject::tr("Minimum"), QString("%1 %2 %3").arg(stats.m_min[0]).arg(stats.m_min[1]).arg(stats.m_min[2])});
info.info.append({QObject::tr("Maximum"), QString("%1 %2 %3").arg(stats.m_max[0]).arg(stats.m_max[1]).arg(stats.m_max[2])});
info.info.append({QObject::tr("MAD"), QString("%1 %2 %3").arg(stats.m_mad[0]).arg(stats.m_mad[1]).arg(stats.m_mad[2])});
info.info.append({QObject::tr("Saturated"), QString("%1 %2 %3%").arg(100.0 * stats.m_saturated[0] / rawImage->size())
.arg(100.0 * stats.m_saturated[1] / rawImage->size())
.arg(100.0 * stats.m_saturated[2] / rawImage->size())});
}
if(m_analyzeLevel >= Peaks)
{
std::vector<Peak> peaks;
if(raw) {
rawImage->quarter();
qDebug() << "quarter" << timer.restart();
}
RawImage *medianImage = rawImage->medianFilter();
/*RawImage *medianImage = rawImage->medianFilter();
qDebug() << "median" << timer.restart();
int numPeaks = medianImage->findPeaks(median+stdDev*2, 20, peaks);
delete medianImage;
@@ -483,7 +472,7 @@ void LoadRunable::run()
info.info.append({QObject::tr("FWHM X"), QString::number(fwhmX/stars.size())});
info.info.append({QObject::tr("FWHM Y"), QString::number(fwhmY/stars.size())});
}
qDebug() << "Star fit" << timer.restart();
qDebug() << "Star fit" << timer.restart();*/
}
}
@@ -492,15 +481,11 @@ void LoadRunable::run()
if(rawImage)
{
rawImage->convertToThumbnail();
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(void*, rawImage));
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
}
}
else
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(void*, rawImage), Q_ARG(ImageInfoData, info));
}
catch(cv::Exception e)
{
qDebug() << e.what();
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, info));
}
catch(std::exception e)
{
@@ -527,7 +512,7 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
try
{
LibXISF::XISFReader xisf;
xisf.open(path);
xisf.open(path.toLocal8Bit().data());
const LibXISF::Image &image = xisf.getImage(0, false);
auto fitskeywords = image.fitsKeywords();
@@ -559,47 +544,47 @@ ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QStr
{
}
void writeFITSImage(fitsfile *fw, RawImage *rawimage, ImageInfoData &imageinfo)
void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoData &imageinfo)
{
static QStringList skipKeys = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "BZERO", "BSCALE", "EXTEND"};
int status = 0;
long firstpix[3] = {1,1,1};
int channels = rawimage->mat().channels();
int channels = rawimage->channels();
int naxis = channels == 1 ? 2 : 3;
long naxes[3] = {(int)rawimage->width(), (int)rawimage->height(), rawimage->mat().channels()};
long naxes[3] = {(int)rawimage->width(), (int)rawimage->height(), rawimage->channels()};
std::vector<cv::Mat> mat;
std::vector<RawImage> planes;
if(channels == 1)
mat.push_back(rawimage->mat());
planes.push_back(*rawimage);
else
cv::split(rawimage->mat(), mat);
planes = rawimage->split();
switch(CV_MAT_DEPTH(rawimage->dataType()))
switch(rawimage->type())
{
case CV_8U:
case RawImage::UINT8:
fits_create_img(fw, BYTE_IMG, naxis, naxes, &status);
for(int i=0; i<channels; i++)
{
firstpix[2] = i+1;
fits_write_pix(fw, TBYTE, firstpix, rawimage->size(), mat[i].data, &status);
fits_write_pix(fw, TBYTE, firstpix, rawimage->size(), planes[i].data(), &status);
}
break;
case CV_16U:
case RawImage::UINT16:
fits_create_img(fw, USHORT_IMG, naxis, naxes, &status);
for(int i=0; i<channels; i++)
{
firstpix[2] = i+1;
fits_write_pix(fw, TUSHORT, firstpix, rawimage->size(), mat[i].data, &status);
fits_write_pix(fw, TUSHORT, firstpix, rawimage->size(), planes[i].data(), &status);
}
break;
case CV_32F:
case RawImage::FLOAT32:
fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status);
for(int i=0; i<channels; i++)
{
firstpix[2] = i+1;
fits_write_pix(fw, TFLOAT, firstpix, rawimage->size(), mat[i].data, &status);
fits_write_pix(fw, TFLOAT, firstpix, rawimage->size(), planes[i].data(), &status);
}
break;
}
@@ -632,11 +617,11 @@ void writeFITSImage(fitsfile *fw, RawImage *rawimage, ImageInfoData &imageinfo)
void ConvertRunable::run()
{
ImageInfoData imageinfo;
RawImage *rawimage = nullptr;
std::shared_ptr<RawImage> rawimage;
if(m_infile.endsWith(".FITS", Qt::CaseInsensitive) || m_infile.endsWith(".FIT", Qt::CaseInsensitive))
loadFITS(m_infile, imageinfo, &rawimage);
loadFITS(m_infile, imageinfo, rawimage);
if(m_infile.endsWith(".XISF", Qt::CaseInsensitive))
loadXISF(m_infile, imageinfo, &rawimage);
loadXISF(m_infile, imageinfo, rawimage);
if(rawimage)
{
@@ -645,13 +630,13 @@ void ConvertRunable::run()
try
{
LibXISF::XISFWriter xisf;
int channelCount = rawimage->mat().channels();
int channelCount = rawimage->channels();
LibXISF::Image::SampleFormat sampleFormat;
switch(CV_MAT_DEPTH(rawimage->dataType()))
switch(rawimage->type())
{
case CV_8U: sampleFormat = LibXISF::Image::UInt8; break;
case CV_16U: sampleFormat = LibXISF::Image::UInt16; break;
case CV_32F: sampleFormat = LibXISF::Image::Float32; break;
case RawImage::UINT8: sampleFormat = LibXISF::Image::UInt8; break;
case RawImage::UINT16: sampleFormat = LibXISF::Image::UInt16; break;
case RawImage::FLOAT32: sampleFormat = LibXISF::Image::Float32; break;
default: return;
}
@@ -660,18 +645,17 @@ void ConvertRunable::run()
for(auto &record : imageinfo.fitsHeader)
{
if(record.value.type() == QVariant::Bool)
image.addFITSKeyword({record.key, record.value.toBool() ? "T" : "F", record.comment});
image.addFITSKeyword({record.key.toStdString(), record.value.toBool() ? "T" : "F", record.comment.toStdString()});
else
image.addFITSKeyword({record.key, record.value.toString(), record.comment});
image.addFITSKeyword({record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
}
xisf.writeImage(image);
xisf.save(m_outfile);
xisf.save(m_outfile.toLocal8Bit().data());
}
catch(LibXISF::Error &err)
{
qDebug() << "Failed to save XISF image" << err.what();
delete rawimage;
}
}
@@ -684,6 +668,5 @@ void ConvertRunable::run()
writeFITSImage(fw, rawimage, imageinfo);
fits_close_file(fw, &status);
}
delete rawimage;
}
}
+16 -4
View File
@@ -23,6 +23,7 @@
#include "about.h"
#include "statusbar.h"
#include "settingsdialog.h"
#include "histogram.h"
#ifdef __linux__
#include <sys/ioctl.h>
@@ -37,7 +38,7 @@ int MainWindow::socketPair[2] = {0, 0};
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
qRegisterMetaType<ImageInfoData>("ImageInfoData");
qRegisterMetaType<RawImage*>("RawImage");
qRegisterMetaType<std::shared_ptr<RawImage>>("std::shared_ptr<RawImage>");
SettingsDialog::loadSettings();
@@ -80,14 +81,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_imageGL->imageWidget(), &ImageWidget::status, statusBar, &StatusBar::newStatus);
m_stretchPanel = new StretchToolbar(this);
connect(m_stretchPanel, SIGNAL(paramChanged(float,float,float)), m_imageGL->imageWidget(), SLOT(setMTFParams(float,float,float)));
connect(m_stretchPanel, &StretchToolbar::paramChanged, m_imageGL->imageWidget(), &ImageWidget::setMTFParams);
connect(m_stretchPanel, &StretchToolbar::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); });
connect(m_stretchPanel, &StretchToolbar::invert, m_imageGL->imageWidget(), &ImageWidget::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_imageGL->imageWidget(), &ImageWidget::superPixel);
connect(m_stretchPanel, &StretchToolbar::falseColor, m_imageGL->imageWidget(), &ImageWidget::falseColor);
m_ringList = new ImageRingList(m_database, nameFilter, this);
m_filesystem = new FilesystemWidget(m_ringList, this);
connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int)));
connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort);
connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort);
m_filetree = new Filetree(this);
connect(m_filetree, &Filetree::fileSelected, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
@@ -117,6 +121,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
databaseViewDock->hide();
addDockWidget(Qt::LeftDockWidgetArea, filetreeDock);
Histogram *histogram = new Histogram(this);
QDockWidget *histogramDock = new QDockWidget(tr("Histogram"), this);
histogramDock->setWidget(histogram);
histogramDock->setObjectName("histogramDock");
histogramDock->hide();
addDockWidget(Qt::LeftDockWidgetArea, histogramDock);
setWindowTitle(tr("Tenmon"));
connect(m_ringList, SIGNAL(pixmapLoaded(Image*)), this, SLOT(pixmapLoaded(Image*)));
@@ -125,6 +136,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_ringList, SIGNAL(currentImageChanged(int)), m_filesystem, SLOT(selectFile(int)));
connect(m_ringList, &ImageRingList::thumbnailLoaded, m_imageGL->imageWidget(), &ImageWidget::thumbnailLoaded);
connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded);
connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded);
connect(m_imageGL->imageWidget(), &ImageWidget::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
QMenu *fileMenu = new QMenu(tr("File"), this);
@@ -158,7 +170,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
m_imageGL->imageWidget()->allocateThumbnails(m_ringList->imageNames());
m_imageGL->imageWidget()->showThumbnail(checked);
m_imageGL->setThumbnails(checked ? m_ringList->imageCount() : 0);
if(checked)m_ringList->loadThumbnails();
else m_ringList->stopLoading();
}, Qt::Key_F2);
@@ -197,7 +208,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(peakAction, SIGNAL(toggled(bool)), this, SLOT(peakFinder(bool)));
connect(starAction, SIGNAL(toggled(bool)), this, SLOT(starFinder(bool)));
analyzeMenu->addActions({statsAction, peakAction, starAction});
menuBar()->addMenu(analyzeMenu);
//menuBar()->addMenu(analyzeMenu);
QMenu *dockMenu = new QMenu(tr("Docks"), this);
dockMenu->addAction(infoDock->toggleViewAction());
@@ -205,6 +216,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
dockMenu->addAction(filesystemDock->toggleViewAction());
dockMenu->addAction(databaseViewDock->toggleViewAction());
dockMenu->addAction(filetreeDock->toggleViewAction());
dockMenu->addAction(histogramDock->toggleViewAction());
menuBar()->addMenu(dockMenu);
QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
+498 -245
View File
@@ -1,211 +1,276 @@
#include "rawimage.h"
#include <QDebug>
#include <cstring>
#include <QElapsedTimer>
int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138;
int THUMB_SIZE_BORDER_Y = 158;
double SATURATION = 0.95;
RawImage::ImgType CV2Type(int cvtype)
template<typename T, int ch>
void fromPlanarSSE(const void *in, void *out, size_t count);
size_t RawImage::typeSize(RawImage::DataType type)
{
switch (cvtype)
switch(type)
{
case CV_8U:
return RawImage::UINT8;
case CV_16U:
return RawImage::UINT16;
case CV_32F:
return RawImage::FLOAT32;
case CV_8UC3:
return RawImage::UINT8C3;
case CV_8UC4:
return RawImage::UINT8C4;
case CV_16UC3:
return RawImage::UINT16C3;
case CV_16UC4:
return RawImage::UINT16C4;
case CV_32FC3:
return RawImage::FLOAT32C3;
default:
return RawImage::UNKNOWN;
case RawImage::UINT8:
return 1;
case RawImage::UINT16:
return 2;
case RawImage::UINT32:
case RawImage::FLOAT32:
return 4;
case RawImage::FLOAT64:
return 8;
default: return 1;
}
}
int Type2CV(RawImage::ImgType type)
void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
{
switch (type)
{
case RawImage::UINT8:
return CV_8U;
case RawImage::UINT16:
return CV_16U;
case RawImage::FLOAT32:
return CV_32F;
case RawImage::UINT8C3:
return CV_8UC3;
case RawImage::UINT8C4:
return CV_8UC4;
case RawImage::UINT16C3:
return CV_16UC3;
case RawImage::UINT16C4:
return CV_16UC4;
case RawImage::FLOAT32C3:
return CV_32FC3;
case RawImage::UNKNOWN:
return CV_8S;
default:
return CV_8U;
}
m_width = w;
m_height = h;
m_channels = ch;
m_ch = ch == 3 ? 4 : ch;
m_origType = m_type = type;
m_pixels = std::make_unique<PixelType[]>(m_width * m_height * m_ch * typeSize(type));
}
RawImage::RawImage()
{
m_stats = false;
}
RawImage::RawImage(int w, int h, ImgType type)
RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type)
{
m_img.create(h, w, Type2CV(type));
m_stats = false;
}
RawImage::RawImage(cv::Mat &img)
{
m_img = img;
m_stats = false;
scaleToUnit();
allocate(w, h, ch, type);
}
RawImage::RawImage(const RawImage &d)
{
d.m_img.copyTo(m_img);
m_mean = d.m_mean;
m_stdDev = d.m_stdDev;
m_median = d.m_median;
m_min = d.m_min;
m_max = d.m_max;
m_mad = d.m_mad;
allocate(d.m_width, d.m_height, d.m_channels, d.m_type);
std::memcpy(m_pixels.get(), d.m_pixels.get(), m_width * m_height * m_ch * typeSize(m_type));
m_stats = d.m_stats;
m_saturated = d.m_saturated;
}
RawImage::RawImage(RawImage &&d)
{
m_pixels = std::move(d.m_pixels);
m_original = std::move(d.m_original);
m_width = d.m_width;
m_height = d.m_height;
m_channels = d.m_channels;
m_ch = d.m_ch;
m_type = d.m_type;
m_origType = d.m_origType;
m_stats = d.m_stats;
m_thumbAspect = d.m_thumbAspect;
}
RawImage::RawImage(const QImage &img)
{
if(img.format() == QImage::Format_RGB32)
qDebug() << img;
if(img.format() == QImage::Format_RGBX8888)
{
m_img.create(img.height(), img.width(), CV_8UC4);
allocate(img.width(), img.height(), 3, UINT8);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*4);
cv::cvtColor(m_img, m_img, cv::COLOR_BGRA2RGB);
std::memcpy(data(i), img.scanLine(i), img.width()*4);
}
else if(img.format() == QImage::Format_ARGB32)
else if(img.format() == QImage::Format_RGBA8888)
{
m_img.create(img.height(), img.width(), CV_8UC4);
allocate(img.width(), img.height(), 4, UINT8);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*4);
cv::cvtColor(m_img, m_img, cv::COLOR_BGRA2RGBA);
std::memcpy(data(i), img.scanLine(i), img.width()*4);
}
else if(img.format() == QImage::Format_RGBX64)
{
m_img.create(img.height(), img.width(), CV_16UC4);
allocate(img.width(), img.height(), 3, UINT16);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*8);
cv::cvtColor(m_img, m_img, cv::COLOR_RGBA2RGB);
std::memcpy(data(i), img.scanLine(i), img.width()*8);
}
else if(img.format() == QImage::Format_RGBA64)
{
m_img.create(img.height(), img.width(), CV_16UC4);
allocate(img.width(), img.height(), 4, UINT16);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*8);
std::memcpy(data(i), img.scanLine(i), img.width()*8);
}
else if(img.format() == QImage::Format_Grayscale8)
{
allocate(img.width(), img.height(), 1, UINT8);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width());
}
else if(img.format() == QImage::Format_Grayscale16)
{
allocate(img.width(), img.height(), 1, UINT16);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*2);
}
else
{
QImage tmp = img.convertToFormat(QImage::Format_RGB888);
m_img.create(img.height(), img.width(), CV_8UC3);
QImage tmp = img.convertToFormat(QImage::Format_RGBA8888);
allocate(img.width(), img.height(), 4, UINT8);
for(int i=0; i<tmp.height(); i++)
std::memcpy(m_img.ptr(i), tmp.scanLine(i), tmp.width()*3);
std::memcpy(data(i), tmp.scanLine(i), tmp.width()*4);
}
m_stats = false;
m_stats.m_stats = false;
}
bool RawImage::imageStats(double *mean, double *stdDev, double *median, double *min, double *max, double *mad, uint32_t *saturated)
const RawImage::Stats& RawImage::imageStats()
{
if(!m_stats)calcStats();
if(mean)*mean = m_mean;
if(stdDev)*stdDev = m_stdDev;
if(median)*median = m_median;
if(min)*min = m_min;
if(max)*max = m_max;
if(mad)*mad = m_mad;
if(saturated)*saturated = m_saturated;
return m_stats;
}
return true;
template<typename T, typename U, int ch>
void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
{
U sum[4] = {0};
U sumSq[4] = {0};
T min[4] = {std::numeric_limits<T>::max(), std::numeric_limits<T>::max(), std::numeric_limits<T>::max(), std::numeric_limits<T>::max()};
T max[4] = {std::numeric_limits<T>::min(), std::numeric_limits<T>::min(), std::numeric_limits<T>::min(), std::numeric_limits<T>::min()};
uint32_t histSize = 65536;
if constexpr(std::is_same<T, uint8_t>::value)histSize = 256;
std::vector<uint32_t> histogram[4];
histogram[0].resize(histSize); histogram[1].resize(histSize); histogram[2].resize(histSize); histogram[3].resize(histSize);
T sat = SATURATION * std::numeric_limits<T>::max();
if constexpr(!std::numeric_limits<T>::is_integer)sat = SATURATION;
uint32_t saturated[4] = {0};
auto statsFunc = [&](T d, int x)
{
sum[x] += d;
sumSq[x] += (U)d * d;
min[x] = std::min(min[x], d);
max[x] = std::max(max[x], d);
uint16_t idx;
if constexpr(std::is_same<T, uint32_t>::value)idx = d >> 16;
if constexpr(std::is_same<T, uint8_t>::value || std::is_same<T, uint16_t>::value)idx = d;
if constexpr(!std::numeric_limits<T>::is_integer)idx = std::clamp((T)d * histSize, (T)0.0, (T)65535.0);
histogram[x][idx]++;
if(d > sat)saturated[x]++;
};
auto findMedian = [histSize](std::vector<uint32_t> &histogram, size_t n) -> size_t
{
size_t histSum = 0;
for(size_t o=0; o < histSize; o++)
{
histSum += histogram[o];
if(histSum >= n/2)
return o;
}
return 0;
};
size_t na[4] = {n, n, n, n};
if constexpr(ch == 1)
{
na[1] /= 4;
na[2] /= 2;
na[3] /= 4;
}
for(size_t i = 0; i < n; i++)
{
statsFunc(data[i*ch], 0);
if constexpr(ch >= 3)
{
statsFunc(data[i*ch + 1], 1);
statsFunc(data[i*ch + 2], 2);
}
}
if constexpr(ch == 1)
{
size_t h = (n / w) & (SIZE_MAX-1);
w &= (SIZE_MAX-1);
for(size_t y=0; y<h; y+=2)
{
for(size_t x=0; x<w; x+=2)
{
statsFunc(data[y*w+x], 1);
statsFunc(data[y*w+x+1], 2);
statsFunc(data[(y+1)*w+x], 2);
statsFunc(data[(y+1)*w+x+1], 3);
}
}
}
for(int i = 0; i < 4; i++)
{
stats.m_min[i] = min[i];
stats.m_max[i] = max[i];
stats.m_mean[i] = (double)sum[i] / na[i];
stats.m_saturated[i] = saturated[i];
double sum2 = (double)sum[i] * sum[i];
stats.m_stdDev[i] = std::sqrt((sumSq[i] - sum2 / na[i]) / (na[i] - 1));
uint32_t median = findMedian(histogram[i], na[i]);
stats.m_median[i] = median;
std::vector<uint32_t> madHist(histSize, 0);
madHist[0] = histogram[i][median];
for(size_t o = 1; o < histSize; o++)
{
if(median + o < histSize)madHist[o] += histogram[i][median + o];
if(o <= median)madHist[o] += histogram[i][median - o];
}
stats.m_mad[i] = findMedian(madHist, na[i]);
if constexpr(!std::numeric_limits<T>::is_integer)
{
stats.m_median[i] /= 65535.0;
stats.m_mad[i] /= 65535.0;
}
}
stats.m_histogram.resize(histSize, 0);
for(size_t i = 0; i < histSize; i++)
for(size_t o = 0; o < ch; o++)
stats.m_histogram[i] += histogram[o][i];
}
void RawImage::calcStats()
{
if(m_stats)return;
m_stats = true;
if(m_stats.m_stats)return;
m_stats.m_stats = true;
cv::Scalar meanS, stdDevS;
cv::meanStdDev(m_img, meanS, stdDevS);
cv::minMaxIdx(m_img, &m_min, &m_max);
cv::Mat img;
if(m_img.channels() == 1)img = m_img;
else if (m_img.channels() == 3)cv::cvtColor(m_img, img, cv::COLOR_BGR2GRAY);
else if (m_img.channels() == 4)cv::cvtColor(m_img, img, cv::COLOR_BGRA2GRAY);
int histSize = 256;
if(img.type() == CV_16U || img.type() == CV_32F)histSize = 65536;
float range[] = {0, (float)histSize};
if(img.type() == CV_32F)range[1] = 1.0f;
const float *ranges[] = {range};
cv::Mat hist;
cv::calcHist(&img, 1, nullptr, cv::Mat(), hist, 1, &histSize, ranges);
m_mean = meanS[0];
m_stdDev = stdDevS[0];
size_t halfImageSize = size()/2;
size_t medianSum = 0;
for(int i=0; i < histSize; i++)
switch(m_origType)
{
medianSum += hist.at<float>(0, i);
if(medianSum >= halfImageSize)
{
m_median = i;
break;
}
case UINT8:
if(channels()==1)
::calcStats<uint8_t, uint64_t, 1>(static_cast<const uint8_t*>(origData()), size(), width(), m_stats);
else
::calcStats<uint8_t, uint64_t, 4>(static_cast<const uint8_t*>(origData()), size(), width(), m_stats);
break;
case UINT16:
if(channels()==1)
::calcStats<uint16_t, uint64_t, 1>(static_cast<const uint16_t*>(origData()), size(), width(), m_stats);
else
::calcStats<uint16_t, uint64_t, 4>(static_cast<const uint16_t*>(origData()), size(), width(), m_stats);
break;
case UINT32:
if(channels()==1)
::calcStats<uint32_t, double, 1>(static_cast<const uint32_t*>(origData()), size(), width(), m_stats);
else
::calcStats<uint32_t, double, 4>(static_cast<const uint32_t*>(origData()), size(), width(), m_stats);
break;
case FLOAT32:
if(channels()==1)
::calcStats<float, double, 1>(static_cast<const float*>(origData()), size(), width(), m_stats);
else
::calcStats<float, double, 4>(static_cast<const float*>(origData()), size(), width(), m_stats);
break;
case FLOAT64:
if(channels()==1)
::calcStats<double, double, 1>(static_cast<const double*>(origData()), size(), width(), m_stats);
else
::calcStats<double, double, 4>(static_cast<const double*>(origData()), size(), width(), m_stats);
break;
}
if(img.type() == CV_32F)m_median /= histSize;
int threshold = SATURATION * histSize;
m_saturated = 0;
for(int i = histSize-1; i >= threshold; i--)
m_saturated += hist.at<float>(0, i);
cv::Mat absDev;
img.convertTo(absDev, CV_32F, 1, -m_median);
absDev = cv::abs(absDev);
cv::Mat madHist;
medianSum = 0;
cv::calcHist(&absDev, 1, nullptr, cv::Mat(), madHist, 1, &histSize, ranges);
for(int i=0; i < histSize; i++)
{
medianSum += madHist.at<float>(0, i);
if(medianSum >= halfImageSize)
{
m_mad = i;
break;
}
}
if(img.type() == CV_32F)m_mad /= histSize;
}
void RawImage::rect(int &x, int &y, int w, int h, std::vector<double> &r) const
{
r.resize(w*h);
/*r.resize(w*h);
x -= w/2;
y -= h/2;
if(x<0)x = 0;
@@ -215,12 +280,12 @@ void RawImage::rect(int &x, int &y, int w, int h, std::vector<double> &r) const
cv::Mat roiImg(m_img, cv::Rect(x, y, w, h));
cv::Mat doubleMat;
roiImg.convertTo(doubleMat, CV_64F);
r = std::vector<double>(doubleMat.begin<double>(), doubleMat.end<double>());
r = std::vector<double>(doubleMat.begin<double>(), doubleMat.end<double>());*/
}
int RawImage::findPeaks(double background, double distance, std::vector<Peak> &peaks) const
{
std::vector<std::vector<cv::Point>> contours;
/*std::vector<std::vector<cv::Point>> contours;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(distance, distance));
@@ -240,29 +305,22 @@ int RawImage::findPeaks(double background, double distance, std::vector<Peak> &p
peaks.push_back(Peak(1, contour[0].x, contour[0].y));
}
return peaks.size();
}
RawImage* RawImage::medianFilter() const
{
RawImage *ret = new RawImage();
cv::medianBlur(m_img, ret->m_img, 3);
return ret;
}
void RawImage::quarter()
{
return peaks.size();*/
}
uint32_t RawImage::width() const
{
return m_img.cols;
return m_width;
}
uint32_t RawImage::height() const
{
return m_img.rows;
return m_height;
}
uint32_t RawImage::channels() const
{
return m_channels;
}
uint32_t RawImage::size() const
@@ -270,27 +328,21 @@ uint32_t RawImage::size() const
return width()*height();
}
RawImage::ImgType RawImage::type() const
RawImage::DataType RawImage::type() const
{
return CV2Type(m_img.type());
}
int RawImage::dataType() const
{
return m_img.type();
return m_type;
}
uint32_t RawImage::norm() const
{
switch(m_img.type())
switch(m_type)
{
case CV_8U:
case CV_8UC3:
case CV_8UC4:
case UINT8:
return UINT8_MAX;
case CV_16U:
case CV_16UC3:
case UINT16:
return UINT16_MAX;
case UINT32:
return UINT32_MAX;
default:
return 1;
}
@@ -298,37 +350,121 @@ uint32_t RawImage::norm() const
void* RawImage::data()
{
return m_img.ptr();
return m_pixels.get();
}
const void *RawImage::data() const
{
return m_img.ptr();
return m_pixels.get();
}
void *RawImage::data(uint32_t row, uint32_t col)
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
}
const void *RawImage::data(uint32_t row, uint32_t col) const
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
}
const void *RawImage::origData() const
{
if(m_original)
return m_original.get();
else
return m_pixels.get();
}
const void *RawImage::origData(uint32_t row, uint32_t col) const
{
if(m_original)
return m_original.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_origType);
else
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
}
void RawImage::convertToThumbnail()
{
m_thumbAspect = (float)width() / height();
switch(CV_MAT_DEPTH(m_img.type()))
std::unique_ptr<PixelType[]> outptr = std::make_unique<PixelType[]>(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(uint16_t));
uint16_t *out = reinterpret_cast<uint16_t*>(outptr.get());
auto loop = [&](uint16_t *out, auto *in, auto scale)
{
case CV_8U:
m_img.convertTo(m_img, CV_16U, 255);
for(int i=0; i<THUMB_SIZE; i++)
{
for(int o=0; o<THUMB_SIZE; o++)
{
int idx = (i*THUMB_SIZE + o)*4;
int idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
if(m_channels == 1)
{
out[idx] = out[idx + 1] = out[idx + 2] = in[idx2] * scale;
}
else
{
out[idx] = in[idx2] * scale;;
out[idx + 1] = in[idx2 + 1] * scale;;
out[idx + 2] = in[idx2 + 2] * scale;;
}
out[idx + 3] = UINT16_MAX;
}
}
};
switch(m_type)
{
case UINT8:
loop(out, reinterpret_cast<uint8_t*>(m_pixels.get()), 256);
break;
case CV_32F:
m_img.convertTo(m_img, CV_16U, 65535);
case UINT16:
loop(out, reinterpret_cast<uint16_t*>(m_pixels.get()), 1);
break;
case CV_16U:
case UINT32:
loop(out, reinterpret_cast<uint32_t*>(m_pixels.get()), UINT16_MAX/(float)UINT32_MAX);
break;
case FLOAT32:
loop(out, reinterpret_cast<float*>(m_pixels.get()), 65535.0);
break;
default:
break;
qWarning() << "FLOAT64 should not happend";
return;
}
if(m_img.channels() == 1)
cv::cvtColor(m_img, m_img, cv::COLOR_GRAY2RGB);
if(m_img.channels() == 4)
cv::cvtColor(m_img, m_img, cv::COLOR_RGBA2RGB);
cv::Size dsize(THUMB_SIZE, THUMB_SIZE);
cv::resize(m_img, m_img, dsize, 0, 0, cv::INTER_NEAREST);
m_pixels = std::move(outptr);
m_width = THUMB_SIZE;
m_height = THUMB_SIZE;
m_ch = 4;
m_channels = 3;
m_type = UINT16;
}
void RawImage::convertToGLFormat()
{
size_t s = size() * m_ch;
if(m_type == UINT32)
{
m_original = std::move(m_pixels);
allocate(m_width, m_height, m_channels, FLOAT32);
m_origType = UINT32;
float *dst = reinterpret_cast<float*>(m_pixels.get());
uint32_t *src = reinterpret_cast<uint32_t*>(m_original.get());
for(size_t i = 0; i < s; i++)
dst[i] = src[i] / (float)UINT32_MAX;
}
else if(m_type == FLOAT64)
{
m_original = std::move(m_pixels);
allocate(m_width, m_height, m_channels, FLOAT32);
m_origType = FLOAT64;
float *dst = reinterpret_cast<float*>(m_pixels.get());
double *src = reinterpret_cast<double*>(m_original.get());
for(size_t i = 0; i < s; i++)
dst[i] = src[i];
}
}
float RawImage::thumbAspect() const
@@ -336,87 +472,204 @@ float RawImage::thumbAspect() const
return m_thumbAspect;
}
const cv::Mat& RawImage::mat() const
{
return m_img;
}
bool RawImage::pixel(int x, int y, QVector3D &rgb) const
bool RawImage::pixel(int x, int y, double &r, double &g, double &b) const
{
if(x < 0 || y < 0 || x >= (int)width() || y >= (int)height())return false;
switch(m_img.type())
switch(m_origType)
{
case CV_8U:
case UINT8:
{
uint8_t v = m_img.at<uint8_t>(y, x);
rgb = QVector3D(v, v, v);
const uint8_t *v = static_cast<const uint8_t*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case CV_16U:
case UINT16:
{
uint16_t v = m_img.at<uint16_t>(y, x);
rgb = QVector3D(v, v, v);
const uint16_t *v = static_cast<const uint16_t*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case CV_32F:
case UINT32:
{
float v = m_img.at<float>(y, x);
rgb = QVector3D(v, v, v);
const uint32_t *v = static_cast<const uint32_t*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case CV_8UC3:
case FLOAT32:
{
cv::Vec3b v = m_img.at<cv::Vec3b>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
const float *v = static_cast<const float*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case CV_8UC4:
case FLOAT64:
{
cv::Vec4b v = m_img.at<cv::Vec4b>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
const double *v = static_cast<const double*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case CV_16UC3:
{
cv::Vec3w v = m_img.at<cv::Vec3w>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
break;
}
case CV_32FC3:
{
cv::Vec3f v = m_img.at<cv::Vec3f>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
break;
}
default:
rgb = QVector3D(0, 0, 0);
break;
}
return true;
}
void RawImage::scaleToUnit()
{
if(CV_MAT_DEPTH(m_img.type()) == CV_32F)
{
double min, max;
cv::minMaxIdx(m_img, &min, &max);
if(min < 0 || max > 1)
{
float scale = 1.0 / (max - min);
float zero = min * scale;
m_img = m_img * scale - zero;
}
}
}
void RawImage::downscaleTo(uint32_t size)
{
if(size < width() || size < height())
/*if(size < width() || size < height())
{
double s = (double)size / std::max(width(), height());
cv::Size dsize(std::floor(width() * s), std::floor(height() * s));
cv::resize(m_img, m_img, dsize, 0, 0, cv::INTER_AREA);
}
}*/
}
std::shared_ptr<RawImage> RawImage::fromPlanar(const RawImage &img)
{
return RawImage::fromPlanar(img.data(), img.width(), img.height(), img.channels(), img.type());
}
std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, uint32_t h, uint32_t ch, RawImage::DataType type)
{
std::shared_ptr<RawImage> image = std::make_shared<RawImage>(w, h, ch, type);
size_t size = w * h;
size_t ch2 = ch == 1 ? 1 : 4;
auto convert = [&](auto *in, auto *out, auto alpha)
{
for(size_t i=0; i<size; i++)
for(size_t o=0; o<ch; o++)
out[i*ch2 + o] = in[o*size + i];
if(ch != ch2)
for(size_t i=0; i<size; i++)
out[i*ch2 + 3] = alpha;
};
switch(type)
{
case UINT8:
#ifdef __SSE2__
if(ch==3)
fromPlanarSSE<uint8_t, 3>(pixels, image->data(), size);
else
fromPlanarSSE<uint8_t, 4>(pixels, image->data(), size);
#else
convert(static_cast<const uint8_t*>(pixels), static_cast<uint8_t*>(image->data()), UINT8_MAX);
#endif
break;
case UINT16:
#ifdef __SSE2__
if(ch==3)
fromPlanarSSE<uint16_t, 3>(pixels, static_cast<uint16_t*>(image->data()), size);
else
fromPlanarSSE<uint16_t, 4>(pixels, static_cast<uint16_t*>(image->data()), size);
#else
convert(static_cast<const uint16_t*>(pixels), static_cast<uint16_t*>(image->data()), UINT16_MAX);
#endif
break;
case UINT32:
#ifdef __SSE2__
if(ch==3)
fromPlanarSSE<uint32_t, 3>(pixels, image->data(), size);
else
fromPlanarSSE<uint32_t, 4>(pixels, image->data(), size);
#else
convert(static_cast<const uint32_t*>(pixels), static_cast<uint32_t*>(image->data()), UINT32_MAX);
#endif
break;
case FLOAT32:
#ifdef __SSE2__
if(ch==3)
fromPlanarSSE<float, 3>(pixels, image->data(), size);
else
fromPlanarSSE<float, 4>(pixels, image->data(), size);
#else
convert(static_cast<const float*>(pixels), static_cast<float*>(image->data()), 1);
#endif
break;
case FLOAT64:
convert(static_cast<const double*>(pixels), static_cast<double*>(image->data()), 1);
break;
}
return image;
}
std::vector<RawImage> RawImage::split() const
{
std::vector<RawImage> planes;
planes.resize(m_channels);
for(size_t i=0; i<m_channels; i++)
planes[i].allocate(m_width, m_height, 1, m_type);
size_t s = size();
auto extract = [&](auto *in, auto *out, size_t off)
{
for(size_t i=0; i < s; i+=m_ch)
out[i] = in[i*m_ch + off];
};
for(uint32_t i=0; i<m_ch; i++)
{
switch(m_type)
{
case UINT8:
extract(static_cast<const uint8_t*>(data()), static_cast<uint8_t*>(planes[i].data()), i);
break;
case UINT16:
extract(static_cast<const uint16_t*>(data()), static_cast<uint16_t*>(planes[i].data()), i);
break;
case UINT32:
case FLOAT32:
extract(static_cast<const uint32_t*>(data()), static_cast<uint32_t*>(planes[i].data()), i);
break;
case FLOAT64:
extract(static_cast<const double*>(data()), static_cast<double*>(planes[i].data()), i);
break;
}
}
return planes;
}
+49 -30
View File
@@ -3,12 +3,11 @@
#include <vector>
#include <algorithm>
#include <memory>
#include <stdint.h>
#include <math.h>
#include <memory.h>
#include <opencv2/imgproc.hpp>
#include <QImage>
#include <QVector3D>
extern int THUMB_SIZE;
extern int THUMB_SIZE_BORDER;
@@ -38,55 +37,75 @@ public:
class RawImage
{
protected:
cv::Mat m_img;
bool m_stats;
double m_mean;
double m_stdDev;
double m_median;
double m_min;
double m_max;
double m_mad;
float m_thumbAspect;
uint32_t m_saturated;
using PixelType = uint8_t;
public:
enum ImgType
enum DataType
{
UINT8,
UINT16,
UINT32,
FLOAT32,
UINT8C3,
UINT8C4,
UINT16C3,
UINT16C4,
FLOAT32C3,
UNKNOWN,
FLOAT64,
};
struct Stats
{
bool m_stats = false;
double m_mean[4] = {0.0};
double m_stdDev[4] = {0.0};
double m_median[4] = {0.0};
double m_min[4] = {0.0};
double m_max[4] = {0.0};
double m_mad[4] = {0.0};
uint32_t m_saturated[4] = {0};
std::vector<uint32_t> m_histogram;
};
protected:
std::unique_ptr<PixelType[]> m_pixels;
std::unique_ptr<PixelType[]> m_original;
uint32_t m_width = 0;
uint32_t m_height = 0;
uint32_t m_channels = 0;
uint32_t m_ch = 0;
DataType m_type = UINT8;
DataType m_origType = UINT8;
float m_thumbAspect = 0.0;
Stats m_stats;
void allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type);
public:
RawImage();
RawImage(int w, int h, ImgType type);
RawImage(cv::Mat &img);
RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
RawImage(const RawImage &d);
RawImage(RawImage &&d);
RawImage(const QImage &img);
bool imageStats(double *mean, double *stdDev, double *median, double *min, double *max, double *mad, uint32_t *saturated);
const RawImage::Stats& imageStats();
void calcStats();
void rect(int &x, int &y, int w, int h, std::vector<double> &r) const;
int findPeaks(double background, double distance, std::vector<Peak> &peaks) const;
RawImage* medianFilter() const;
void quarter();
uint32_t width() const;
uint32_t height() const;
uint32_t channels() const;
uint32_t size() const;
ImgType type() const;
int dataType() const;
DataType type() const;
uint32_t norm() const;
void* data();
const void* data() const;
void* data(uint32_t row, uint32_t col = 0);
const void* data(uint32_t row, uint32_t col = 0) const;
const void *origData() const;
const void *origData(uint32_t row, uint32_t col = 0) const;
void convertToThumbnail();
void convertToGLFormat();
float thumbAspect() const;
const cv::Mat& mat() const;
bool pixel(int x, int y, QVector3D &rgb) const;
void scaleToUnit();
bool pixel(int x, int y, double &r, double &g, double &b) const;
void downscaleTo(uint32_t size);
static std::shared_ptr<RawImage> fromPlanar(const RawImage &img);
static std::shared_ptr<RawImage> fromPlanar(const void *pixels, uint32_t w, uint32_t h, uint32_t ch, DataType type);
static size_t typeSize(DataType type);
std::vector<RawImage> split() const;
};
//Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
//Q_DECLARE_METATYPE(std::shared_ptr<RawImage>);
#endif // RAWIMAGE_H
+111
View File
@@ -0,0 +1,111 @@
#include "rawimage.h"
#include <x86intrin.h>
template<typename T, int ch>
void fromPlanarSSE(const void *in, void *out, size_t count)
{
const __m128i *_in[4] = {(const __m128i*) static_cast<const T*>(in),
(const __m128i*)(static_cast<const T*>(in) + count),
(const __m128i*)(static_cast<const T*>(in) + count*2),
(const __m128i*)(static_cast<const T*>(in) + count*3)};
__m128i *_out = (__m128i*)out;
size_t s2 = count;
if constexpr(sizeof(T) == 1)
{
count /= 16;
__m128i a = _mm_set1_epi8(-1);
for(size_t i = 0; i < count; i++)
{
__m128i r = _mm_loadu_si128(_in[0] + i);
__m128i g = _mm_loadu_si128(_in[1] + i);
__m128i b = _mm_loadu_si128(_in[2] + i);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3]);
__m128i d1 = _mm_unpacklo_epi8(r, b);
__m128i d2 = _mm_unpacklo_epi8(g, a);
_mm_storeu_si128(_out + i*4, _mm_unpacklo_epi8(d1, d2));
_mm_storeu_si128(_out + i*4 + 1, _mm_unpackhi_epi8(d1, d2));
d1 = _mm_unpackhi_epi8(r, b);
d2 = _mm_unpackhi_epi8(g, a);
_mm_storeu_si128(_out + i*4 + 2, _mm_unpacklo_epi8(d1, d2));
_mm_storeu_si128(_out + i*4 + 3, _mm_unpackhi_epi8(d1, d2));
}
count *= 16;
}
if constexpr(sizeof(T) == 2)
{
count /= 8;
__m128i a = _mm_set1_epi16(-1);
for(size_t i = 0; i < count; i++)
{
__m128i r = _mm_loadu_si128(_in[0] + i);
__m128i g = _mm_loadu_si128(_in[1] + i);
__m128i b = _mm_loadu_si128(_in[2] + i);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3]);
__m128i d1 = _mm_unpacklo_epi16(r, b);
__m128i d2 = _mm_unpacklo_epi16(g, a);
_mm_storeu_si128(_out + i*4, _mm_unpacklo_epi16(d1, d2));
_mm_storeu_si128(_out + i*4 + 1, _mm_unpackhi_epi16(d1, d2));
d1 = _mm_unpackhi_epi16(r, b);
d2 = _mm_unpackhi_epi16(g, a);
_mm_storeu_si128(_out + i*4 + 2, _mm_unpacklo_epi16(d1, d2));
_mm_storeu_si128(_out + i*4 + 3, _mm_unpackhi_epi16(d1, d2));
}
count *= 8;
}
if constexpr(sizeof(T) == 4)
{
count /= 4;
__m128i a = _mm_set1_epi32(-1);
if constexpr(!std::numeric_limits<T>::is_integer)a = _mm_castps_si128(_mm_set1_ps(1.0));
for(size_t i = 0; i < count; i++)
{
__m128i r = _mm_loadu_si128(_in[0] + i);
__m128i g = _mm_loadu_si128(_in[1] + i);
__m128i b = _mm_loadu_si128(_in[2] + i);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3]);
__m128i d1 = _mm_unpacklo_epi32(r, b);
__m128i d2 = _mm_unpacklo_epi32(g, a);
_mm_storeu_si128(_out + i*4, _mm_unpacklo_epi32(d1, d2));
_mm_storeu_si128(_out + i*4 + 1, _mm_unpackhi_epi32(d1, d2));
d1 = _mm_unpackhi_epi32(r, b);
d2 = _mm_unpackhi_epi32(g, a);
_mm_storeu_si128(_out + i*4 + 2, _mm_unpacklo_epi32(d1, d2));
_mm_storeu_si128(_out + i*4 + 3, _mm_unpackhi_epi32(d1, d2));
}
count *= 4;
}
for(size_t i = count; i < s2; i++)
{
switch(sizeof(T))
{
case 1:
for(uint32_t o=0; o<ch; o++)static_cast<uint8_t*>(out)[i + o] = static_cast<const uint8_t*>(in)[i + o + s2];
if(ch==3)static_cast<uint8_t*>(out)[i*4 + 3] = 0xff;
break;
case 2:
for(uint32_t o=0; o<ch; o++)static_cast<uint16_t*>(out)[i + o] = static_cast<const uint16_t*>(in)[i + o + s2];
if(ch==3)static_cast<uint16_t*>(out)[i*4 + 3] = 0xffff;
break;
case 4:
for(uint32_t o=0; o<ch; o++)static_cast<uint32_t*>(out)[i + o] = static_cast<const uint32_t*>(in)[i + o + s2];
if(ch==3)
{
if(!std::numeric_limits<T>::is_integer)static_cast<float*>(out)[i*4 + 3] = 1.0;
else static_cast<uint32_t*>(out)[i*4 + 3] = 0xffffffff;
}
break;
}
}
}
template void fromPlanarSSE<uint8_t, 3>(const void *in, void *out, size_t count);
template void fromPlanarSSE<uint8_t, 4>(const void *in, void *out, size_t count);
template void fromPlanarSSE<uint16_t, 3>(const void *in, void *out, size_t count);
template void fromPlanarSSE<uint16_t, 4>(const void *in, void *out, size_t count);
template void fromPlanarSSE<uint32_t, 3>(const void *in, void *out, size_t count);
template void fromPlanarSSE<uint32_t, 4>(const void *in, void *out, size_t count);
template void fromPlanarSSE<float, 3>(const void *in, void *out, size_t count);
template void fromPlanarSSE<float, 4>(const void *in, void *out, size_t count);
+5 -3
View File
@@ -17,14 +17,16 @@
<file>shaders/thumb.vert</file>
<file>shaders/debayer.frag</file>
<file>shaders/debayer.vert</file>
<file>falsecolor.png</file>
<file>link.png</file>
</qresource>
<qresource lang="en" prefix="/">
<qresource prefix="/" lang="en">
<file alias="help">about/help_en</file>
</qresource>
<qresource lang="sk" prefix="/">
<qresource prefix="/" lang="sk">
<file alias="help">about/help_sk</file>
</qresource>
<qresource lang="fr" prefix="/">
<qresource prefix="/" lang="fr">
<file alias="help">about/help_fr</file>
</qresource>
</RCC>
+1 -1
View File
@@ -3,7 +3,7 @@
uniform sampler2D qt_Texture0;
in vec2 qt_TexCoord0;
in vec2 center;
out vec4 color;
layout(location = 0) out vec4 color;
#define f(x, y) texelFetch(qt_Texture0, icenter + ivec2(x, y), 0).r
+24 -9
View File
@@ -1,13 +1,14 @@
#version 330
uniform sampler2D qt_Texture0;
uniform vec3 mtf_param;
uniform vec3 mtf_param[3];
uniform vec2 unit_scale;
uniform bool bw;
uniform bool invert;
uniform bool srgb;
uniform vec3 whiteBalance;
uniform bool false_color;
in vec2 qt_TexCoord0;
out vec4 color;
layout(location = 0) out vec4 color;
vec3 Linear2sRGB(vec3 color)
{
@@ -16,11 +17,25 @@ vec3 Linear2sRGB(vec3 color)
greaterThan(color, vec3(0.0031308)));
}
vec4 MTF(vec4 x, vec3 m)
vec4 MTF(vec4 x, vec4 low, vec4 mid, vec4 high)
{
x = (x - m.x) / (m.z - m.x);
x = (x - low) / (high - low);
x = clamp(x, vec4(0.0), vec4(1.0));
return ((m.y - 1) * x) / ((2 * m.y - 1) * x - m.y);
return ((mid - 1) * x) / ((2 * mid - 1) * x - mid);
}
vec3 falsecolor(float color)
{
const vec3 pallete[] = vec3[](
vec3(1.0, 0.0, 1.0), //magneta
vec3(0.0, 0.0, 1.0), //blue
vec3(0.0, 1.0, 1.0), //cyan
vec3(0.0, 1.0, 0.0), //green
vec3(1.0, 1.0, 0.0), //yellow
vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0));//red
color *= 5.0;
int i = int(color);
return mix(pallete[i], pallete[i+1], fract(color));
}
vec3 checker()
@@ -32,8 +47,10 @@ vec3 checker()
void main(void)
{
color = texture(qt_Texture0, qt_TexCoord0);
color.rgb = color.rgb * unit_scale.x + unit_scale.y;
if(bw)color = color.rrra;
color = MTF(color, mtf_param);
color = MTF(color, vec4(mtf_param[0], 0.0), vec4(mtf_param[1], 0.5), vec4(mtf_param[2], 1.0));
if(false_color)color.rgb = falsecolor(color.r);
if(invert)color.rgb = vec3(1.0) - color.rgb;
@@ -41,8 +58,6 @@ void main(void)
if(srgb)color.rgb = Linear2sRGB(color.rgb);
color.rgb *= whiteBalance;
if(any(lessThan(qt_TexCoord0, vec2(0.0))) || any(greaterThan(qt_TexCoord0, vec2(1.0))))
color = vec4(0.0, 0.0, 0.0, 1.0);
+6 -6
View File
@@ -1,22 +1,22 @@
#version 330
uniform sampler2DArray qt_Texture0;
uniform vec3 mtf_param;
uniform vec3 mtf_param[3];
uniform bool invert;
in vec3 qt_TexCoord0;
out vec4 color;
layout(location = 0) out vec4 color;
vec4 MTF(vec4 x, vec3 m)
vec4 MTF(vec4 x, vec4 low, vec4 mid, vec4 high)
{
x = (x - m.x) / (m.z - m.x);
x = (x - low) / (high - low);
x = clamp(x, vec4(0.0), vec4(1.0));
return ((m.y - 1) * x) / ((2 * m.y - 1) * x - m.y);
return ((mid - 1) * x) / ((2 * mid - 1) * x - mid);
}
void main(void)
{
color = texture(qt_Texture0, qt_TexCoord0);
color = MTF(color, mtf_param);
color = MTF(color, vec4(mtf_param[0], 0.0), vec4(mtf_param[1], 0.5), vec4(mtf_param[2], 1.0));
if(invert)color = vec4(1.0) - color;
color.a = 1.0;
}
+24 -2
View File
@@ -9,8 +9,8 @@
<description>
<p>It is intended primarily for viewing astro photos and images. It supports the following formats:</p>
<ul>
<li>FITS 8, 16 bit integer and 32 bit float</li>
<li>XISF 8, 16 bit integer and 32 bit float</li>
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
<li>RAW CR2, DNG, NEF</li>
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
</ul>
@@ -27,6 +27,7 @@
<li>Thumbnails</li>
<li>Convert CFA images to colour - debayer</li>
<li>Color space aware</li>
<li>Histogram</li>
</ul>
</description>
<categories>
@@ -48,6 +49,27 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="20231116" date="2023-11-16">
<description>
<ul>
<li>Histogram</li>
<li>False colors</li>
<li>Strech each RGB channel individually</li>
<li>Better white balancing</li>
</ul>
</description>
</release>
<release version="20230419" date="2023-04-19">
<description>
<ul>
<li>Add file sorting</li>
<li>Improved zoom and scrolling.</li>
<li>Shift modify if zoom on mouse position or center</li>
<li>Fix issue with XISF from NINA</li>
<li>Fix crash in flatpak version</li>
</ul>
</description>
</release>
<release version="20230212" date="2023-02-12">
<description>
<ul>
+21 -5
View File
@@ -10,18 +10,34 @@ static float clamp(float x)
return std::min(std::max(x, 0.0f), 1.0f);
}
STFSlider::STFSlider(QWidget *parent) : QWidget(parent)
STFSlider::STFSlider(const QColor &color, QWidget *parent) : QWidget(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setMinimumWidth(100);
setMinimumHeight(15);
setMaximumHeight(15);
setMouseTracking(true);
if(color == Qt::white)
{
setMaximumHeight(16);
setMinimumHeight(16);
}
else
{
setMaximumHeight(10);
setMinimumHeight(10);
}
m_blackPoint = 0;
m_midPoint = 0.5;
m_whitePoint = 1;
m_grabbed = -1;
m_fineTune = false;
m_color = color;
if(color == Qt::blue || color == Qt::red)
m_threshold = 1.1f;
else if(color == Qt::green)
m_threshold = 0.8f;
else
m_threshold = 0.4f;
setToolTip(tr("Press Shift for fine tuning"));
}
@@ -60,7 +76,7 @@ void STFSlider::paintEvent(QPaintEvent *event)
{
qreal p = i/32.0f;
qreal c = std::pow(p, 1.0/2.2)*255;
gradient.setColorAt(p, QColor(c, c, c));
gradient.setColorAt(p, QColor(m_color.redF()*c, m_color.greenF()*c, m_color.blueF()*c));
}
QPainterPath tick(QPointF(0, 0));
@@ -75,7 +91,7 @@ void STFSlider::paintEvent(QPaintEvent *event)
auto drawTick = [&](qreal p)
{
painter.setPen(p < 0.4 ? Qt::white : Qt::black);
painter.setPen(p < m_threshold ? Qt::white : Qt::black);
painter.resetTransform();
painter.translate(w*p, 0);
painter.drawPath(tick);
+3 -1
View File
@@ -13,8 +13,10 @@ class STFSlider : public QWidget
int m_grabbed;
bool m_fineTune;
float m_fineTuneX;
QColor m_color;
float m_threshold;
public:
explicit STFSlider(QWidget *parent = nullptr);
explicit STFSlider(const QColor &color = Qt::white, QWidget *parent = nullptr);
float blackPoint() const;
float midPoint() const;
float whitePoint() const;
+137 -17
View File
@@ -18,13 +18,60 @@ float MTF(float x, float m)
StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar"), parent)
{
setObjectName("stretchtoolbar");
m_stfSlider = new STFSlider(this);
addWidget(m_stfSlider);
connect(m_stfSlider, SIGNAL(paramChanged(float, float, float)), this, SIGNAL(paramChanged(float,float,float)));
QWidget *lum = new QWidget(this);
QVBoxLayout *vbox1 = new QVBoxLayout(lum);
m_stfSlider = new STFSlider(Qt::white, this);
vbox1->addWidget(m_stfSlider);
m_stfSliderR = new STFSlider(Qt::red, this);
m_stfSliderG = new STFSlider(Qt::green, this);
m_stfSliderB = new STFSlider(Qt::blue, this);
QWidget *rgb = new QWidget(this);
QVBoxLayout *vbox2 = new QVBoxLayout(rgb);
vbox2->setSpacing(0);
vbox2->addWidget(m_stfSliderR);
vbox2->addWidget(m_stfSliderG);
vbox2->addWidget(m_stfSliderB);
m_stack = new QStackedWidget(this);
m_stack->addWidget(lum);
m_stack->addWidget(rgb);
m_stack->setCurrentIndex(0);
addWidget(m_stack);
connect(m_stfSlider, &STFSlider::paramChanged, [this](float blackPoint, float midPoint, float whitePoint){
m_mtfParam.blackPoint[0] = m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = blackPoint;
m_mtfParam.midPoint[0] = m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = midPoint;
m_mtfParam.whitePoint[0] = m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = whitePoint;
emit paramChanged(m_mtfParam);
});
connect(m_stfSliderR, &STFSlider::paramChanged, [this](float blackPoint, float midPoint, float whitePoint){
m_mtfParam.blackPoint[0] = blackPoint;
m_mtfParam.midPoint[0] = midPoint;
m_mtfParam.whitePoint[0] = whitePoint;
emit paramChanged(m_mtfParam);
});
connect(m_stfSliderG, &STFSlider::paramChanged, [this](float blackPoint, float midPoint, float whitePoint){
m_mtfParam.blackPoint[1] = blackPoint;
m_mtfParam.midPoint[1] = midPoint;
m_mtfParam.whitePoint[1] = whitePoint;
emit paramChanged(m_mtfParam);
});
connect(m_stfSliderB, &STFSlider::paramChanged, [this](float blackPoint, float midPoint, float whitePoint){
m_mtfParam.blackPoint[2] = blackPoint;
m_mtfParam.midPoint[2] = midPoint;
m_mtfParam.whitePoint[2] = whitePoint;
emit paramChanged(m_mtfParam);
});
QAction *rgbStretch = addAction(QIcon(":/link.png"), tr("Linked channels"));
rgbStretch->setCheckable(true);
rgbStretch->setChecked(true);
connect(rgbStretch, &QAction::toggled, this, &StretchToolbar::unlinkStretch);
QAction *autoStretchButton = addAction(QIcon(":/nuke.png"), tr("Auto Stretch F12"));
autoStretchButton->setShortcut(Qt::Key_F12);
connect(autoStretchButton, SIGNAL(triggered()), this, SIGNAL(autoStretch()));
connect(autoStretchButton, &QAction::triggered, this, &StretchToolbar::autoStretch);
QAction *resetButton = addAction(style()->standardIcon(QStyle::SP_DialogResetButton), tr("Reset Screen Transfer Function F11"));
resetButton->setShortcut(Qt::Key_F11);
@@ -32,11 +79,15 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
QAction *invertButton = addAction(QIcon(":/invert.png"), tr("Invert colors"));
invertButton->setCheckable(true);
connect(invertButton, SIGNAL(toggled(bool)), this, SIGNAL(invert(bool)));
connect(invertButton, &QAction::toggled, this, &StretchToolbar::invert);
QAction *superPixelButton = addAction(QIcon(":/bayer.png"), tr("Debayer CFA"));
superPixelButton->setCheckable(true);
connect(superPixelButton, SIGNAL(toggled(bool)), this, SIGNAL(superPixel(bool)));
QAction *falseColorButton = addAction(QIcon(":/falsecolor.png"), tr("False colors"));
falseColorButton->setCheckable(true);
connect(falseColorButton, &QAction::toggled, this, &StretchToolbar::falseColor);
m_debayer = addAction(QIcon(":/bayer.png"), tr("Debayer CFA"));
m_debayer->setCheckable(true);
connect(m_debayer, &QAction::toggled, this, &StretchToolbar::superPixel);
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
m_autoStretchOnLoad->setCheckable(true);
@@ -44,28 +95,80 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
void StretchToolbar::stretchImage(Image *img)
{
if(img)
if(img && img->rawImage())
{
if(img->rawImage())
const RawImage::Stats &stats = img->rawImage()->imageStats();
int i = 0;
int ch = 1;
int o = 0;
if(m_stack->currentIndex() == 1 && img->rawImage()->channels() == 1 && m_debayer->isChecked())
{
i = 1;
ch = 4;
o = 1;
}
if(img->rawImage()->channels() >= 3)
ch = 3;
float bp2 = 0;
float mid2 = 0;
float max2 = 0;
for(; i < ch; i++)
{
double median, mad, max;
img->rawImage()->imageStats(nullptr, nullptr, &median, nullptr, &max, &mad, nullptr);
median = stats.m_median[i];
mad = stats.m_mad[i];
max = stats.m_max[i];
median /= img->rawImage()->norm();
mad /= img->rawImage()->norm();
max /= img->rawImage()->norm();
if(max>1.0f)max = 1.0f;
float bp = median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA;
float mid = MTF(median - bp, TARGET_BACKGROUND);
m_stfSlider->setMTFParams(bp, mid, max);
emit paramChanged(m_stfSlider->blackPoint(), m_stfSlider->midPoint(), max);
m_mtfParam.blackPoint[i-o] = bp;
m_mtfParam.midPoint[i-o] = mid / max;
m_mtfParam.whitePoint[i-o] = max;
bp2 += bp;
mid2 += mid;
max2 = max > max2 ? max : max2;
}
if(ch == 1)
{
m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = m_mtfParam.blackPoint[0];
m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = m_mtfParam.midPoint[0];
m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = m_mtfParam.whitePoint[0];
}
if(m_stack->currentIndex() == 0)
{
m_mtfParam.blackPoint[0] = m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = bp2 / ch;
m_mtfParam.midPoint[0] = m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = mid2 / ch;
m_mtfParam.whitePoint[0] = m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = max2;
m_stfSlider->setMTFParams(m_mtfParam.blackPoint[0], m_mtfParam.midPoint[0], m_mtfParam.whitePoint[0]);
}
else
{
m_stfSliderR->setMTFParams(m_mtfParam.blackPoint[0], m_mtfParam.midPoint[0], m_mtfParam.whitePoint[0]);
m_stfSliderG->setMTFParams(m_mtfParam.blackPoint[1], m_mtfParam.midPoint[1], m_mtfParam.whitePoint[1]);
m_stfSliderB->setMTFParams(m_mtfParam.blackPoint[2], m_mtfParam.midPoint[2], m_mtfParam.whitePoint[2]);
}
emit paramChanged(m_mtfParam);
}
}
void StretchToolbar::resetMTF()
{
m_stfSlider->setMTFParams(0, 0.5, 1);
emit paramChanged(0, 0.5, 1);
MTFParam params;
m_mtfParam = params;
if(m_stack->currentIndex() == 0)
{
m_stfSlider->setMTFParams(0, 0.5, 1);
}
else
{
m_stfSliderR->setMTFParams(0, 0.5, 1);
m_stfSliderG->setMTFParams(0, 0.5, 1);
m_stfSliderB->setMTFParams(0, 0.5, 1);
}
emit paramChanged(params);
}
void StretchToolbar::imageLoaded(Image *img)
@@ -74,4 +177,21 @@ void StretchToolbar::imageLoaded(Image *img)
stretchImage(img);
}
void StretchToolbar::unlinkStretch(bool enable)
{
if(!enable)
{
m_stack->setCurrentIndex(1);
m_mtfParam.blackPoint[0] = m_stfSliderR->blackPoint(); m_mtfParam.midPoint[0] = m_stfSliderR->midPoint(); m_mtfParam.whitePoint[0] = m_stfSliderR->whitePoint();
m_mtfParam.blackPoint[1] = m_stfSliderG->blackPoint(); m_mtfParam.midPoint[1] = m_stfSliderG->midPoint(); m_mtfParam.whitePoint[1] = m_stfSliderG->whitePoint();
m_mtfParam.blackPoint[2] = m_stfSliderB->blackPoint(); m_mtfParam.midPoint[2] = m_stfSliderB->midPoint(); m_mtfParam.whitePoint[2] = m_stfSliderB->whitePoint();
}
else
{
m_stack->setCurrentIndex(0);
m_mtfParam.blackPoint[0] = m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = m_stfSlider->blackPoint();
m_mtfParam.midPoint[0] = m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = m_stfSlider->midPoint();
m_mtfParam.whitePoint[0] = m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = m_mtfParam.whitePoint[2] = m_stfSlider->whitePoint();
}
emit paramChanged(m_mtfParam);
}
+17 -1
View File
@@ -2,26 +2,42 @@
#define STRETCHTOOLBAR_H
#include <QToolBar>
#include <QStackedWidget>
#include "stfslider.h"
class Image;
struct MTFParam
{
float blackPoint[3] = {0.0f, 0.0f, 0.0f};
float midPoint[3] = {0.5f, 0.5f, 0.5f};
float whitePoint[3] = {1.0f, 1.0f, 1.0f};
};
class StretchToolbar : public QToolBar
{
Q_OBJECT
STFSlider *m_stfSlider;
STFSlider *m_stfSliderR;
STFSlider *m_stfSliderG;
STFSlider *m_stfSliderB;
QAction *m_autoStretchOnLoad;
QAction *m_debayer;
QStackedWidget *m_stack;
MTFParam m_mtfParam;
public:
explicit StretchToolbar(QWidget *parent = nullptr);
public slots:
void stretchImage(Image *img);
void resetMTF();
void imageLoaded(Image *img);
void unlinkStretch(bool enable);
signals:
void paramChanged(float low, float mid, float high);
void paramChanged(const MTFParam &params);
void autoStretch();
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
};
#endif // STRETCHTOOLBAR_H
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1370
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+42 -28
View File
@@ -41,6 +41,29 @@
<translation>File name</translation>
</message>
</context>
<context>
<name>FilesystemWidget</name>
<message>
<source>Sort by filename</source>
<translation>Sort by file name</translation>
</message>
<message>
<source>Sort by time</source>
<translation>Sort by time</translation>
</message>
<message>
<source>Sort by size</source>
<translation>Sort by size</translation>
</message>
<message>
<source>Sort by type</source>
<translation>Sort by type</translation>
</message>
<message>
<source>Reverse</source>
<translation>Reverse order</translation>
</message>
</context>
<context>
<name>Filetree</name>
<message>
@@ -83,6 +106,13 @@
<translation>Help</translation>
</message>
</context>
<context>
<name>Histogram</name>
<message>
<source>Logarithmic scale</source>
<translation>Logarithmic scale</translation>
</message>
</context>
<context>
<name>ImageInfo</name>
<message>
@@ -202,10 +232,6 @@
<source>100%</source>
<translation>100%</translation>
</message>
<message>
<source>Fullscreen</source>
<translation type="vanished">Fullscreen</translation>
</message>
<message>
<source>Select</source>
<translation>Select</translation>
@@ -314,10 +340,6 @@
<source>Edit</source>
<translation>Edit</translation>
</message>
<message>
<source>FITS header editor</source>
<translation type="vanished">FITS header editor</translation>
</message>
<message>
<source>Settings</source>
<translation>Settings</translation>
@@ -382,6 +404,10 @@
<source>CSV file (*.csv)</source>
<translation>CSV files (*.csv)</translation>
</message>
<message>
<source>Histogram</source>
<translation>Histogram</translation>
</message>
</context>
<context>
<name>MarkedFiles</name>
@@ -452,22 +478,6 @@
<source>MAD</source>
<translation>MAD</translation>
</message>
<message>
<source>Peaks</source>
<translation>Peaks</translation>
</message>
<message>
<source>Peaks draw</source>
<translation type="vanished">Peaks draw</translation>
</message>
<message>
<source>FWHM X</source>
<translation>FWHM X</translation>
</message>
<message>
<source>FWHM Y</source>
<translation>FWHM Y</translation>
</message>
<message>
<source>Unsupported sample format</source>
<translation>Unsupported sample format</translation>
@@ -513,10 +523,6 @@
<source>Thumbnails size</source>
<translation>Thumbnails size</translation>
</message>
<message>
<source>Changes in settings will take effect after program restart.</source>
<translation>Changes in settings will take effect after program restart.</translation>
</message>
<message>
<source>Don&apos;t use native file dialog</source>
<translation>Don&apos;t use native file dialog</translation>
@@ -558,5 +564,13 @@ For RAW files you may set 22%</translation>
<source>Debayer CFA</source>
<translation>Debayer CFA</translation>
</message>
<message>
<source>False colors</source>
<translation>False colors</translation>
</message>
<message>
<source>Linked channels</source>
<translation>Linked channels</translation>
</message>
</context>
</TS>
Binary file not shown.
+42 -28
View File
@@ -41,6 +41,29 @@
<translation>Nom de fichier</translation>
</message>
</context>
<context>
<name>FilesystemWidget</name>
<message>
<source>Sort by filename</source>
<translation>Trier par nom de fichier</translation>
</message>
<message>
<source>Sort by time</source>
<translation>Trier par heure</translation>
</message>
<message>
<source>Sort by size</source>
<translation>Trier par taille</translation>
</message>
<message>
<source>Sort by type</source>
<translation>Trier par type</translation>
</message>
<message>
<source>Reverse</source>
<translation>Ordre inverse</translation>
</message>
</context>
<context>
<name>Filetree</name>
<message>
@@ -83,6 +106,13 @@
<translation>Aide</translation>
</message>
</context>
<context>
<name>Histogram</name>
<message>
<source>Logarithmic scale</source>
<translation>Échelle logarithmique</translation>
</message>
</context>
<context>
<name>ImageInfo</name>
<message>
@@ -202,10 +232,6 @@
<source>100%</source>
<translation>100%</translation>
</message>
<message>
<source>Fullscreen</source>
<translation type="vanished">Plein écran</translation>
</message>
<message>
<source>Select</source>
<translation>Sélectionner</translation>
@@ -314,10 +340,6 @@
<source>Edit</source>
<translation>Éditer</translation>
</message>
<message>
<source>FITS header editor</source>
<translation type="vanished">Éditeur d&apos;en-tête FITS</translation>
</message>
<message>
<source>Settings</source>
<translation>Réglages</translation>
@@ -382,6 +404,10 @@
<source>CSV file (*.csv)</source>
<translation>Fichiers CSV (*.csv)</translation>
</message>
<message>
<source>Histogram</source>
<translation>Histogramme</translation>
</message>
</context>
<context>
<name>MarkedFiles</name>
@@ -452,22 +478,6 @@
<source>MAD</source>
<translation>MAD</translation>
</message>
<message>
<source>Peaks</source>
<translation>Pics</translation>
</message>
<message>
<source>Peaks draw</source>
<translation type="vanished">Dessin des pic</translation>
</message>
<message>
<source>FWHM X</source>
<translation>FWHM X</translation>
</message>
<message>
<source>FWHM Y</source>
<translation>FWHM Y</translation>
</message>
<message>
<source>Unsupported sample format</source>
<translation>Format non pris en charge</translation>
@@ -513,10 +523,6 @@
<source>Thumbnails size</source>
<translation>Taille des vignette</translation>
</message>
<message>
<source>Changes in settings will take effect after program restart.</source>
<translation>Les changements de paramètres prendront effet après le redémarrage du programme.</translation>
</message>
<message>
<source>Don&apos;t use native file dialog</source>
<translation>N&apos;utilisez pas la boîte de dialogue de fichier natif</translation>
@@ -558,5 +564,13 @@ Pour les fichiers RAW, vous pouvez définir 22&#xa0;%</translation>
<source>Debayer CFA</source>
<translation>Débayeriser CFA</translation>
</message>
<message>
<source>False colors</source>
<translation>Fausses couleurs</translation>
</message>
<message>
<source>Linked channels</source>
<translation>Chaînes liées</translation>
</message>
</context>
</TS>
Binary file not shown.
+42 -28
View File
@@ -42,6 +42,29 @@
<translation>Meno súboru</translation>
</message>
</context>
<context>
<name>FilesystemWidget</name>
<message>
<source>Sort by filename</source>
<translation>Zoradiť podľa názvu súboru</translation>
</message>
<message>
<source>Sort by time</source>
<translation>Zoradiť podľa času</translation>
</message>
<message>
<source>Sort by size</source>
<translation>Zoradiť podľa veľkosti</translation>
</message>
<message>
<source>Sort by type</source>
<translation>Zoradiť podľa typu</translation>
</message>
<message>
<source>Reverse</source>
<translation>Otočit poradie</translation>
</message>
</context>
<context>
<name>Filetree</name>
<message>
@@ -84,6 +107,13 @@
<translation>Pomoc</translation>
</message>
</context>
<context>
<name>Histogram</name>
<message>
<source>Logarithmic scale</source>
<translation>Logaritmická stupnica</translation>
</message>
</context>
<context>
<name>ImageInfo</name>
<message>
@@ -199,10 +229,6 @@
<source>100%</source>
<translation>100%</translation>
</message>
<message>
<source>Fullscreen</source>
<translation type="vanished">Celá obrazovka</translation>
</message>
<message>
<source>Select</source>
<translation>Výber</translation>
@@ -315,10 +341,6 @@
<source>Edit</source>
<translation>Upraviť</translation>
</message>
<message>
<source>FITS header editor</source>
<translation type="vanished">Editor FITS hlavičky</translation>
</message>
<message>
<source>Settings</source>
<translation>Nastavenia</translation>
@@ -383,6 +405,10 @@
<source>CSV file (*.csv)</source>
<translation>Súbory CSV (*.csv)</translation>
</message>
<message>
<source>Histogram</source>
<translation>Histogram</translation>
</message>
</context>
<context>
<name>MarkedFiles</name>
@@ -457,22 +483,6 @@
<source>MAD</source>
<translation>MAD</translation>
</message>
<message>
<source>Peaks</source>
<translation>Vrcholky</translation>
</message>
<message>
<source>Peaks draw</source>
<translation type="vanished">Vykreslené vrcholky</translation>
</message>
<message>
<source>FWHM X</source>
<translation>FWHM X</translation>
</message>
<message>
<source>FWHM Y</source>
<translation>FWHM Y</translation>
</message>
<message>
<source>Saturated</source>
<translation>Saturované</translation>
@@ -514,10 +524,6 @@
<source>Thumbnails size</source>
<translation>Veľkosť náhľadu</translation>
</message>
<message>
<source>Changes in settings will take effect after program restart.</source>
<translation>Zmeny v nastaveniach sa prejavia po reštarte programu.</translation>
</message>
<message>
<source>Don&apos;t use native file dialog</source>
<translation>Nepoužívať natívny súborový dialóg</translation>
@@ -559,5 +565,13 @@ Pre RAW súbory možno treba nastaviť 22%</translation>
<source>Debayer CFA</source>
<translation>Preveď CFA na farbu</translation>
</message>
<message>
<source>False colors</source>
<translation>Falošné farby</translation>
</message>
<message>
<source>Linked channels</source>
<translation>Prepojené kanály</translation>
</message>
</context>
</TS>