Compare commits

..

56 Commits

Author SHA1 Message Date
nou 4e952873e3 Fix metainfo 2024-02-02 00:09:42 +01:00
nou fb24800050 Add DBus for MacOS to fix build issue 2024-02-01 23:27:40 +01:00
nou ea0dcc226a Update metainfo 2024-02-01 23:26:08 +01:00
nou 6a7b677b95 Translatiotions 2024-02-01 23:22:35 +01:00
nou 0cee4c9c53 Add thumbnail quality to settings 2024-02-01 23:03:21 +01:00
nou d5f2351905 Only degress should show negative sign 2024-01-22 21:32:15 +01:00
nou 18732a8cbf Limit image info to 1024 characters 2024-01-22 21:31:28 +01:00
nou 8c9c1d8d06 Flip image according to ROWORDER 2024-01-22 21:30:39 +01:00
nou e5f425ff8d Fix some edge cases when stretch 2024-01-18 16:10:11 +01:00
nou 428f9c360a Small optimization in resample 2024-01-15 08:58:14 +01:00
nou a8a1509db7 Show error message in main window when image fail to load 2024-01-14 14:32:01 +01:00
nou 6539c78c57 Add box resize algorithm 2024-01-14 14:28:28 +01:00
nou 0e0d29320e Set unlimited image size so it doesn't fail to load big images 2024-01-14 14:04:54 +01:00
nou 1efe8e6974 Fix date in meta info 2024-01-11 13:30:30 +01:00
nou dae10182d1 Fix SSE instricts ifdef 2024-01-09 15:40:27 +01:00
nou ed5fc9c1c2 Increase number max preload images to 32 2024-01-08 16:52:46 +01:00
nou cd6a64a98b Additional work on batch processing 2024-01-08 15:44:05 +01:00
nou 67355a82b7 Add bayer mask selection 2024-01-08 15:43:21 +01:00
nou 8fc2078a3a Don't skip images before they load 2024-01-05 13:37:13 +01:00
nou da9b389409 Add slideshow 2024-01-05 13:36:06 +01:00
nou 7818b8d3e9 Fix icon instalation 2023-12-31 16:04:32 +01:00
nou 11294bfcb0 Scripting module 2023-12-31 16:04:16 +01:00
nou faecb385aa Reorganize resources 2023-12-22 11:20:03 +01:00
nou e5be04926b Fix warnings 2023-12-20 11:31:55 +01:00
nou eaf2c7094b Migrate to Qt6 2023-12-20 11:31:51 +01:00
nou aef41f5f6b Fix Qt deprectation warnings 2023-12-18 16:03:04 +01:00
nou 2134f13b06 Add nearest and bicubic filtering 2023-12-18 15:54:15 +01:00
nou 0e9c980325 Add support for CR3 files 2023-11-25 18:06:38 +01:00
nou b9bf6bf183 Fixed scaling for int32 2023-11-21 18:31:07 +01:00
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
84 changed files with 5840 additions and 815 deletions
+41 -33
View File
@@ -12,8 +12,12 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
find_package(Qt5 COMPONENTS Widgets Sql OpenGL REQUIRED) option(SANITIZE_ADDRESS_LEAK "Enable -fsanitize=address -fsanitize=leak" OFF)
find_package(OpenCV REQUIRED) if(SANITIZE_ADDRESS_LEAK)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
endif(SANITIZE_ADDRESS_LEAK)
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
find_library(GSL_LIB gsl REQUIRED) find_library(GSL_LIB gsl REQUIRED)
find_library(GSLCBLAS_LIB gslcblas REQUIRED) find_library(GSLCBLAS_LIB gslcblas REQUIRED)
find_library(EXIF_LIB exif REQUIRED) find_library(EXIF_LIB exif REQUIRED)
@@ -24,68 +28,72 @@ find_library(WCS_LIB wcs wcslib PATHS REQUIRED)
add_subdirectory(libXISF) add_subdirectory(libXISF)
set(TENMON_SRC set(TENMON_SRC
about.cpp about.cpp about.h
database.cpp batchprocessing.cpp batchprocessing.h batchprocessing.ui
databaseview.cpp database.cpp database.h
databaseview.cpp databaseview.h
delete.cpp delete.cpp
filesystemwidget.cpp filesystemwidget.cpp filesystemwidget.h
imageinfo.cpp histogram.cpp histogram.h
imageringlist.cpp imageinfo.cpp imageinfo.h
imageringlist.cpp imageringlist.h
imagescrollarea.cpp imagescrollarea.cpp
imagescrollareagl.cpp imagescrollareagl.cpp imagescrollareagl.h
loadrunable.cpp loadrunable.cpp loadrunable.h
main.cpp main.cpp
mainwindow.cpp mainwindow.cpp mainwindow.h
markedfiles.cpp markedfiles.cpp markedfiles.h
rawimage.cpp rawimage.cpp rawimage.h
settingsdialog.cpp rawimage_sse.cpp
starfit.cpp scriptengine.cpp scriptengine.h
statusbar.cpp settingsdialog.cpp settingsdialog.h
stfslider.cpp starfit.cpp starfit.h
stretchtoolbar.cpp 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) option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.14")
set(COLOR_MANAGMENT OFF)
endif(${Qt5Core_VERSION_STRING} VERSION_LESS "5.14")
if(COLOR_MANAGMENT) if(COLOR_MANAGMENT)
add_compile_definitions("COLOR_MANAGMENT") add_compile_definitions("COLOR_MANAGMENT")
endif(COLOR_MANAGMENT) endif(COLOR_MANAGMENT)
qt5_add_resources(TENMON_SRC resources.qrc) qt_add_resources(TENMON_SRC resources/resources.qrc)
qt_add_resources(TENMON_SRC shaders/shaders.qrc)
if(WIN32) if(WIN32)
list(APPEND TENMON_SRC icon.rc) list(APPEND TENMON_SRC resources/icon.rc)
set(tenmon_ICON "") set(tenmon_ICON "")
elseif(APPLE) elseif(APPLE)
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/tenmon.icns) set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/tenmon.icns)
find_package(Qt6 COMPONENTS DBus REQUIRED)
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
else() else()
set(tenmon_ICON "") set(tenmon_ICON "")
find_package(Qt6 COMPONENTS DBus REQUIRED)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_search_module(GIO REQUIRED gio-2.0) pkg_search_module(GIO REQUIRED gio-2.0)
endif() endif()
add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC}) qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED) 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) if(UNIX AND NOT APPLE)
target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS}) target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS})
endif() 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 PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} XISF)
if(APPLE) if(APPLE)
target_link_libraries(tenmon "-framework CoreFoundation") target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
else() elseif(UNIX)
target_link_libraries(tenmon ${GIO_LDFLAGS}) target_link_libraries(tenmon PRIVATE Qt6::DBus ${GIO_LDFLAGS})
endif(APPLE) endif(APPLE)
if(LIBRAW_STATIC) if(LIBRAW_STATIC)
add_compile_definitions("LIBRAW_NODLL") add_compile_definitions("LIBRAW_NODLL")
target_link_libraries(tenmon jasper) target_link_libraries(tenmon PRIVATE jasper)
endif() endif()
install(TARGETS tenmon BUNDLE DESTINATION .) install(TARGETS tenmon BUNDLE DESTINATION .)
@@ -96,8 +104,8 @@ if(UNIX AND NOT APPLE)
install(SCRIPT install.cmake) install(SCRIPT install.cmake)
else() else()
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications") install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
install(FILES space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps") install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
install(FILES space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png) install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
endif() endif()
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo") install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
endif(UNIX AND NOT APPLE) endif(UNIX AND NOT APPLE)
+4 -4
View File
@@ -2,20 +2,20 @@ FITS/XISF image viewer with multithreaded image loading
To get all dependencies install these packages To get all dependencies install these packages
sudo apt install qtbase5-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev libopencv-dev cmake sudo apt install qt6-base-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev cmake
on OpenSUSE on OpenSUSE
sudo zypper install opencv-devel gsl-devel exif-devel libraw-devel wcslib-devel libqt5-qtbase-devel sudo zypper install gsl-devel exif-devel libraw-devel wcslib-devel libqt6-qtbase-devel
MacOS X MacOS X
To compile on MacOS install XCode first. Then install homebrew in x86_64 mode To compile on MacOS install XCode first. Then install homebrew in x86_64 mode
with "arch -i x86_64". Building on native ARM is not supported. with "arch -i x86_64". Building on native ARM is not supported.
homebrew install qt5 libraw cfitsio libexif libgsl wcslib opencv homebrew install qt6 libraw cfitsio libexif libgsl wcslib
You may need to set CMAKE_PREFIX_PATH for Qt5 and OpenCV so CMake can find them. You may need to set CMAKE_PREFIX_PATH for Qt6 so CMake can find them.
Then to build run standard cmake Then to build run standard cmake
+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: <p>Tenmon is intended primarily for viewing astro photos and images. It supports the following formats:
<ul> <ul>
<li>FITS 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 bit integer and 32 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>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
<li>CR2, NEF, DNG raw images</li> <li>CR2, NEF, DNG raw images</li>
</ul> </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>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> <li>white point - all pixels with higher value (brighter) than this will be clipped white</li>
</ul> </ul>
Following the slider are 5 buttons for automatic stretching: Following the slider are 7 buttons for automatic stretching:
<ul> <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>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>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>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> <li><i>Apply Auto stretch on load</i> toggle automatically applying Autostretch for each image when loaded.</p>
</ul> </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: <p>Tenmon slúži primárne na zobrazenie astronomických fotiek a obrázkov. Dokáže otvoriť nasledovné formáty:
<ul> <ul>
<li>FITS 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 bitové celočíselné a 32 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>JPEG, PNG, BMP, GIF, PBM, PGM, PPM a SVG obrázky</li>
<li>CR2, NEF, DNG raw obrázky</li> <li>CR2, NEF, DNG raw obrázky</li>
</ul> </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>stredný bod - pixeli s touto hodnotou budú zobrazené ako 50% šedá</li>
<li>biely bod - pixeli nad touto hodnotou budú zobrazené ako biele</li> <li>biely bod - pixeli nad touto hodnotou budú zobrazené ako biele</li>
</ul> </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. 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. 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> 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> <h3>Označovanie obrázkov</h3>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

+218
View File
@@ -0,0 +1,218 @@
#include "batchprocessing.h"
#include "ui_batchprocessing.h"
#include <QDir>
#include <QFileDialog>
#include <QStandardPaths>
#include <QProcess>
#include <QSettings>
#include <QCloseEvent>
#include <QMessageBox>
#include "scriptengine.h"
#ifdef Q_OS_LINUX
#include <QCloseEvent>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QMessageBox>
#endif
void scanDirectory(const QString &path, QStringList &files)
{
QFileInfo info(path);
if(info.isDir())
{
QDir dir(path);
QStringList entries = dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
for(QString &entry : entries)
scanDirectory(dir.absoluteFilePath(entry), files);
}
else if(info.isFile())
{
files.append(path);
}
}
QStringList scanDirectories(const QStringList &paths)
{
QStringList files;
for(const QString &path : paths)
scanDirectory(path, files);
return files;
}
void BatchProcessing::scanScriptDir()
{
_ui->scriptsList->clear();
QDir dir(_scriptBasePath);
for(const QString &script : dir.entryList(QDir::Files | QDir::Readable))
{
_ui->scriptsList->addItem(script);
}
}
BatchProcessing::BatchProcessing(QWidget *parent) : QDialog(parent)
{
_ui = new Ui::BatchProcessing;
_ui->setupUi(this);
QStringList scriptsPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
if(scriptsPath.size())
{
QDir dir(scriptsPath.first());
if(!dir.exists("scripts"))
{
if(!dir.mkpath("scripts"))
qWarning() << "Failed to create scripts directory";
}
dir.cd("scripts");
_scriptBasePath = dir.absolutePath() + "/";
scanScriptDir();
_fileWatcher.addPath(_scriptBasePath);
connect(&_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &BatchProcessing::scanScriptDir);
}
else
{
qWarning() << "Failed to get app data location";
}
connect(_ui->addFilesButton, &QPushButton::released, this, &BatchProcessing::addFiles);
connect(_ui->addDirButton, &QPushButton::released, this, &BatchProcessing::addDir);
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
connect(_ui->startButton, &QPushButton::released, this, &BatchProcessing::runScript);
connect(_ui->stopButton, &QPushButton::released, this, &BatchProcessing::stopScript);
connect(_ui->browseButton, &QPushButton::released, this, &BatchProcessing::browse);
connect(_ui->openScriptsButton, &QPushButton::released, this, &BatchProcessing::openScriptDir);
QSettings settings;
_ui->outputPath->setText(settings.value("batchprocessing/outputpath", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()).toString());
}
BatchProcessing::~BatchProcessing()
{
delete _engineThread;
QSettings settings;
settings.setValue("batchprocessing/outputpath", _ui->outputPath->text());
delete _ui;
}
void BatchProcessing::closeEvent(QCloseEvent *event)
{
if(_engineThread)
{
QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Interrupt running script?"), tr("Interrupt running script?"));
if(ret == QMessageBox::StandardButton::Yes)
{
_engineThread->interrupt();
event->accept();
}
else
{
event->ignore();
}
}
else
{
event->accept();
}
}
void BatchProcessing::addFiles()
{
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files"), "/home/nou/Obrázky/astro");
_ui->pathsList->addItems(files);
}
void BatchProcessing::addDir()
{
QString dir = QFileDialog::getExistingDirectory(this, tr("Select directory"), "/home/nou/Obrázky/astro");
if(!dir.isEmpty())
_ui->pathsList->addItem(dir);
}
void BatchProcessing::removePath()
{
for(auto &item : _ui->pathsList->selectedItems())
delete item;
}
void BatchProcessing::removeAllPaths()
{
_ui->pathsList->clear();
}
void BatchProcessing::browse()
{
QString output = QFileDialog::getExistingDirectory(this, tr("Select output directory"), "/home/nou/Obrázky");
if(!output.isEmpty())
_ui->outputPath->setText(output);
}
void BatchProcessing::openScriptDir()
{
#ifdef Q_OS_LINUX
QDBusConnection con = QDBusConnection::sessionBus();
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", "ShowFolders");
QList<QVariant> args = {QStringList(QUrl::fromLocalFile(_scriptBasePath).toString()), QString()};
message.setArguments(args);
con.call(message);
#endif
#ifdef Q_OS_WINDOWS
QProcess::startDetached("explorer.exe", {QDir::toNativeSeparators(_scriptBasePath)});
#endif
}
void BatchProcessing::runScript()
{
_ui->log->clear();
auto selectedItems = _ui->scriptsList->selectedItems();
if(selectedItems.size())
{
_engineThread = new Script::ScriptEngineThread(this);
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
QStringList paths;
for(int i=0; i<_ui->pathsList->count(); i++)
paths.append(_ui->pathsList->item(i)->text());
QFileInfo outDir(_ui->outputPath->text());
if(outDir.exists() && outDir.isWritable())
{
_engineThread->setParams(_scriptBasePath + selectedItems.first()->text(), scanDirectories(paths), _ui->outputPath->text());
_engineThread->start();
_ui->startButton->setEnabled(false);
_ui->stopButton->setEnabled(true);
}
else
{
QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable"));
}
}
}
void BatchProcessing::stopScript()
{
qDebug() << "Stop script";
if(_engineThread)
_engineThread->interrupt();
}
void BatchProcessing::scriptFinished()
{
_ui->startButton->setEnabled(true);
_ui->stopButton->setEnabled(false);
qDebug() << "script finished";
delete _engineThread;
_engineThread = nullptr;
}
void BatchProcessing::newMessage(const QString &message, bool error)
{
QColor color = _ui->log->textColor();
if(error)_ui->log->setTextColor(Qt::red);
_ui->log->append(message);
if(error)_ui->log->setTextColor(color);
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef BATCHPROCESSING_H
#define BATCHPROCESSING_H
#include <QDialog>
#include <QFileSystemWatcher>
#include "scriptengine.h"
namespace Ui { class BatchProcessing; }
class BatchProcessing : public QDialog
{
Ui::BatchProcessing *_ui;
QString _scriptBasePath;
QFileSystemWatcher _fileWatcher;
Script::ScriptEngineThread *_engineThread = nullptr;
private slots:
void scanScriptDir();
public:
explicit BatchProcessing(QWidget *parent = nullptr);
~BatchProcessing();
protected:
void closeEvent(QCloseEvent *event);
public slots:
void scriptDirChanged();
void addFiles();
void addDir();
void removePath();
void removeAllPaths();
void browse();
void openScriptDir();
void runScript();
void stopScript();
void scriptFinished();
void newMessage(const QString &message, bool error);
};
#endif // BATCHPROCESSING_H
+226
View File
@@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BatchProcessing</class>
<widget class="QDialog" name="BatchProcessing">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<height>768</height>
</rect>
</property>
<property name="windowTitle">
<string>Batch Processing</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Input files and directories</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="pathsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="addFilesButton">
<property name="text">
<string>Add files</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addDirButton">
<property name="text">
<string>Add directories</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeAllButton">
<property name="text">
<string>Remove all</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Output directory</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="outputPath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseButton">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Scripts</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="openScriptsButton">
<property name="text">
<string>Open scripts</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="scriptsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Log</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="log">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>4</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>FreeMono</family>
</font>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="startButton">
<property name="text">
<string>Start script</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Stop script</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>closeButton</sender>
<signal>released()</signal>
<receiver>BatchProcessing</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>973</x>
<y>745</y>
</hint>
<hint type="destinationlabel">
<x>511</x>
<y>383</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+2 -2
View File
@@ -10,12 +10,12 @@ Database::Database(QObject *parent) : QObject(parent)
{ {
} }
bool Database::init() bool Database::init(const QLatin1String &connectionName)
{ {
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path); QDir dir(path);
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE"); QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
if(!dir.mkpath(".")) if(!dir.mkpath("."))
return false; return false;
+1 -1
View File
@@ -24,7 +24,7 @@ class Database : public QObject
int m_progress; int m_progress;
public: public:
explicit Database(QObject *parent = 0); explicit Database(QObject *parent = 0);
bool init(); bool init(const QLatin1String &connectionName = QLatin1String(QSqlDatabase::defaultConnection));
bool mark(const QString &filename); bool mark(const QString &filename);
bool unmark(const QString &filename); bool unmark(const QString &filename);
bool mark(const QStringList &filenames); bool mark(const QStringList &filenames);
+6 -1
View File
@@ -123,6 +123,10 @@ QVariant FITSFileModel::data(const QModelIndex &index, int role) const
font.setBold(m_markedFiles.contains(file)); font.setBold(m_markedFiles.contains(file));
return font; return font;
} }
if(role == Qt::ToolTipRole && index.column() == 0)
{
return QSqlQueryModel::data(index, Qt::DisplayRole);
}
return QSqlQueryModel::data(index, role); return QSqlQueryModel::data(index, role);
} }
@@ -197,7 +201,8 @@ void FITSFileModel::prepareQuery()
if(lastError().type() != QSqlError::NoError) if(lastError().type() != QSqlError::NoError)
qDebug() << "Database error" << lastError(); qDebug() << "Database error" << lastError();
m_markedFiles = m_database->getMarkedFiles().toSet(); QStringList list = m_database->getMarkedFiles();
m_markedFiles = QSet<QString>(list.begin(), list.end());
} }
DatabaseTableView::DatabaseTableView(QWidget *parent) : QTableView(parent) DatabaseTableView::DatabaseTableView(QWidget *parent) : QTableView(parent)
+7 -1
View File
@@ -45,11 +45,17 @@ void FilesystemWidget::fileClicked(const QModelIndex &index, const QModelIndex &
emit fileSelected(index.row()); 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) Filetree::Filetree(QWidget *parent) : QTreeView(parent)
{ {
QSettings settings; QSettings settings;
m_rootDir = settings.value("filetree/rootDir", QDir::homePath()).toString(); 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->setRootPath(m_rootDir);
m_fileSystemModel->setNameFilters({"*.fits", "*.fit", "*.xisf", "*.jpg", "*.jpeg", "*.png", "*.cr2", "*.nef", "*.dng"}); m_fileSystemModel->setNameFilters({"*.fits", "*.fit", "*.xisf", "*.jpg", "*.jpeg", "*.png", "*.cr2", "*.nef", "*.dng"});
m_fileSystemModel->setNameFilterDisables(false); m_fileSystemModel->setNameFilterDisables(false);
+8 -1
View File
@@ -23,10 +23,17 @@ signals:
void reverseSort(); 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 class Filetree : public QTreeView
{ {
Q_OBJECT Q_OBJECT
QFileSystemModel *m_fileSystemModel; FileSystemModel *m_fileSystemModel;
QString m_rootDir; QString m_rootDir;
public: public:
explicit Filetree(QWidget *parent = nullptr); 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
+2 -2
View File
@@ -81,7 +81,7 @@ void ImageInfo::setInfo(const ImageInfoData &info)
QTreeWidgetItem *fitsHeader = new QTreeWidgetItem({tr("FITS Header")}); QTreeWidgetItem *fitsHeader = new QTreeWidgetItem({tr("FITS Header")});
for(const FITSRecord &record : info.fitsHeader) for(const FITSRecord &record : info.fitsHeader)
{ {
new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString(), record.comment}); new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString().left(1024), record.comment});
} }
addTopLevelItem(fitsHeader); addTopLevelItem(fitsHeader);
} }
@@ -279,7 +279,7 @@ QString SkyPoint::toString() const
t = t.addSecs(ra * 240); t = t.addSecs(ra * 240);
double deg, min, sec; double deg, min, sec;
min = std::modf(dec, &deg) * 60; min = std::abs(std::modf(dec, &deg) * 60);
sec = std::modf(min, &min) * 60; sec = std::modf(min, &min) * 60;
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0'); return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
} }
+30 -8
View File
@@ -1,6 +1,8 @@
#include "imageringlist.h" #include "imageringlist.h"
#include <QThreadPool> #include <QThreadPool>
#include <QDir> #include <QDir>
#include <QSettings>
#include <QTimer>
#include "loadrunable.h" #include "loadrunable.h"
#include "rawimage.h" #include "rawimage.h"
#include "database.h" #include "database.h"
@@ -81,24 +83,20 @@ void Image::clearThumbnail()
m_thumbnail.reset(); m_thumbnail.reset();
} }
void Image::imageLoaded(void *rawImage, ImageInfoData info) void Image::imageLoaded(std::shared_ptr<RawImage> rawImage, ImageInfoData info)
{ {
m_loading = false; m_loading = false;
if(!m_released) if(!m_released)
{ {
m_rawImage.reset(static_cast<RawImage*>(rawImage)); m_rawImage = rawImage;
m_info = info; m_info = info;
emit pixmapLoaded(this); 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) if(m_thumbnail)
emit thumbnailLoaded(this); emit thumbnailLoaded(this);
} }
@@ -112,6 +110,9 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString))); connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*."); m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
m_thumbPool = new QThreadPool(this); m_thumbPool = new QThreadPool(this);
m_slideShowTimer = new QTimer(this);
connect(m_slideShowTimer, &QTimer::timeout, this, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
} }
ImageRingList::~ImageRingList() ImageRingList::~ImageRingList()
@@ -167,6 +168,10 @@ void ImageRingList::increment()
{ {
if(m_images.size()) if(m_images.size())
{ {
//don't increment if current image was not loaded yet
if(!(*m_currImage)->rawImage())
return;
(*m_firstImage)->release(); (*m_firstImage)->release();
m_firstImage = increment(m_firstImage); m_firstImage = increment(m_firstImage);
m_currImage = increment(m_currImage); m_currImage = increment(m_currImage);
@@ -299,11 +304,13 @@ void ImageRingList::clearThumbnails()
QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
{ {
Q_UNUSED(parent);
return createIndex(row, column, m_images.at(row).get()); return createIndex(row, column, m_images.at(row).get());
} }
QModelIndex ImageRingList::parent(const QModelIndex &child) const QModelIndex ImageRingList::parent(const QModelIndex &child) const
{ {
Q_UNUSED(child);
return QModelIndex(); return QModelIndex();
} }
@@ -317,6 +324,7 @@ int ImageRingList::rowCount(const QModelIndex &parent) const
int ImageRingList::columnCount(const QModelIndex &parent) const int ImageRingList::columnCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent);
return 1; return 1;
} }
@@ -402,6 +410,20 @@ void ImageRingList::reverseSort()
} }
} }
void ImageRingList::toggleSlideshow(bool start)
{
if(start)
{
QSettings settings;
int time = settings.value("settings/slideshowtime", 1.0).toDouble() * 1000;
m_slideShowTimer->start(time);
}
else
{
m_slideShowTimer->stop();
}
}
void ImageRingList::setFiles(const QStringList files, const QString &currentFile) void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
{ {
QThreadPool::globalInstance()->clear(); QThreadPool::globalInstance()->clear();
+7 -5
View File
@@ -21,7 +21,7 @@ class Image : public QObject
bool m_current; bool m_current;
int m_number; int m_number;
std::shared_ptr<RawImage> m_rawImage; std::shared_ptr<RawImage> m_rawImage;
std::unique_ptr<RawImage> m_thumbnail; std::shared_ptr<RawImage> m_thumbnail;
QString m_name; QString m_name;
ImageInfoData m_info; ImageInfoData m_info;
ImageRingList *m_ringList; ImageRingList *m_ringList;
@@ -41,8 +41,8 @@ signals:
void pixmapLoaded(Image *ptr); void pixmapLoaded(Image *ptr);
void thumbnailLoaded(Image *ptr); void thumbnailLoaded(Image *ptr);
protected slots: protected slots:
void imageLoaded(void *rawImage, ImageInfoData info); void imageLoaded(std::shared_ptr<RawImage> rawImage, ImageInfoData info);
void thumbnailLoadFinish(void *rawImage); void thumbnailLoadFinish(std::shared_ptr<RawImage> rawImage);
}; };
typedef std::shared_ptr<Image> ImagePtr; typedef std::shared_ptr<Image> ImagePtr;
@@ -65,14 +65,13 @@ class ImageRingList : public QAbstractItemModel
QThreadPool *m_thumbPool; QThreadPool *m_thumbPool;
Database *m_database; Database *m_database;
QStringList m_nameFilter; QStringList m_nameFilter;
QTimer *m_slideShowTimer;
public: public:
explicit ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent = 0); explicit ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent = 0);
~ImageRingList() override; ~ImageRingList() override;
bool setDir(const QString path, const QString &currentFile = QString()); bool setDir(const QString path, const QString &currentFile = QString());
void setFile(const QString &file); void setFile(const QString &file);
ImagePtr currentImage(); ImagePtr currentImage();
void increment();
void decrement();
void setLiveMode(bool live); void setLiveMode(bool live);
void setCalculateStats(bool stats); void setCalculateStats(bool stats);
void setFindPeaks(bool findPeaks); void setFindPeaks(bool findPeaks);
@@ -96,6 +95,9 @@ public slots:
void setPreload(int width); void setPreload(int width);
void setSort(QDir::SortFlag sort); void setSort(QDir::SortFlag sort);
void reverseSort(); void reverseSort();
void toggleSlideshow(bool start);
void increment();
void decrement();
protected: protected:
void setFiles(const QStringList files, const QString &currentFile = QString()); void setFiles(const QStringList files, const QString &currentFile = QString());
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter); QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
+2 -2
View File
@@ -106,7 +106,7 @@ void ImageScrollArea::wheelEvent(QWheelEvent *event)
m_scale = (float)size().width()/m_pixmap.size().width(); m_scale = (float)size().width()/m_pixmap.size().width();
QPointF top(horizontalScrollBar()->value(), verticalScrollBar()->value()); QPointF top(horizontalScrollBar()->value(), verticalScrollBar()->value());
QPointF mousePos = (top + event->posF()) / m_scale; QPointF mousePos = (top + event->position()) / m_scale;
QPoint delta = event->angleDelta(); QPoint delta = event->angleDelta();
if(delta.y() > 0) if(delta.y() > 0)
@@ -115,7 +115,7 @@ void ImageScrollArea::wheelEvent(QWheelEvent *event)
setScale(m_scale - 0.1); setScale(m_scale - 0.1);
mousePos *= m_scale; mousePos *= m_scale;
top = mousePos - event->posF(); top = mousePos - event->position();
horizontalScrollBar()->setValue(top.x()); horizontalScrollBar()->setValue(top.x());
verticalScrollBar()->setValue(top.y()); verticalScrollBar()->setValue(top.y());
} }
+117 -119
View File
@@ -1,5 +1,6 @@
#include "imagescrollareagl.h" #include "imagescrollareagl.h"
#include <QOpenGLFunctions> #include <QOpenGLFunctions>
#include <QOpenGLVersionFunctionsFactory>
#include <QDebug> #include <QDebug>
#include <QKeyEvent> #include <QKeyEvent>
#include <QOpenGLDebugLogger> #include <QOpenGLDebugLogger>
@@ -12,70 +13,63 @@
#include <QPainter> #include <QPainter>
#include <QFileInfo> #include <QFileInfo>
#include <cmath> #include <cmath>
#include <QElapsedTimer>
int FILTERING = 1;
struct RawImageType struct RawImageType
{ {
QOpenGLTexture::PixelFormat pixelFormat; QOpenGLTexture::PixelFormat pixelFormat;
QOpenGLTexture::TextureFormat textureFormat; QOpenGLTexture::TextureFormat textureFormat;
QOpenGLTexture::PixelType dataType; QOpenGLTexture::PixelType dataType;
bool bw;
}; };
const RawImageType rawImageTypes[] = { RawImageType getRawImageType(const RawImage *img)
{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)
{ {
int page = scrollBar->pageStep(); RawImageType type;
int pos = scrollBar->value() + page/2; switch(img->type())
int range = scrollBar->maximum() + page; {
float relPos = (float)pos/(float)range; case RawImage::UINT8:
if(img->channels() >= 3)
if(page >= newRange) type.textureFormat = QOpenGLTexture::SRGB8_Alpha8;
scrollBar->hide();
else else
scrollBar->show(); 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;
break;
default:
qWarning() << "Invalid format" << img->type();
break;
}
scrollBar->setRange(0, newRange - page); if(img->channels() >= 3)
scrollBar->setValue(relPos*newRange - page/2); type.pixelFormat = QOpenGLTexture::RGBA;
else
type.pixelFormat = QOpenGLTexture::Red;
return type;
} }
ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(parent) ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(parent)
, m_database(database) , m_database(database)
{ {
setFocusPolicy(Qt::ClickFocus); 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 = new QTimer(this);
m_updateTimer->setInterval(500); m_updateTimer->setInterval(500);
m_updateTimer->setSingleShot(true); m_updateTimer->setSingleShot(true);
m_sizesDirty = false;
connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update())); connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
setAcceptDrops(true); setAcceptDrops(true);
QTimer::singleShot(1000, [this](){ QTimer::singleShot(1000, [this](){
@@ -96,22 +90,36 @@ ImageWidget::~ImageWidget()
void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index) void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
{ {
if(image == nullptr)return; m_currentImg = index;
if(!image || !image->valid())
{
m_imgWidth = 0;
m_imgHeight = 0;
m_error = tr("Failed to load image");
m_rawImage.reset();
update();
return;
}
m_error.clear();
makeCurrent(); makeCurrent();
m_rawImage = image; m_rawImage = image;
m_rawImage->downscaleTo(m_maxTextureSize); if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
m_rawImage->resize(std::min(m_maxTextureSize, (int)image->width()), std::min(m_maxTextureSize, (int)image->height()));
m_imgWidth = image->width(); m_imgWidth = image->width();
m_imgHeight = image->height(); m_imgHeight = image->height();
m_currentImg = index;
m_whiteBalance[0] = m_whiteBalance[1] = m_whiteBalance[2] = 1.0f;
if(!m_image)return; if(!m_image)return;
const RawImageType &rawImageType = rawImageTypes[image->type()]; RawImageType rawImageType = getRawImageType(image.get());
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8 || rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8; m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
m_bwImg = rawImageType.bw; m_bwImg = image->channels() == 1;
QElapsedTimer timer;
timer.start();
m_image->destroy(); m_image->destroy();
m_image->setAutoMipMapGenerationEnabled(false); m_image->setAutoMipMapGenerationEnabled(false);
m_image->setFormat(rawImageType.textureFormat); m_image->setFormat(rawImageType.textureFormat);
@@ -122,40 +130,17 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
m_image->setWrapMode(QOpenGLTexture::ClampToEdge); m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
m_image->setBorderColor(0, 0, 0, 0); m_image->setBorderColor(0, 0, 0, 0);
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data(), m_transferOptions.get()); 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;
if(image->type() == RawImage::FLOAT32)
{ {
pixel.x = pixel.x <= 0.04045f ? pixel.x / 12.92f : std::pow((pixel.x + 0.055) / 1.055f, 2.4f); auto unitScaling = image->unitScale();
pixel.y = pixel.y <= 0.04045f ? pixel.y / 12.92f : std::pow((pixel.y + 0.055) / 1.055f, 2.4f); m_unit_scale[0] = unitScaling.first;
pixel.z = pixel.z <= 0.04045f ? pixel.z / 12.92f : std::pow((pixel.z + 0.055) / 1.055f, 2.4f); m_unit_scale[1] = unitScaling.second;
};
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++)
{
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());
} }
}
else m_image->generateMipMaps();
if(m_debayerTex) if(m_debayerTex)
{ {
@@ -243,11 +228,21 @@ QVector2D ImageWidget::getImagePixelCoord(const QVector2D &pos)
return (pos + offset) / m_scale; return (pos + offset) / m_scale;
} }
void ImageWidget::setMTFParams(float low, float mid, float high) void ImageWidget::setBayerMask(int mask)
{ {
m_low = low; m_firstRed[0] = mask & 0x1;
m_mid = mid; m_firstRed[1] = (mask & 0x2) >> 1;
m_high = high; if(m_debayerTex)
{
f->glDeleteTextures(1, &m_debayerTex);
m_debayerTex = 0;
}
update();
}
void ImageWidget::setMTFParams(const MTFParam &params)
{
m_mtfParams = params;
update(); update();
} }
@@ -274,6 +269,12 @@ void ImageWidget::invert(bool enable)
update(); update();
} }
void ImageWidget::falseColor(bool enable)
{
m_falseColor = enable;
update();
}
QImage ImageWidget::renderToImage() QImage ImageWidget::renderToImage()
{ {
if(m_imgWidth < 0)return QImage(); if(m_imgWidth < 0)return QImage();
@@ -302,7 +303,8 @@ void ImageWidget::thumbnailLoaded(const Image *image)
{ {
makeCurrent(); makeCurrent();
const RawImage *raw = image->thumbnail(); const RawImage *raw = image->thumbnail();
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGB, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get()); if(!raw || !raw->valid())return;
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
float a = raw->thumbAspect(); 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() }; 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; m_sizesDirty = true;
@@ -350,7 +352,7 @@ void ImageWidget::paintGL()
m_thumbnailProgram->bind(); m_thumbnailProgram->bind();
f->glUniform3i(m_thumbnailProgram->uniformLocation("viewport_row"), width(), height(), width()/THUMB_SIZE_BORDER); 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); 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("invert", m_invert);
m_thumbnailProgram->setUniformValue("offset", 0, m_dy); m_thumbnailProgram->setUniformValue("offset", 0, m_dy);
QMatrix4x4 mvp; QMatrix4x4 mvp;
@@ -386,6 +388,13 @@ void ImageWidget::paintGL()
} }
} }
} }
else if(!m_error.isEmpty())
{
QPainter painter(this);
painter.setPen(Qt::red);
painter.setFont(QFont("Sans", 24, QFont::Bold));
painter.drawText(0, 0, width(), height(), Qt::AlignCenter | Qt::AlignHCenter, m_error);
}
else else
{ {
debayer(); debayer();
@@ -399,12 +408,13 @@ void ImageWidget::paintGL()
m_program->bind(); m_program->bind();
m_program->setUniformValue("viewport", (float)width(), (float)height()); m_program->setUniformValue("viewport", (float)width(), (float)height());
m_program->setUniformValue("offset", std::floor(dx), std::floor(dy)); m_program->setUniformValue("offset", std::floor(dx), std::floor(dy));
m_program->setUniformValue("mtf_param", m_low, m_mid, m_high); 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("zoom", 1.0f/m_scale);
m_program->setUniformValue("bw", m_bwImg && !m_superpixel); m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert); m_program->setUniformValue("invert", m_invert);
if(m_superpixel)m_program->setUniformValue("whiteBalance", m_whiteBalance[0], m_whiteBalance[1], m_whiteBalance[2]); m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
else m_program->setUniformValue("whiteBalance", 1.0f, 1.0f, 1.0f);
#ifdef COLOR_MANAGMENT #ifdef COLOR_MANAGMENT
m_program->setUniformValue("srgb", m_srgb); m_program->setUniformValue("srgb", m_srgb);
#endif #endif
@@ -426,7 +436,7 @@ void ImageWidget::initializeGL()
{ {
f = context()->functions(); f = context()->functions();
f->glClearColor(0.5f, 0.5f, 0.5f, 1.0f); f->glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
f3 = context()->versionFunctions<QOpenGLFunctions_3_3_Core>(); f3 = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_3_3_Core>(context());
if(f3 == nullptr) if(f3 == nullptr)
QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed.")); QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed."));
@@ -469,8 +479,8 @@ void ImageWidget::initializeGL()
// f->glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(float)*4, 0); // f->glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(float)*4, 0);
m_program = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram); m_program = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/image.vert"); m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/image.vert");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/image.frag"); m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/image.frag");
if(!m_program->link()) if(!m_program->link())
{ {
@@ -486,8 +496,8 @@ void ImageWidget::initializeGL()
m_program->setUniformValue("scale", 1.0f, 0.0f); m_program->setUniformValue("scale", 1.0f, 0.0f);
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram); m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/debayer.vert"); m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/debayer.vert");
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/debayer.frag"); m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/debayer.frag");
m_debayerProgram->bind(); m_debayerProgram->bind();
m_debayerProgram->enableAttributeArray("qt_Vertex"); m_debayerProgram->enableAttributeArray("qt_Vertex");
@@ -503,8 +513,8 @@ void ImageWidget::initializeGL()
m_vaoThumb->bind(); m_vaoThumb->bind();
m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram); m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/thumb.vert"); m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/thumb.vert");
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/thumb.frag"); m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/thumb.frag");
m_thumbnailProgram->bind(); m_thumbnailProgram->bind();
m_thumbnailProgram->enableAttributeArray("qt_Vertex"); m_thumbnailProgram->enableAttributeArray("qt_Vertex");
@@ -584,7 +594,7 @@ void ImageWidget::mousePressEvent(QMouseEvent *event)
else else
{ {
if(event->button() == Qt::LeftButton) if(event->button() == Qt::LeftButton)
m_lastPos = event->localPos(); m_lastPos = event->position();
} }
} }
@@ -596,8 +606,8 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
} }
else if(!m_lastPos.isNull()) else if(!m_lastPos.isNull())
{ {
QPointF off = event->localPos() - m_lastPos; QPointF off = event->position() - m_lastPos;
m_lastPos = event->localPos(); m_lastPos = event->position();
setOffset(m_dx - off.x(), m_dy - off.y()); setOffset(m_dx - off.x(), m_dy - off.y());
return; return;
} }
@@ -605,7 +615,7 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
if(!m_showThumbnails && m_rawImage) if(!m_showThumbnails && m_rawImage)
{ {
QVector2D pix = getImagePixelCoord(QVector2D(event->pos())); QVector2D pix = getImagePixelCoord(QVector2D(event->pos()));
QVector3D rgb; double r,g,b;
SkyPoint sky; SkyPoint sky;
if(m_wcs) if(m_wcs)
@@ -613,12 +623,12 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
m_wcs->pixelToWorld(QPointF(pix.x(), pix.y()), sky); 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) 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 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());
} }
} }
} }
@@ -663,7 +673,7 @@ void ImageWidget::wheelEvent(QWheelEvent *event)
else else
{ {
if(std::abs(event->angleDelta().y()) > 15) if(std::abs(event->angleDelta().y()) > 15)
zoom(event->angleDelta().y(), event->modifiers() & Qt::ShiftModifier ? QPointF() : event->posF()); zoom(event->angleDelta().y(), event->modifiers() & Qt::ShiftModifier ? QPointF() : event->position());
} }
} }
@@ -706,6 +716,7 @@ void ImageWidget::debayer()
f->glViewport(0, 0, m_imgWidth, m_imgHeight); f->glViewport(0, 0, m_imgWidth, m_imgHeight);
m_debayerProgram->bind(); m_debayerProgram->bind();
f->glUniform2i(m_debayerProgram->uniformLocation("firstRed"), m_firstRed[0], m_firstRed[1]);
m_image->bind(0); m_image->bind(0);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
@@ -716,19 +727,6 @@ void ImageWidget::debayer()
f->glGenerateMipmap(GL_TEXTURE_2D); f->glGenerateMipmap(GL_TEXTURE_2D);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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() void ImageWidget::updateScrollBars()
+28 -25
View File
@@ -14,6 +14,7 @@
#include "rawimage.h" #include "rawimage.h"
#include "imageringlist.h" #include "imageringlist.h"
#include "database.h" #include "database.h"
#include "stretchtoolbar.h"
struct ImageThumb struct ImageThumb
{ {
@@ -27,9 +28,9 @@ struct ImageThumb
class ImageWidget : public QOpenGLWidget class ImageWidget : public QOpenGLWidget
{ {
Q_OBJECT Q_OBJECT
QOpenGLFunctions *f; QOpenGLFunctions *f = nullptr;
QOpenGLFunctions_3_3_Core *f3; QOpenGLFunctions_3_3_Core *f3 = nullptr;
QTimer *m_updateTimer; QTimer *m_updateTimer = nullptr;
std::unique_ptr<QOpenGLShaderProgram> m_program; std::unique_ptr<QOpenGLShaderProgram> m_program;
std::unique_ptr<QOpenGLShaderProgram> m_thumbnailProgram; std::unique_ptr<QOpenGLShaderProgram> m_thumbnailProgram;
std::unique_ptr<QOpenGLShaderProgram> m_debayerProgram; std::unique_ptr<QOpenGLShaderProgram> m_debayerProgram;
@@ -44,31 +45,31 @@ class ImageWidget : public QOpenGLWidget
std::shared_ptr<RawImage> m_rawImage; std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs; std::shared_ptr<WCSData> m_wcs;
int m_width, m_height; int m_width, m_height;
int m_imgWidth, m_imgHeight; int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg; int m_currentImg = 0;
float m_low; MTFParam m_mtfParams;
float m_mid; float m_unit_scale[2] = {1.0f, 0.0f}; // scale and offset
float m_high; float m_dx = 0, m_dy = 0;
float m_range; float m_scale = 1.0f;
float m_dx, m_dy;
float m_scale;
int m_scaleStop = 0; int m_scaleStop = 0;
bool m_bestFit = false; bool m_bestFit = false;
float m_whiteBalance[3] = {1.0f, 1.0f, 1.0f}; bool m_blockRepaint = false;
bool m_blockRepaint; bool m_bwImg = false;
bool m_bwImg; bool m_falseColor = false;
bool m_invert; bool m_invert = false;
bool m_superpixel; bool m_superpixel = false;
bool m_showThumbnails; bool m_showThumbnails = false;
bool m_selecting; bool m_selecting = false;
bool m_sizesDirty; bool m_sizesDirty = false;
bool m_srgb; bool m_srgb = false;
int m_thumbnailCount; int m_thumbnailCount = 0;
int m_maxTextureSize; int m_maxTextureSize = 0;
int m_maxArrayLayers; int m_maxArrayLayers = 0;
int m_firstRed[2] = {0, 0};
QVector<ImageThumb> m_thumnails; QVector<ImageThumb> m_thumnails;
Database *m_database; Database *m_database = nullptr;
QPointF m_lastPos; QPointF m_lastPos;
QString m_error;
public: public:
explicit ImageWidget(Database *database, QWidget *parent = nullptr); explicit ImageWidget(Database *database, QWidget *parent = nullptr);
~ImageWidget() override; ~ImageWidget() override;
@@ -80,11 +81,13 @@ public:
void blockRepaint(bool block); void blockRepaint(bool block);
void allocateThumbnails(const QStringList &paths); void allocateThumbnails(const QStringList &paths);
QVector2D getImagePixelCoord(const QVector2D &pos); QVector2D getImagePixelCoord(const QVector2D &pos);
void setBayerMask(int mask);
public slots: public slots:
void setMTFParams(float low, float mid, float high); void setMTFParams(const MTFParam &params);
void setOffset(float dx, float dy); void setOffset(float dx, float dy);
void superPixel(bool enable); void superPixel(bool enable);
void invert(bool enable); void invert(bool enable);
void falseColor(bool enable);
QImage renderToImage(); QImage renderToImage();
void thumbnailLoaded(const Image *image); void thumbnailLoaded(const Image *image);
void showThumbnail(bool enable); void showThumbnail(bool enable);
+2 -2
View File
@@ -1,5 +1,5 @@
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu) find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource) find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
+1 -1
Submodule libXISF updated: dafc26984e...033a34e248
+127 -130
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; 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>(); std::unique_ptr<LibRaw> raw = std::make_unique<LibRaw>();
raw->open_file(path.toLocal8Bit().data()); raw->open_file(path.toLocal8Bit().data());
raw->imgdata.params.half_size = true; raw->imgdata.params.half_size = true;
@@ -95,8 +92,7 @@ bool loadRAW(const QString path, ImageInfoData &info, RawImage **image)
if(raw->unpack()) if(raw->unpack())
return false; return false;
if(image)
{
libraw_rawdata_t rawdata = raw->imgdata.rawdata; libraw_rawdata_t rawdata = raw->imgdata.rawdata;
size_t size = rawdata.sizes.width*rawdata.sizes.height; size_t size = rawdata.sizes.width*rawdata.sizes.height;
@@ -115,17 +111,16 @@ bool loadRAW(const QString path, ImageInfoData &info, RawImage **image)
out[d++] = p; out[d++] = p;
} }
} }
*image = new RawImage(rawdata.sizes.width, rawdata.sizes.height, RawImage::UINT16); image = std::make_shared<RawImage>(rawdata.sizes.width, rawdata.sizes.height, 1, RawImage::UINT16);
memcpy((*image)->data(), &out[0], sizeof(uint16_t)*d); memcpy(image->data(), &out[0], sizeof(uint16_t)*d);
}
QString shutterSpeed = QString::number(raw->imgdata.other.shutter); QString shutterSpeed = QString::number(raw->imgdata.other.shutter);
if(raw->imgdata.other.shutter < 1) if(raw->imgdata.other.shutter < 1)
{ {
shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter); shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter);
} }
//info.append(StringPair(QObject::tr("Width"), QString::number(rawImg->width))); info.info.append({QObject::tr("Width"), QString::number(raw->imgdata.sizes.width)});
//info.append(StringPair(QObject::tr("Height"), QString::number(rawImg->height))); 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("ISO"), QString::number(raw->imgdata.other.iso_speed)});
info.info.append({QObject::tr("Shutter speed"), shutterSpeed}); info.info.append({QObject::tr("Shutter speed"), shutterSpeed});
#if LIBRAW_MINOR_VERSION>=19 #if LIBRAW_MINOR_VERSION>=19
@@ -191,11 +186,8 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
return status; 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; fitsfile *file;
int status = 0; int status = 0;
int type; int type;
@@ -212,28 +204,35 @@ bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
if(naxis >= 2 && naxis <= 3 && status == 0) if(naxis >= 2 && naxis <= 3 && status == 0)
{ {
int cvtype; RawImage::DataType type;
int fitstype; int fitstype;
std::vector<cv::Mat> cvimg;
long fpixel[3] = {1,1,1}; long fpixel[3] = {1,1,1};
switch(imgtype) switch(imgtype)
{ {
case BYTE_IMG: case BYTE_IMG:
cvtype = CV_8U; type = RawImage::UINT8;
fitstype = TBYTE; fitstype = TBYTE;
break; break;
case SHORT_IMG: case SHORT_IMG:
cvtype = CV_16S; type = RawImage::UINT16;
fitstype = TSHORT; fitstype = TSHORT;
break; break;
case USHORT_IMG: case USHORT_IMG:
cvtype = CV_16U; type = RawImage::UINT16;
fitstype = TUSHORT; fitstype = TUSHORT;
break; break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG: case FLOAT_IMG:
cvtype = CV_32F; type = RawImage::FLOAT32;
fitstype = TFLOAT; fitstype = TFLOAT;
break; break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default: default:
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")}); info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
goto noload; goto noload;
@@ -247,32 +246,43 @@ bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
info.info.append({QObject::tr("Width"), QString::number(naxes[0])}); info.info.append({QObject::tr("Width"), QString::number(naxes[0])});
info.info.append({QObject::tr("Height"), QString::number(naxes[1])}); 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++) for (int i=1; i==1 || i<=naxes[2]; i++)
{ {
cv::Mat tmp(h, w, cvtype);
fpixel[2] = i; fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, tmp.ptr(), NULL, &status); fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
if(cvtype == CV_16S) }
tmp.convertTo(tmp, CV_16U, 1, 32767); if(fitstype == TSHORT)
cvimg.push_back(tmp); {
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) if(img.channels() == 1)
{ image = std::make_shared<RawImage>(std::move(img));
*image = new RawImage(cvimg[0]); else
} image = RawImage::fromPlanar(img);
if(cvimg.size() == 3)
{ if(image)
cv::Mat rgb; image->convertToGLFormat();
cv::merge(cvimg, rgb);
*image = new RawImage(rgb);
}
} }
} }
noload: noload:
if(file) if(file)
loadFITSHeader(file, info); loadFITSHeader(file, info);
if(image)
{
for(auto fits : info.fitsHeader)
{
if(fits.key == "ROWORDER" && fits.value == "BOTTOM-UP")
image->flip();
}
}
fits_close_file(file, &status); fits_close_file(file, &status);
if(status) if(status)
{ {
@@ -285,7 +295,7 @@ bool loadFITS(const QString path, ImageInfoData &info, RawImage **image)
return true; return true;
} }
bool loadXISF(const QString &path, ImageInfoData &info, RawImage **image) bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
{ {
try try
{ {
@@ -310,52 +320,34 @@ bool loadXISF(const QString &path, ImageInfoData &info, RawImage **image)
info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())}); info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())});
if(!info.wcs->valid())info.wcs.reset(); if(!info.wcs->valid())info.wcs.reset();
if(xisfImage.channelCount() == 1) RawImage::DataType type;
{
switch(xisfImage.sampleFormat()) switch(xisfImage.sampleFormat())
{ {
case LibXISF::Image::UInt8: case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
*image = new RawImage(xisfImage.width(), xisfImage.height(), RawImage::UINT8); case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
std::memcpy((*image)->data(), xisfImage.imageData(), xisfImage.imageDataSize()); case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
break; case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::UInt16: case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
*image = new RawImage(xisfImage.width(), xisfImage.height(), RawImage::UINT16); default: break;
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;
} }
}
else if(xisfImage.channelCount() == 3)
{
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Normal);
switch(tmpImage.sampleFormat()) LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{ {
case LibXISF::Image::UInt8: image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
*image = new RawImage(tmpImage.width(), tmpImage.height(), RawImage::UINT8C3); std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
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;
} }
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
} }
if(*image) if(image)
{
image->convertToGLFormat();
return true; return true;
} }
}
catch (LibXISF::Error &err) catch (LibXISF::Error &err)
{ {
info.info.append(QPair<QString, QString>("Error", err.what())); info.info.append(QPair<QString, QString>("Error", err.what()));
@@ -370,7 +362,6 @@ void LoadRunable::run()
{ {
try try
{ {
if(!m_thumbnail && !m_receiver->isCurrent()) if(!m_thumbnail && !m_receiver->isCurrent())
{ {
return; return;
@@ -380,23 +371,21 @@ void LoadRunable::run()
QFileInfo finfo(m_file); QFileInfo finfo(m_file);
info.info.append({QObject::tr("Filename"), finfo.fileName()}); info.info.append({QObject::tr("Filename"), finfo.fileName()});
RawImage *rawImage = nullptr; std::shared_ptr<RawImage> rawImage;
bool raw = false;
timer.start(); timer.start();
if(m_file.endsWith(".CR2", Qt::CaseInsensitive) || m_file.endsWith(".NEF", Qt::CaseInsensitive) || m_file.endsWith(".DNG", Qt::CaseInsensitive)) if(m_file.endsWith(".CR2", Qt::CaseInsensitive) || m_file.endsWith(".CR3", Qt::CaseInsensitive) || m_file.endsWith(".NEF", Qt::CaseInsensitive) || m_file.endsWith(".DNG", Qt::CaseInsensitive))
{ {
loadRAW(m_file, info, &rawImage); loadRAW(m_file, info, rawImage);
raw = true;
qDebug() << "LoadRAW" << timer.elapsed(); qDebug() << "LoadRAW" << timer.elapsed();
} }
else if(m_file.endsWith(".FIT", Qt::CaseInsensitive) || m_file.endsWith(".FITS", Qt::CaseInsensitive)) 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(); qDebug() << "LoadFITS" << timer.elapsed();
} }
else if(m_file.endsWith(".XISF", Qt::CaseInsensitive)) else if(m_file.endsWith(".XISF", Qt::CaseInsensitive))
{ {
loadXISF(m_file, info, &rawImage); loadXISF(m_file, info, rawImage);
qDebug() << "LoadXISF" << timer.elapsed(); qDebug() << "LoadXISF" << timer.elapsed();
} }
else else
@@ -416,34 +405,43 @@ void LoadRunable::run()
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE); loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
exif_data_free(exif); exif_data_free(exif);
} }
rawImage = new RawImage(img); rawImage = std::make_shared<RawImage>(img);
qDebug() << "LoadQImage" << timer.elapsed(); 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(); timer.start();
rawImage->imageStats(&mean, &stdDev, &median, &min, &max, &mad, &saturated); rawImage->calcStats();
const RawImage::Stats &stats = rawImage->imageStats();
qDebug() << "image stats" << timer.restart(); qDebug() << "image stats" << timer.restart();
info.info.append({QObject::tr("Mean"), QString::number(mean)}); if(rawImage->channels() == 1)
info.info.append({QObject::tr("Standart deviation"), QString::number(stdDev)}); {
info.info.append({QObject::tr("Median"), QString::number(median)}); info.info.append({QObject::tr("Mean"), QString::number(stats.m_mean[0])});
info.info.append({QObject::tr("Minimum"), QString::number(min)}); info.info.append({QObject::tr("Standart deviation"), QString::number(stats.m_stdDev[0])});
info.info.append({QObject::tr("Maximum"), QString::number(max)}); info.info.append({QObject::tr("Median"), QString::number(stats.m_median[0])});
info.info.append({QObject::tr("MAD"), QString::number(mad)}); info.info.append({QObject::tr("Minimum"), QString::number(stats.m_min[0])});
info.info.append({QObject::tr("Saturated"), QString::number(100.0 * saturated / rawImage->size()) + "%"}); 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) if(m_analyzeLevel >= Peaks)
{ {
std::vector<Peak> peaks; std::vector<Peak> peaks;
if(raw) { /*RawImage *medianImage = rawImage->medianFilter();
rawImage->quarter();
qDebug() << "quarter" << timer.restart();
}
RawImage *medianImage = rawImage->medianFilter();
qDebug() << "median" << timer.restart(); qDebug() << "median" << timer.restart();
int numPeaks = medianImage->findPeaks(median+stdDev*2, 20, peaks); int numPeaks = medianImage->findPeaks(median+stdDev*2, 20, peaks);
delete medianImage; delete medianImage;
@@ -483,24 +481,25 @@ void LoadRunable::run()
info.info.append({QObject::tr("FWHM X"), QString::number(fwhmX/stars.size())}); info.info.append({QObject::tr("FWHM X"), QString::number(fwhmX/stars.size())});
info.info.append({QObject::tr("FWHM Y"), QString::number(fwhmY/stars.size())}); info.info.append({QObject::tr("FWHM Y"), QString::number(fwhmY/stars.size())});
} }
qDebug() << "Star fit" << timer.restart(); qDebug() << "Star fit" << timer.restart();*/
} }
} }
if(m_thumbnail) if(m_thumbnail)
{ {
if(rawImage) if(rawImage && rawImage->valid())
{ {
if(QUALITY_RESIZE)
rawImage->resize(THUMB_SIZE, THUMB_SIZE);
rawImage->convertToThumbnail(); 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 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) catch(std::exception e)
{ {
@@ -559,47 +558,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"}; static QStringList skipKeys = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "BZERO", "BSCALE", "EXTEND"};
int status = 0; int status = 0;
long firstpix[3] = {1,1,1}; long firstpix[3] = {1,1,1};
int channels = rawimage->mat().channels(); int channels = rawimage->channels();
int naxis = channels == 1 ? 2 : 3; 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) if(channels == 1)
mat.push_back(rawimage->mat()); planes.push_back(*rawimage);
else 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); fits_create_img(fw, BYTE_IMG, naxis, naxes, &status);
for(int i=0; i<channels; i++) for(int i=0; i<channels; i++)
{ {
firstpix[2] = i+1; 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; break;
case CV_16U: case RawImage::UINT16:
fits_create_img(fw, USHORT_IMG, naxis, naxes, &status); fits_create_img(fw, USHORT_IMG, naxis, naxes, &status);
for(int i=0; i<channels; i++) for(int i=0; i<channels; i++)
{ {
firstpix[2] = i+1; 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; break;
case CV_32F: case RawImage::FLOAT32:
fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status); fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status);
for(int i=0; i<channels; i++) for(int i=0; i<channels; i++)
{ {
firstpix[2] = i+1; 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; break;
} }
@@ -632,11 +631,11 @@ void writeFITSImage(fitsfile *fw, RawImage *rawimage, ImageInfoData &imageinfo)
void ConvertRunable::run() void ConvertRunable::run()
{ {
ImageInfoData imageinfo; ImageInfoData imageinfo;
RawImage *rawimage = nullptr; std::shared_ptr<RawImage> rawimage;
if(m_infile.endsWith(".FITS", Qt::CaseInsensitive) || m_infile.endsWith(".FIT", Qt::CaseInsensitive)) 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)) if(m_infile.endsWith(".XISF", Qt::CaseInsensitive))
loadXISF(m_infile, imageinfo, &rawimage); loadXISF(m_infile, imageinfo, rawimage);
if(rawimage) if(rawimage)
{ {
@@ -645,13 +644,13 @@ void ConvertRunable::run()
try try
{ {
LibXISF::XISFWriter xisf; LibXISF::XISFWriter xisf;
int channelCount = rawimage->mat().channels(); int channelCount = rawimage->channels();
LibXISF::Image::SampleFormat sampleFormat; LibXISF::Image::SampleFormat sampleFormat;
switch(CV_MAT_DEPTH(rawimage->dataType())) switch(rawimage->type())
{ {
case CV_8U: sampleFormat = LibXISF::Image::UInt8; break; case RawImage::UINT8: sampleFormat = LibXISF::Image::UInt8; break;
case CV_16U: sampleFormat = LibXISF::Image::UInt16; break; case RawImage::UINT16: sampleFormat = LibXISF::Image::UInt16; break;
case CV_32F: sampleFormat = LibXISF::Image::Float32; break; case RawImage::FLOAT32: sampleFormat = LibXISF::Image::Float32; break;
default: return; default: return;
} }
@@ -671,7 +670,6 @@ void ConvertRunable::run()
catch(LibXISF::Error &err) catch(LibXISF::Error &err)
{ {
qDebug() << "Failed to save XISF image" << err.what(); qDebug() << "Failed to save XISF image" << err.what();
delete rawimage;
} }
} }
@@ -684,6 +682,5 @@ void ConvertRunable::run()
writeFITSImage(fw, rawimage, imageinfo); writeFITSImage(fw, rawimage, imageinfo);
fits_close_file(fw, &status); fits_close_file(fw, &status);
} }
delete rawimage;
} }
} }
+59 -5
View File
@@ -10,6 +10,7 @@
#include <QProgressDialog> #include <QProgressDialog>
#include <QDebug> #include <QDebug>
#include <QDockWidget> #include <QDockWidget>
#include <QActionGroup>
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
#include <QSettings> #include <QSettings>
@@ -23,6 +24,8 @@
#include "about.h" #include "about.h"
#include "statusbar.h" #include "statusbar.h"
#include "settingsdialog.h" #include "settingsdialog.h"
#include "histogram.h"
#include "batchprocessing.h"
#ifdef __linux__ #ifdef __linux__
#include <sys/ioctl.h> #include <sys/ioctl.h>
@@ -37,7 +40,7 @@ int MainWindow::socketPair[2] = {0, 0};
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{ {
qRegisterMetaType<ImageInfoData>("ImageInfoData"); qRegisterMetaType<ImageInfoData>("ImageInfoData");
qRegisterMetaType<RawImage*>("RawImage"); qRegisterMetaType<std::shared_ptr<RawImage>>("std::shared_ptr<RawImage>");
SettingsDialog::loadSettings(); SettingsDialog::loadSettings();
@@ -56,9 +59,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
_openFilter.append(" "); _openFilter.append(" ");
nameFilter.append(mimeType.suffixes()); nameFilter.append(mimeType.suffixes());
} }
_openFilter.append("*.fit *.fits *.xisf *.cr2 *.nef *.dng)"); _openFilter.append("*.fit *.fits *.xisf *.cr2 *.cr3 *.nef *.dng)");
_openFilter.append(tr(";;All files (*)")); _openFilter.append(tr(";;All files (*)"));
nameFilter.append({"fit", "fits", "xisf", "cr2", "nef", "dng"}); nameFilter.append({"fit", "fits", "xisf", "cr2", "cr3", "nef", "dng"});
QImageReader::setAllocationLimit(0);
m_info = new ImageInfo(this); m_info = new ImageInfo(this);
QDockWidget *infoDock = new QDockWidget(tr("Image info"), this); QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
@@ -80,10 +84,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_imageGL->imageWidget(), &ImageWidget::status, statusBar, &StatusBar::newStatus); connect(m_imageGL->imageWidget(), &ImageWidget::status, statusBar, &StatusBar::newStatus);
m_stretchPanel = new StretchToolbar(this); 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::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); });
connect(m_stretchPanel, &StretchToolbar::invert, m_imageGL->imageWidget(), &ImageWidget::invert); connect(m_stretchPanel, &StretchToolbar::invert, m_imageGL->imageWidget(), &ImageWidget::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_imageGL->imageWidget(), &ImageWidget::superPixel); 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_ringList = new ImageRingList(m_database, nameFilter, this);
m_filesystem = new FilesystemWidget(m_ringList, this); m_filesystem = new FilesystemWidget(m_ringList, this);
@@ -119,6 +124,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
databaseViewDock->hide(); databaseViewDock->hide();
addDockWidget(Qt::LeftDockWidgetArea, filetreeDock); 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")); setWindowTitle(tr("Tenmon"));
connect(m_ringList, SIGNAL(pixmapLoaded(Image*)), this, SLOT(pixmapLoaded(Image*))); connect(m_ringList, SIGNAL(pixmapLoaded(Image*)), this, SLOT(pixmapLoaded(Image*)));
@@ -127,6 +139,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_ringList, SIGNAL(currentImageChanged(int)), m_filesystem, SLOT(selectFile(int))); 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::thumbnailLoaded, m_imageGL->imageWidget(), &ImageWidget::thumbnailLoaded);
connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded); 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)); connect(m_imageGL->imageWidget(), &ImageWidget::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
QMenu *fileMenu = new QMenu(tr("File"), this); QMenu *fileMenu = new QMenu(tr("File"), this);
@@ -140,6 +153,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir())); fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir()));
fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex())); fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex()));
fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV); fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV);
/*fileMenu->addAction(tr("Batch processing"), [this](){
BatchProcessing *batchProcessing = new BatchProcessing(this);
batchProcessing->exec();
delete batchProcessing;
}, Qt::Key_B | Qt::CTRL);*/
fileMenu->addSeparator(); fileMenu->addSeparator();
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool))); QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool)));
liveModeAction->setCheckable(true); liveModeAction->setCheckable(true);
@@ -156,6 +174,26 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
viewMenu->addAction(tr("Zoom Out"), m_imageGL, SLOT(zoomOut()), QKeySequence::ZoomOut); viewMenu->addAction(tr("Zoom Out"), m_imageGL, SLOT(zoomOut()), QKeySequence::ZoomOut);
viewMenu->addAction(tr("Best Fit"), m_imageGL, SLOT(bestFit()), QKeySequence("Ctrl+1")); viewMenu->addAction(tr("Best Fit"), m_imageGL, SLOT(bestFit()), QKeySequence("Ctrl+1"));
viewMenu->addAction(tr("100%"), m_imageGL, SLOT(oneToOne())); viewMenu->addAction(tr("100%"), m_imageGL, SLOT(oneToOne()));
viewMenu->addSeparator();
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
QActionGroup *bayerActionGroup = new QActionGroup(this);
QAction *rggbAction = bayerActionGroup->addAction(tr("RGGB"));//0 0
QAction *grbgAction = bayerActionGroup->addAction(tr("GRBG"));//1 0
QAction *gbrgAction = bayerActionGroup->addAction(tr("GBRG"));//0 1
QAction *bggrAction = bayerActionGroup->addAction(tr("BGGR"));//1 1
rggbAction->setCheckable(true); rggbAction->setData(0);
grbgAction->setCheckable(true); grbgAction->setData(1);
gbrgAction->setCheckable(true); gbrgAction->setData(2);
bggrAction->setCheckable(true); bggrAction->setData(3);
bayerMenu->addActions({rggbAction, grbgAction, gbrgAction, bggrAction});
viewMenu->addMenu(bayerMenu);
connect(bayerActionGroup, &QActionGroup::triggered, [this](QAction *action){
int data = action->data().toInt();
m_imageGL->imageWidget()->setBayerMask(data);
QSettings settings;
settings.setValue("mainwindow/bayermask", data);
});
QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), [this](bool checked){ QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), [this](bool checked){
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails(); if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
m_imageGL->imageWidget()->allocateThumbnails(m_ringList->imageNames()); m_imageGL->imageWidget()->allocateThumbnails(m_ringList->imageNames());
@@ -164,6 +202,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
else m_ringList->stopLoading(); else m_ringList->stopLoading();
}, Qt::Key_F2); }, Qt::Key_F2);
thumbnailsAction->setCheckable(true); thumbnailsAction->setCheckable(true);
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), m_ringList, &ImageRingList::toggleSlideshow, Qt::Key_F3);
slideshowAction->setCheckable(true);
menuBar()->addMenu(viewMenu); menuBar()->addMenu(viewMenu);
QMenu *selectMenu = new QMenu(tr("Select"), this); QMenu *selectMenu = new QMenu(tr("Select"), this);
@@ -198,7 +238,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(peakAction, SIGNAL(toggled(bool)), this, SLOT(peakFinder(bool))); connect(peakAction, SIGNAL(toggled(bool)), this, SLOT(peakFinder(bool)));
connect(starAction, SIGNAL(toggled(bool)), this, SLOT(starFinder(bool))); connect(starAction, SIGNAL(toggled(bool)), this, SLOT(starFinder(bool)));
analyzeMenu->addActions({statsAction, peakAction, starAction}); analyzeMenu->addActions({statsAction, peakAction, starAction});
menuBar()->addMenu(analyzeMenu); //menuBar()->addMenu(analyzeMenu);
QMenu *dockMenu = new QMenu(tr("Docks"), this); QMenu *dockMenu = new QMenu(tr("Docks"), this);
dockMenu->addAction(infoDock->toggleViewAction()); dockMenu->addAction(infoDock->toggleViewAction());
@@ -206,6 +246,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
dockMenu->addAction(filesystemDock->toggleViewAction()); dockMenu->addAction(filesystemDock->toggleViewAction());
dockMenu->addAction(databaseViewDock->toggleViewAction()); dockMenu->addAction(databaseViewDock->toggleViewAction());
dockMenu->addAction(filetreeDock->toggleViewAction()); dockMenu->addAction(filetreeDock->toggleViewAction());
dockMenu->addAction(histogramDock->toggleViewAction());
menuBar()->addMenu(dockMenu); menuBar()->addMenu(dockMenu);
QMenu *helpMenu = menuBar()->addMenu(tr("Help")); QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
@@ -217,6 +258,19 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
QSettings settings; QSettings settings;
restoreGeometry(settings.value("mainwindow/geometry").toByteArray()); restoreGeometry(settings.value("mainwindow/geometry").toByteArray());
restoreState(settings.value("mainwindow/state").toByteArray()); restoreState(settings.value("mainwindow/state").toByteArray());
int bayermask = settings.value("mainwindow/bayermask", 0).toInt();
switch(bayermask)
{
default:
case 0:
rggbAction->setChecked(true); break;
case 1:
grbgAction->setChecked(true); break;
case 2:
gbrgAction->setChecked(true); break;
case 3:
bggrAction->setChecked(true); break;
}
QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
if(standardLocations.size()) if(standardLocations.size())
+597 -238
View File
File diff suppressed because it is too large Load Diff
+54 -31
View File
@@ -3,16 +3,16 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <memory>
#include <stdint.h> #include <stdint.h>
#include <math.h> #include <math.h>
#include <memory.h> #include <memory.h>
#include <opencv2/imgproc.hpp>
#include <QImage> #include <QImage>
#include <QVector3D>
extern int THUMB_SIZE; extern int THUMB_SIZE;
extern int THUMB_SIZE_BORDER; extern int THUMB_SIZE_BORDER;
extern int THUMB_SIZE_BORDER_Y; extern int THUMB_SIZE_BORDER_Y;
extern bool QUALITY_RESIZE;
class Peak class Peak
{ {
@@ -38,55 +38,78 @@ public:
class RawImage class RawImage
{ {
protected: using PixelType = uint8_t;
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;
public: public:
enum ImgType enum DataType
{ {
UINT8, UINT8,
UINT16, UINT16,
UINT32,
FLOAT32, FLOAT32,
UINT8C3, FLOAT64,
UINT8C4,
UINT16C3,
UINT16C4,
FLOAT32C3,
UNKNOWN,
}; };
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();
RawImage(int w, int h, ImgType type); RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
RawImage(cv::Mat &img);
RawImage(const RawImage &d); RawImage(const RawImage &d);
RawImage(RawImage &&d);
RawImage(const QImage &img); 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 calcStats();
void rect(int &x, int &y, int w, int h, std::vector<double> &r) const; 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; int findPeaks(double background, double distance, std::vector<Peak> &peaks) const;
RawImage* medianFilter() const;
void quarter();
uint32_t width() const; uint32_t width() const;
uint32_t height() const; uint32_t height() const;
uint32_t channels() const;
uint32_t size() const; uint32_t size() const;
ImgType type() const; DataType type() const;
int dataType() const;
uint32_t norm() const; uint32_t norm() const;
void* data(); void* data();
const void* data() const; 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 convertToThumbnail();
void convertToGLFormat();
float thumbAspect() const; float thumbAspect() const;
const cv::Mat& mat() const; bool pixel(int x, int y, double &r, double &g, double &b) const;
bool pixel(int x, int y, QVector3D &rgb) const; void resize(uint32_t w, uint32_t h);
void scaleToUnit(); std::pair<float, float> unitScale() const;
void downscaleTo(uint32_t size); void flip();
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;
bool valid() const;
}; };
//Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
//Q_DECLARE_METATYPE(std::shared_ptr<RawImage>);
#endif // RAWIMAGE_H #endif // RAWIMAGE_H
+115
View File
@@ -0,0 +1,115 @@
#include "rawimage.h"
#ifdef __SSE2__
#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);
#endif
-30
View File
@@ -1,30 +0,0 @@
<RCC>
<qresource prefix="/">
<file>invert.png</file>
<file>nuke.png</file>
<file>bayer.png</file>
<file>space.nouspiro.tenmon.png</file>
<file>nuke_a.png</file>
<file>about/tenmon</file>
<file>translations/tenmon_en.qm</file>
<file>translations/tenmon_sk.qm</file>
<file>about/filter.png</file>
<file>about/stretch-panel.png</file>
<file>translations/tenmon_fr.qm</file>
<file>shaders/image.frag</file>
<file>shaders/image.vert</file>
<file>shaders/thumb.frag</file>
<file>shaders/thumb.vert</file>
<file>shaders/debayer.frag</file>
<file>shaders/debayer.vert</file>
</qresource>
<qresource lang="en" prefix="/">
<file alias="help">about/help_en</file>
</qresource>
<qresource lang="sk" prefix="/">
<file alias="help">about/help_sk</file>
</qresource>
<qresource lang="fr" prefix="/">
<file alias="help">about/help_fr</file>
</qresource>
</RCC>
View File

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File
View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 454 B

+26
View File
@@ -0,0 +1,26 @@
<RCC>
<qresource prefix="/">
<file>invert.png</file>
<file>nuke.png</file>
<file>bayer.png</file>
<file>space.nouspiro.tenmon.png</file>
<file>nuke_a.png</file>
<file>../about/tenmon</file>
<file>../translations/tenmon_en.qm</file>
<file>../translations/tenmon_sk.qm</file>
<file>../about/filter.png</file>
<file>../about/stretch-panel.png</file>
<file>../translations/tenmon_fr.qm</file>
<file>falsecolor.png</file>
<file>link.png</file>
</qresource>
<qresource lang="en" prefix="/">
<file alias="help">../about/help_en</file>
</qresource>
<qresource lang="sk" prefix="/">
<file alias="help">../about/help_sk</file>
</qresource>
<qresource lang="fr" prefix="/">
<file alias="help">../about/help_fr</file>
</qresource>
</RCC>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

+263
View File
@@ -0,0 +1,263 @@
#include "scriptengine.h"
#include <QDir>
#include <QFileInfo>
#include <QDebug>
#include "loadrunable.h"
namespace Script
{
ScriptEngine::ScriptEngine(QObject *parent) : QObject(parent)
, _jsEngine(new QJSEngine(this))
, _database(new Database(this))
{
QJSValue engine = _jsEngine->newQObject(this);
_jsEngine->globalObject().setProperty("engine", engine);
_database->init(QLatin1String("scriptengine"));
}
void ScriptEngine::setParams(const QString &scriptPath, const QStringList &paths, const QString &outputDir)
{
_scriptPath = scriptPath;
_paths = paths;
_outputDir = outputDir + "/";
}
void ScriptEngine::reportError(const QString &message)
{
_jsEngine->throwError(message);
}
const QString &ScriptEngine::outputDir() const
{
return _outputDir;
}
void ScriptEngine::interrupt()
{
_jsEngine->setInterrupted(true);
}
void ScriptEngine::logError(const QString &message)
{
emit newMessage(message, true);
}
void ScriptEngine::log(const QString &message)
{
emit newMessage(message, false);
}
void ScriptEngine::mark(File *file)
{
_database->mark(file->absoluteFilePath());
}
void ScriptEngine::unmark(File *file)
{
_database->unmark(file->absoluteFilePath());
}
bool ScriptEngine::isMarked(const File *file) const
{
return _database->isMarked(file->absoluteFilePath());
}
void ScriptEngine::run()
{
QJSValue jsPaths = _jsEngine->newArray(_paths.size());
for(qsizetype i=0; i<_paths.size(); i++)
jsPaths.setProperty(i, _jsEngine->newQObject(new File(_paths[i], this)));
_jsEngine->globalObject().setProperty("files", jsPaths);
QFile scriptFile(_scriptPath);
if(!scriptFile.open(QIODevice::ReadOnly))
return;
QTextStream stream(&scriptFile);
QString contents = stream.readAll();
scriptFile.close();
QJSValue result = _jsEngine->evaluate(contents, _scriptPath);
qDebug() << result.isError() << result.toString();
if(result.isError())
{
QString error = result.property("name").toString() + " on line " + result.property("lineNumber").toString() + " : " + result.toString();
error += "\n" + result.property("stack").toString();
emit newMessage(error, true);
}
emit finished();
}
void File::loadFitsKeywords()
{
if(!_fitsKeywordsLoaded)
{
_fitsKeywordsLoaded = true;
ImageInfoData info;
if(suffix() == "xisf")
{
readXISFHeader(_path, info);
}
else if(suffix() == "fits")
{
readFITSHeader(_path, info);
}
else return;
for(const FITSRecord &record : info.fitsHeader)
{
_fitsKeywords[record.key] = record.value.toString();
}
}
}
bool File::mkpath(const QString &path) const
{
QFileInfo info(path);
if(!info.isRelative())
{
_engine->logError("Destination path is not relative");
return false;
}
QDir dir(_engine->outputDir());
if(dir.mkpath(info.path()))
{
return true;
}
else
{
_engine->logError("Failed to create dir " + info.path());
return false;
}
}
File::File(const QString &path, Script::ScriptEngine *engine) :
_engine(engine),
_path(path),
_info(path)
{
}
QString File::fileName() const
{
return _info.fileName();
}
QString File::absoluteFilePath() const
{
return _info.absoluteFilePath();
}
QString File::absolutePath() const
{
return _info.absolutePath();
}
QString File::baseName() const
{
return _info.baseName();
}
QString File::completeBaseName() const
{
return _info.completeBaseName();
}
QString File::suffix() const
{
return _info.suffix();
}
qint64 File::size() const
{
return _info.size();
}
QStringList File::fitsKeywords()
{
QThread::msleep(500);
loadFitsKeywords();
return _fitsKeywords.keys();
}
QString File::fitsValue(const QString &key)
{
loadFitsKeywords();
if(_fitsKeywords.contains(key))
return _fitsKeywords[key];
else
return QString();
}
bool File::isMarked() const
{
return _engine->isMarked(this);
}
bool File::copy(const QString &newpath) const
{
if(mkpath(newpath))
{
if(QFile::copy(_path, _engine->outputDir() + newpath))
return true;
_engine->logError("Failed copy to " + newpath);
return false;
}
return false;
}
bool File::move(const QString &newpath) const
{
if(mkpath(newpath))
{
if(QFile::rename(_path, _engine->outputDir() + newpath))
return true;
_engine->logError("Failed move to " + newpath);
return false;
}
return false;
}
bool File::convertTo(const QString &format)
{
_engine->reportError("Not implemented");
return false;
}
ScriptEngineThread::ScriptEngineThread(QObject *parent) : QObject(parent)
{
_thread = new QThread();
_thread->setObjectName("ScriptEngine");
_engine = new ScriptEngine;
_engine->moveToThread(_thread);
connect(_engine, &ScriptEngine::finished, _thread, &QThread::quit);
connect(_engine, &ScriptEngine::newMessage, this, &ScriptEngineThread::newMessage);
connect(_thread, &QThread::started, _engine, &ScriptEngine::run);
connect(_thread, &QThread::finished, _engine, &ScriptEngine::deleteLater);
connect(_thread, &QThread::finished, _thread, &QThread::deleteLater);
connect(_thread, &QThread::finished, this, &ScriptEngineThread::finished);
}
ScriptEngineThread::~ScriptEngineThread()
{
if(_engine)_engine->interrupt();
}
void ScriptEngineThread::setParams(const QString &scriptPath, const QStringList &paths, const QString &outputDir)
{
_engine->setParams(scriptPath, paths, outputDir);
}
void ScriptEngineThread::start()
{
_thread->start();
}
void ScriptEngineThread::interrupt()
{
if(_engine)_engine->interrupt();
}
}
+86
View File
@@ -0,0 +1,86 @@
#ifndef SCRIPTENGINE_H
#define SCRIPTENGINE_H
#include <QObject>
#include <QJSEngine>
#include <QFileInfo>
#include <QThread>
#include "database.h"
namespace Script
{
class File;
class ScriptEngine : public QObject
{
Q_OBJECT
QJSEngine *_jsEngine;
Database *_database;
QString _scriptPath;
QString _outputDir;
QStringList _paths;
public:
explicit ScriptEngine(QObject *parent = nullptr);
void setParams(const QString &scriptPath, const QStringList &paths, const QString &outputDir);
void reportError(const QString &message);
const QString& outputDir() const;
void interrupt();
void logError(const QString &message);
Q_INVOKABLE void log(const QString &message);
Q_INVOKABLE void mark(File *file);
Q_INVOKABLE void unmark(File *file);
Q_INVOKABLE bool isMarked(const File *file) const;
public slots:
void run();
signals:
void newMessage(const QString &message, bool error);
void finished();
};
class ScriptEngineThread : public QObject
{
Q_OBJECT
QThread *_thread;
ScriptEngine *_engine;
public:
ScriptEngineThread(QObject *parent = nullptr);
~ScriptEngineThread();
void setParams(const QString &scriptPath, const QStringList &paths, const QString &outputDir);
void start();
void interrupt();
signals:
void newMessage(const QString &message, bool error);
void finished();
};
class File : public QObject
{
Q_OBJECT
ScriptEngine *_engine;
QString _path;
QFileInfo _info;
bool _fitsKeywordsLoaded = false;
QMap<QString, QString> _fitsKeywords;
void loadFitsKeywords();
bool mkpath(const QString &path) const;
public:
explicit File(const QString &path, ScriptEngine *engine);
Q_INVOKABLE QString fileName() const;
Q_INVOKABLE QString absoluteFilePath() const;
Q_INVOKABLE QString absolutePath() const;
Q_INVOKABLE QString baseName() const;
Q_INVOKABLE QString completeBaseName() const;
Q_INVOKABLE QString suffix() const;
Q_INVOKABLE qint64 size() const;
Q_INVOKABLE QStringList fitsKeywords();
Q_INVOKABLE QString fitsValue(const QString &key);
Q_INVOKABLE bool isMarked() const;
Q_INVOKABLE bool copy(const QString &newpath) const;
Q_INVOKABLE bool move(const QString &newpath) const;
Q_INVOKABLE bool convertTo(const QString &format);
};
}
#endif // SCRIPTENGINE_H
+27 -1
View File
@@ -8,6 +8,7 @@
extern int DEFAULT_WIDTH; extern int DEFAULT_WIDTH;
extern double SATURATION; extern double SATURATION;
extern int FILTERING;
class EvenNumber : public QSpinBox class EvenNumber : public QSpinBox
{ {
@@ -39,7 +40,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
QSettings settings; QSettings settings;
m_preloadImages = new QSpinBox(this); m_preloadImages = new QSpinBox(this);
m_preloadImages->setRange(0, 8); m_preloadImages->setRange(0, 32);
m_preloadImages->setValue(settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt()); m_preloadImages->setValue(settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt());
m_preloadImages->setToolTip(tr("How many images are preloaded before and after current image.")); m_preloadImages->setToolTip(tr("How many images are preloaded before and after current image."));
@@ -56,12 +57,30 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
m_saturation->setValue(settings.value("settings/saturation", SATURATION * 100.0).toDouble()); m_saturation->setValue(settings.value("settings/saturation", SATURATION * 100.0).toDouble());
m_saturation->setToolTip(tr("Set threshold value that is considered saturated when showing statistics.\nFor RAW files you may set 22%")); m_saturation->setToolTip(tr("Set threshold value that is considered saturated when showing statistics.\nFor RAW files you may set 22%"));
m_slideShowTime = new QDoubleSpinBox(this);
m_slideShowTime->setMinimum(0.01);
m_slideShowTime->setMaximum(10);
m_slideShowTime->setSuffix(" s");
m_slideShowTime->setValue(settings.value("settings/slideshowtime", 1.0).toDouble());
m_slideShowTime->setSingleStep(0.1);
m_useNativeDialog = new QCheckBox(tr("Don't use native file dialog"), this); m_useNativeDialog = new QCheckBox(tr("Don't use native file dialog"), this);
m_useNativeDialog->setChecked(QApplication::testAttribute(Qt::AA_DontUseNativeDialogs)); m_useNativeDialog->setChecked(QApplication::testAttribute(Qt::AA_DontUseNativeDialogs));
m_filtering = new QComboBox(this);
m_filtering->addItems({tr("Nearest"), tr("Linear"), tr("Cubic")});
m_filtering->setCurrentIndex(FILTERING);
m_qualityThumbnail = new QCheckBox(tr("Smooth thumbnails"), this);
m_qualityThumbnail->setChecked(QUALITY_RESIZE);
m_qualityThumbnail->setToolTip(tr("Use box filter when downsampling thumbnails instead of nearest. Slightly slower."));
layout->addRow(tr("Image preload count"), m_preloadImages); layout->addRow(tr("Image preload count"), m_preloadImages);
layout->addRow(tr("Thumbnails size"), m_thumSize); layout->addRow(tr("Thumbnails size"), m_thumSize);
layout->addRow(tr("Saturation"), m_saturation); layout->addRow(tr("Saturation"), m_saturation);
layout->addRow(tr("Slideshow interval"), m_slideShowTime);
layout->addRow(tr("Image interpolation"), m_filtering);
layout->addRow(m_qualityThumbnail);
layout->addRow(m_useNativeDialog); layout->addRow(m_useNativeDialog);
//layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart."))); //layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart.")));
@@ -82,6 +101,8 @@ void SettingsDialog::loadSettings()
THUMB_SIZE_BORDER_Y = THUMB_SIZE + 30; THUMB_SIZE_BORDER_Y = THUMB_SIZE + 30;
DEFAULT_WIDTH = settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt(); DEFAULT_WIDTH = settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt();
SATURATION = settings.value("settings/saturation", 95.0).toDouble() / 100.0; SATURATION = settings.value("settings/saturation", 95.0).toDouble() / 100.0;
FILTERING = settings.value("settings/filtering", FILTERING).toInt();
QUALITY_RESIZE = settings.value("settings/qualitythumbnail", QUALITY_RESIZE).toBool();
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, settings.value("settings/dontusenativedialogs", false).toBool()); QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, settings.value("settings/dontusenativedialogs", false).toBool());
} }
@@ -102,6 +123,11 @@ void SettingsDialog::saveSettings()
settings.setValue("settings/preloadimagecount", m_preloadImages->value()); settings.setValue("settings/preloadimagecount", m_preloadImages->value());
settings.setValue("settings/dontusenativedialogs", m_useNativeDialog->isChecked()); settings.setValue("settings/dontusenativedialogs", m_useNativeDialog->isChecked());
settings.setValue("settings/saturation", m_saturation->value()); settings.setValue("settings/saturation", m_saturation->value());
settings.setValue("settings/slideshowtime", m_slideShowTime->value());
settings.setValue("settings/qualitythumbnail", m_qualityThumbnail->isChecked());
QUALITY_RESIZE = m_qualityThumbnail->isChecked();
FILTERING = m_filtering->currentIndex();
settings.setValue("settings/filtering", FILTERING);
SATURATION = m_saturation->value() / 100.0; SATURATION = m_saturation->value() / 100.0;
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked()); QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked());
if(DEFAULT_WIDTH != m_preloadImages->value()) if(DEFAULT_WIDTH != m_preloadImages->value())
+4
View File
@@ -4,6 +4,7 @@
#include <QDialog> #include <QDialog>
#include <QSpinBox> #include <QSpinBox>
#include <QCheckBox> #include <QCheckBox>
#include <QComboBox>
class SettingsDialog : public QDialog class SettingsDialog : public QDialog
{ {
@@ -19,8 +20,11 @@ private:
QSpinBox *m_preloadImages; QSpinBox *m_preloadImages;
QSpinBox *m_thumSize; QSpinBox *m_thumSize;
QDoubleSpinBox *m_slideShowTime;
QDoubleSpinBox *m_saturation; QDoubleSpinBox *m_saturation;
QCheckBox *m_useNativeDialog; QCheckBox *m_useNativeDialog;
QCheckBox *m_qualityThumbnail;
QComboBox *m_filtering;
}; };
#endif // SETTINGSDIALOG_H #endif // SETTINGSDIALOG_H
+2 -1
View File
@@ -1,6 +1,7 @@
#version 330 #version 330
uniform sampler2D qt_Texture0; uniform sampler2D qt_Texture0;
uniform ivec2 firstRed;
in vec2 qt_TexCoord0; in vec2 qt_TexCoord0;
in vec2 center; in vec2 center;
layout(location = 0) out vec4 color; layout(location = 0) out vec4 color;
@@ -11,7 +12,7 @@ void main(void)
{ {
ivec2 texSize = textureSize(qt_Texture0, 0); ivec2 texSize = textureSize(qt_Texture0, 0);
ivec2 icenter = ivec2(center); ivec2 icenter = ivec2(center);
ivec2 alternate = icenter % 2; ivec2 alternate = (icenter + firstRed) % 2;
// cross, checker, theta, phi // cross, checker, theta, phi
const vec4 kA = vec4(-1.0, -1.5, 0.5, -1.0) / 8.0; const vec4 kA = vec4(-1.0, -1.5, 0.5, -1.0) / 8.0;
+120 -8
View File
@@ -1,11 +1,13 @@
#version 330 #version 330
uniform sampler2D qt_Texture0; uniform sampler2D qt_Texture0;
uniform vec3 mtf_param; uniform vec3 mtf_param[3];
uniform vec2 unit_scale;
uniform bool bw; uniform bool bw;
uniform bool invert; uniform bool invert;
uniform bool srgb; uniform bool srgb;
uniform vec3 whiteBalance; uniform bool false_color;
uniform int filtering;
in vec2 qt_TexCoord0; in vec2 qt_TexCoord0;
layout(location = 0) out vec4 color; layout(location = 0) out vec4 color;
@@ -16,11 +18,26 @@ vec3 Linear2sRGB(vec3 color)
greaterThan(color, vec3(0.0031308))); 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)); 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);
float f = fract(color);
return mix(pallete[i], pallete[i+1], f);// * (f * 0.5 + 0.5);
} }
vec3 checker() vec3 checker()
@@ -29,11 +46,108 @@ vec3 checker()
return vec3(step(pattern.x * pattern.y, 0.0) * 0.25 + 0.25); return vec3(step(pattern.x * pattern.y, 0.0) * 0.25 + 0.25);
} }
vec4 cubic(float v)
{
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
vec4 s = n * n * n;
float x = s.x;
float y = s.y - 4.0 * s.x;
float z = s.z - 4.0 * s.y + 6.0 * s.x;
float w = 6.0 - x - y - z;
return vec4(x, y, z, w) * (1.0/6.0);
}
vec4 textureBicubic(sampler2D sampler, vec2 texCoords)
{
vec2 texSize = textureSize(sampler, 0);
vec2 invTexSize = 1.0 / texSize;
texCoords = texCoords * texSize - 0.5;
vec2 fxy = fract(texCoords);
texCoords -= fxy;
vec4 xcubic = cubic(fxy.x);
vec4 ycubic = cubic(fxy.y);
vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy;
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;
offset *= invTexSize.xxyy;
vec4 sample0 = texture(sampler, offset.xz);
vec4 sample1 = texture(sampler, offset.yz);
vec4 sample2 = texture(sampler, offset.xw);
vec4 sample3 = texture(sampler, offset.yw);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
}
vec4 textureCatmul(sampler2D sampler, vec2 texCoords)
{
ivec2 texSize = textureSize(sampler, 0);
texCoords = texCoords * vec2(texSize) - 0.5;
ivec2 texel = ivec2(floor(texCoords));
vec2 fra = fract(texCoords);
texSize -= 1;
const mat4 CatMul = mat4(0, 1, 0, 0, -0.5, 0, 0.5, 0, 1, -2.5, 2, -0.5, -0.5, 1.5, -1.5, 0.5);
vec4 xx = CatMul * vec4(1.0, fra.x, fra.x*fra.x, fra.x*fra.x*fra.x);
vec4 yy = CatMul * vec4(1.0, fra.y, fra.y*fra.y, fra.y*fra.y*fra.y);
vec4 a00 = texelFetch(sampler, clamp(texel + ivec2(-1, -1), ivec2(0, 0), texSize), 0) * xx.x;
vec4 a01 = texelFetch(sampler, clamp(texel + ivec2( 0, -1), ivec2(0, 0), texSize), 0) * xx.y;
vec4 a02 = texelFetch(sampler, clamp(texel + ivec2( 1, -1), ivec2(0, 0), texSize), 0) * xx.z;
vec4 a03 = texelFetch(sampler, clamp(texel + ivec2( 2, -1), ivec2(0, 0), texSize), 0) * xx.w;
vec4 a10 = texelFetch(sampler, clamp(texel + ivec2(-1, 0), ivec2(0, 0), texSize), 0) * xx.x;
vec4 a11 = texelFetch(sampler, clamp(texel + ivec2( 0, 0), ivec2(0, 0), texSize), 0) * xx.y;
vec4 a12 = texelFetch(sampler, clamp(texel + ivec2( 1, 0), ivec2(0, 0), texSize), 0) * xx.z;
vec4 a13 = texelFetch(sampler, clamp(texel + ivec2( 2, 0), ivec2(0, 0), texSize), 0) * xx.w;
vec4 a20 = texelFetch(sampler, clamp(texel + ivec2(-1, 1), ivec2(0, 0), texSize), 0) * xx.x;
vec4 a21 = texelFetch(sampler, clamp(texel + ivec2( 0, 1), ivec2(0, 0), texSize), 0) * xx.y;
vec4 a22 = texelFetch(sampler, clamp(texel + ivec2( 1, 1), ivec2(0, 0), texSize), 0) * xx.z;
vec4 a23 = texelFetch(sampler, clamp(texel + ivec2( 2, 1), ivec2(0, 0), texSize), 0) * xx.w;
vec4 a30 = texelFetch(sampler, clamp(texel + ivec2(-1, 2), ivec2(0, 0), texSize), 0) * xx.x;
vec4 a31 = texelFetch(sampler, clamp(texel + ivec2( 0, 2), ivec2(0, 0), texSize), 0) * xx.y;
vec4 a32 = texelFetch(sampler, clamp(texel + ivec2( 1, 2), ivec2(0, 0), texSize), 0) * xx.z;
vec4 a33 = texelFetch(sampler, clamp(texel + ivec2( 2, 2), ivec2(0, 0), texSize), 0) * xx.w;
vec4 c = vec4(0.0);
c += (a00 + a01 + a02 + a03) * yy.x;
c += (a10 + a11 + a12 + a13) * yy.y;
c += (a20 + a21 + a22 + a23) * yy.z;
c += (a30 + a31 + a32 + a33) * yy.w;
return c;
}
void main(void) void main(void)
{ {
switch(filtering)
{
case 0://nearest
color = texelFetch(qt_Texture0, ivec2(qt_TexCoord0 * textureSize(qt_Texture0, 0)), 0);
break;
default:
case 1://bilinear
color = texture(qt_Texture0, qt_TexCoord0); color = texture(qt_Texture0, qt_TexCoord0);
break;
case 2://catmul bicubic
color = textureCatmul(qt_Texture0, qt_TexCoord0);
break;
}
color.rgb = color.rgb * unit_scale.x + unit_scale.y;
if(bw)color = color.rrra; 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; if(invert)color.rgb = vec3(1.0) - color.rgb;
@@ -41,8 +155,6 @@ void main(void)
if(srgb)color.rgb = Linear2sRGB(color.rgb); 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)))) 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); color = vec4(0.0, 0.0, 0.0, 1.0);
+10
View File
@@ -0,0 +1,10 @@
<RCC>
<qresource prefix="/">
<file>debayer.frag</file>
<file>debayer.vert</file>
<file>image.frag</file>
<file>image.vert</file>
<file>thumb.frag</file>
<file>thumb.vert</file>
</qresource>
</RCC>
+5 -5
View File
@@ -1,22 +1,22 @@
#version 330 #version 330
uniform sampler2DArray qt_Texture0; uniform sampler2DArray qt_Texture0;
uniform vec3 mtf_param; uniform vec3 mtf_param[3];
uniform bool invert; uniform bool invert;
in vec3 qt_TexCoord0; in vec3 qt_TexCoord0;
layout(location = 0) 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)); 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) void main(void)
{ {
color = texture(qt_Texture0, qt_TexCoord0); 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; if(invert)color = vec4(1.0) - color;
color.a = 1.0; color.a = 1.0;
} }
+41 -3
View File
@@ -4,14 +4,18 @@
<launchable type="desktop-id">space.nouspiro.tenmon.desktop</launchable> <launchable type="desktop-id">space.nouspiro.tenmon.desktop</launchable>
<name>Tenmon</name> <name>Tenmon</name>
<summary>FITS/XISF image viewer, converter, index and search</summary> <summary>FITS/XISF image viewer, converter, index and search</summary>
<developer id="nouspiro.space">
<name>Dušan Poizl</name>
</developer>
<developer_name>Dušan Poizl</developer_name>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0</project_license> <project_license>GPL-3.0</project_license>
<description> <description>
<p>It is intended primarily for viewing astro photos and images. It supports the following formats:</p> <p>It is intended primarily for viewing astro photos and images. It supports the following formats:</p>
<ul> <ul>
<li>FITS 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 bit integer and 32 bit float</li> <li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
<li>RAW CR2, DNG, NEF</li> <li>RAW CR2/CR3, DNG, NEF</li>
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li> <li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
</ul> </ul>
<p>Features of application:</p> <p>Features of application:</p>
@@ -27,6 +31,7 @@
<li>Thumbnails</li> <li>Thumbnails</li>
<li>Convert CFA images to colour - debayer</li> <li>Convert CFA images to colour - debayer</li>
<li>Color space aware</li> <li>Color space aware</li>
<li>Histogram</li>
</ul> </ul>
</description> </description>
<categories> <categories>
@@ -42,12 +47,45 @@
<url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url> <url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<caption>Main window with image</caption>
<image>https://nouspiro.space/wp-content/uploads/2022/04/tenmon-1024x579.png</image> <image>https://nouspiro.space/wp-content/uploads/2022/04/tenmon-1024x579.png</image>
</screenshot>
<screenshot type="default">
<caption>Thumnail view</caption>
<image>https://nouspiro.space/wp-content/uploads/2022/12/tenmon2-1024x645.png</image> <image>https://nouspiro.space/wp-content/uploads/2022/12/tenmon2-1024x645.png</image>
</screenshot> </screenshot>
</screenshots> </screenshots>
<content_rating type="oars-1.1"/> <content_rating type="oars-1.1"/>
<releases> <releases>
<release version="20240201" date="2024-02-01">
<description>
<ul>
<li>Smooth thumbnails</li>
<li>Respect ROWORDER</li>
<li>Bugfixes</li>
</ul>
</description>
</release>
<release version="20240108" date="2024-01-08">
<description>
<ul>
<li>Update to Qt6</li>
<li>Add support for CR3 RAW files</li>
<li>Slideshow</li>
<li>Improved rapid image view</li>
</ul>
</description>
</release>
<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"> <release version="20230419" date="2023-04-19">
<description> <description>
<ul> <ul>
+39 -21
View File
@@ -10,18 +10,34 @@ static float clamp(float x)
return std::min(std::max(x, 0.0f), 1.0f); 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); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setMinimumWidth(100); setMinimumWidth(100);
setMinimumHeight(15);
setMaximumHeight(15);
setMouseTracking(true); setMouseTracking(true);
if(color == Qt::white)
{
setMaximumHeight(16);
setMinimumHeight(16);
}
else
{
setMaximumHeight(10);
setMinimumHeight(10);
}
m_blackPoint = 0; m_blackPoint = 0;
m_midPoint = 0.5; m_midPoint = 0.5;
m_whitePoint = 1; m_whitePoint = 1;
m_grabbed = -1; m_grabbed = -1;
m_fineTune = false; 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")); setToolTip(tr("Press Shift for fine tuning"));
} }
@@ -60,7 +76,7 @@ void STFSlider::paintEvent(QPaintEvent *event)
{ {
qreal p = i/32.0f; qreal p = i/32.0f;
qreal c = std::pow(p, 1.0/2.2)*255; 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)); QPainterPath tick(QPointF(0, 0));
@@ -75,7 +91,7 @@ void STFSlider::paintEvent(QPaintEvent *event)
auto drawTick = [&](qreal p) 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.resetTransform();
painter.translate(w*p, 0); painter.translate(w*p, 0);
painter.drawPath(tick); painter.drawPath(tick);
@@ -89,43 +105,44 @@ void STFSlider::paintEvent(QPaintEvent *event)
void STFSlider::mouseMoveEvent(QMouseEvent *event) void STFSlider::mouseMoveEvent(QMouseEvent *event)
{ {
if(std::abs(m_blackPoint*width() - event->x()) < 5 || const qreal x = event->position().x();
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - event->x()) < 5 || if(std::abs(m_blackPoint*width() - x) < 5 ||
std::abs(m_whitePoint*width() - event->x()) < 5) std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5 ||
std::abs(m_whitePoint*width() - x) < 5)
setCursor(Qt::SplitHCursor); setCursor(Qt::SplitHCursor);
else else
unsetCursor(); unsetCursor();
qreal x = (qreal)event->x()/width(); qreal xw = x/width();
if(event->modifiers() & Qt::ShiftModifier && !m_fineTune) if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
{ {
m_fineTune = true; m_fineTune = true;
m_fineTuneX = x; m_fineTuneX = xw;
} }
if(!(event->modifiers() & Qt::ShiftModifier) && m_fineTune) if(!(event->modifiers() & Qt::ShiftModifier) && m_fineTune)
m_fineTune = false; m_fineTune = false;
if(m_fineTune) if(m_fineTune)
{ {
x = m_fineTuneX + (x - m_fineTuneX) * 0.2; xw = m_fineTuneX + (xw - m_fineTuneX) * 0.2;
} }
switch(m_grabbed) switch(m_grabbed)
{ {
case 0: case 0:
m_blackPoint = clamp(x); m_blackPoint = clamp(xw);
m_whitePoint = std::max(m_whitePoint, m_blackPoint); m_whitePoint = std::max(m_whitePoint, m_blackPoint);
QToolTip::showText(event->globalPos(), QString::number(m_blackPoint), this); QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_blackPoint), this);
break; break;
case 1: case 1:
m_midPoint = (x - m_blackPoint) / (m_whitePoint - m_blackPoint); m_midPoint = (xw - m_blackPoint) / (m_whitePoint - m_blackPoint);
m_midPoint = clamp(m_midPoint); m_midPoint = clamp(m_midPoint);
QToolTip::showText(event->globalPos(), QString::number(m_midPoint), this); QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_midPoint), this);
break; break;
case 2: case 2:
m_whitePoint = clamp(x); m_whitePoint = clamp(xw);
m_blackPoint = std::min(m_blackPoint, m_whitePoint); m_blackPoint = std::min(m_blackPoint, m_whitePoint);
QToolTip::showText(event->globalPos(), QString::number(m_whitePoint), this); QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_whitePoint), this);
break; break;
} }
if(m_grabbed >= 0) if(m_grabbed >= 0)
@@ -137,17 +154,18 @@ void STFSlider::mouseMoveEvent(QMouseEvent *event)
void STFSlider::mousePressEvent(QMouseEvent *event) void STFSlider::mousePressEvent(QMouseEvent *event)
{ {
const qreal x = event->position().x();
if(event->modifiers() & Qt::ShiftModifier) if(event->modifiers() & Qt::ShiftModifier)
{ {
m_fineTune = true; m_fineTune = true;
m_fineTuneX = (qreal)event->x()/width(); m_fineTuneX = x/width();
} }
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - event->x()) < 5) if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5)
m_grabbed = 1; m_grabbed = 1;
else if(std::abs(m_blackPoint*width() - event->x()) < 5) else if(std::abs(m_blackPoint*width() - x) < 5)
m_grabbed = 0; m_grabbed = 0;
else if(std::abs(m_whitePoint*width() - event->x()) < 5) else if(std::abs(m_whitePoint*width() - x) < 5)
m_grabbed = 2; m_grabbed = 2;
else else
m_grabbed = -1; m_grabbed = -1;
+3 -1
View File
@@ -13,8 +13,10 @@ class STFSlider : public QWidget
int m_grabbed; int m_grabbed;
bool m_fineTune; bool m_fineTune;
float m_fineTuneX; float m_fineTuneX;
QColor m_color;
float m_threshold;
public: public:
explicit STFSlider(QWidget *parent = nullptr); explicit STFSlider(const QColor &color = Qt::white, QWidget *parent = nullptr);
float blackPoint() const; float blackPoint() const;
float midPoint() const; float midPoint() const;
float whitePoint() const; float whitePoint() const;
+142 -19
View File
@@ -18,13 +18,60 @@ float MTF(float x, float m)
StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar"), parent) StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar"), parent)
{ {
setObjectName("stretchtoolbar"); setObjectName("stretchtoolbar");
m_stfSlider = new STFSlider(this); QWidget *lum = new QWidget(this);
addWidget(m_stfSlider); QVBoxLayout *vbox1 = new QVBoxLayout(lum);
connect(m_stfSlider, SIGNAL(paramChanged(float, float, float)), this, SIGNAL(paramChanged(float,float,float))); 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")); QAction *autoStretchButton = addAction(QIcon(":/nuke.png"), tr("Auto Stretch F12"));
autoStretchButton->setShortcut(Qt::Key_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")); QAction *resetButton = addAction(style()->standardIcon(QStyle::SP_DialogResetButton), tr("Reset Screen Transfer Function F11"));
resetButton->setShortcut(Qt::Key_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")); QAction *invertButton = addAction(QIcon(":/invert.png"), tr("Invert colors"));
invertButton->setCheckable(true); 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")); QAction *falseColorButton = addAction(QIcon(":/falsecolor.png"), tr("False colors"));
superPixelButton->setCheckable(true); falseColorButton->setCheckable(true);
connect(superPixelButton, SIGNAL(toggled(bool)), this, SIGNAL(superPixel(bool))); 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 = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
m_autoStretchOnLoad->setCheckable(true); m_autoStretchOnLoad->setCheckable(true);
@@ -44,28 +95,83 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
void StretchToolbar::stretchImage(Image *img) 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; 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(); median /= img->rawImage()->norm();
bool a = median > 0.5 ? true : false;
mad /= img->rawImage()->norm(); mad /= img->rawImage()->norm();
max /= img->rawImage()->norm(); max = 1.0f;// /= img->rawImage()->norm();
if(max>1.0f)max = 1.0f; float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
float bp = median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA; if(a && mad != 0.0f)
float mid = MTF(median - bp, TARGET_BACKGROUND); max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
m_stfSlider->setMTFParams(bp, mid, max); float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median);
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() void StretchToolbar::resetMTF()
{
MTFParam params;
m_mtfParam = params;
if(m_stack->currentIndex() == 0)
{ {
m_stfSlider->setMTFParams(0, 0.5, 1); m_stfSlider->setMTFParams(0, 0.5, 1);
emit paramChanged(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) void StretchToolbar::imageLoaded(Image *img)
@@ -74,4 +180,21 @@ void StretchToolbar::imageLoaded(Image *img)
stretchImage(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 #define STRETCHTOOLBAR_H
#include <QToolBar> #include <QToolBar>
#include <QStackedWidget>
#include "stfslider.h" #include "stfslider.h"
class Image; 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 class StretchToolbar : public QToolBar
{ {
Q_OBJECT Q_OBJECT
STFSlider *m_stfSlider; STFSlider *m_stfSlider;
STFSlider *m_stfSliderR;
STFSlider *m_stfSliderG;
STFSlider *m_stfSliderB;
QAction *m_autoStretchOnLoad; QAction *m_autoStretchOnLoad;
QAction *m_debayer;
QStackedWidget *m_stack;
MTFParam m_mtfParam;
public: public:
explicit StretchToolbar(QWidget *parent = nullptr); explicit StretchToolbar(QWidget *parent = nullptr);
public slots: public slots:
void stretchImage(Image *img); void stretchImage(Image *img);
void resetMTF(); void resetMTF();
void imageLoaded(Image *img); void imageLoaded(Image *img);
void unlinkStretch(bool enable);
signals: signals:
void paramChanged(float low, float mid, float high); void paramChanged(const MTFParam &params);
void autoStretch(); void autoStretch();
void invert(bool enable); void invert(bool enable);
void superPixel(bool enable); void superPixel(bool enable);
void falseColor(bool enable);
}; };
#endif // STRETCHTOOLBAR_H #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.
+162 -28
View File
@@ -8,6 +8,89 @@
<translation>About Tenmon</translation> <translation>About Tenmon</translation>
</message> </message>
</context> </context>
<context>
<name>BatchProcessing</name>
<message>
<source>Batch Processing</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Input files and directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add files</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove all</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Browse</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Scripts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open scripts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Log</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Start script</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Stop script</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Close</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Interrupt running script?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select files</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Output directory path doesn&apos;t exist or is not writable</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>DataBaseView</name> <name>DataBaseView</name>
<message> <message>
@@ -106,6 +189,13 @@
<translation>Help</translation> <translation>Help</translation>
</message> </message>
</context> </context>
<context>
<name>Histogram</name>
<message>
<source>Logarithmic scale</source>
<translation>Logarithmic scale</translation>
</message>
</context>
<context> <context>
<name>ImageInfo</name> <name>ImageInfo</name>
<message> <message>
@@ -158,6 +248,10 @@
<source>R:%1 G:%2 B:%3</source> <source>R:%1 G:%2 B:%3</source>
<translation>R:%1 G:%2 B:%3</translation> <translation>R:%1 G:%2 B:%3</translation>
</message> </message>
<message>
<source>Failed to load image</source>
<translation>Failed to load image</translation>
</message>
</context> </context>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@@ -225,10 +319,6 @@
<source>100%</source> <source>100%</source>
<translation>100%</translation> <translation>100%</translation>
</message> </message>
<message>
<source>Fullscreen</source>
<translation type="vanished">Fullscreen</translation>
</message>
<message> <message>
<source>Select</source> <source>Select</source>
<translation>Select</translation> <translation>Select</translation>
@@ -337,10 +427,6 @@
<source>Edit</source> <source>Edit</source>
<translation>Edit</translation> <translation>Edit</translation>
</message> </message>
<message>
<source>FITS header editor</source>
<translation type="vanished">FITS header editor</translation>
</message>
<message> <message>
<source>Settings</source> <source>Settings</source>
<translation>Settings</translation> <translation>Settings</translation>
@@ -405,6 +491,34 @@
<source>CSV file (*.csv)</source> <source>CSV file (*.csv)</source>
<translation>CSV files (*.csv)</translation> <translation>CSV files (*.csv)</translation>
</message> </message>
<message>
<source>Histogram</source>
<translation>Histogram</translation>
</message>
<message>
<source>Bayer mask</source>
<translation>Bayer mask</translation>
</message>
<message>
<source>RGGB</source>
<translation>RGGB</translation>
</message>
<message>
<source>GRBG</source>
<translation>GRBG</translation>
</message>
<message>
<source>GBRG</source>
<translation>GBRG</translation>
</message>
<message>
<source>BGGR</source>
<translation>BGGR</translation>
</message>
<message>
<source>Slideshow</source>
<translation>Slideshow</translation>
</message>
</context> </context>
<context> <context>
<name>MarkedFiles</name> <name>MarkedFiles</name>
@@ -475,22 +589,6 @@
<source>MAD</source> <source>MAD</source>
<translation>MAD</translation> <translation>MAD</translation>
</message> </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> <message>
<source>Unsupported sample format</source> <source>Unsupported sample format</source>
<translation>Unsupported sample format</translation> <translation>Unsupported sample format</translation>
@@ -536,10 +634,6 @@
<source>Thumbnails size</source> <source>Thumbnails size</source>
<translation>Thumbnails size</translation> <translation>Thumbnails size</translation>
</message> </message>
<message>
<source>Changes in settings will take effect after program restart.</source>
<translation type="vanished">Changes in settings will take effect after program restart.</translation>
</message>
<message> <message>
<source>Don&apos;t use native file dialog</source> <source>Don&apos;t use native file dialog</source>
<translation>Don&apos;t use native file dialog</translation> <translation>Don&apos;t use native file dialog</translation>
@@ -554,6 +648,38 @@ For RAW files you may set 22%</translation>
<source>Saturation</source> <source>Saturation</source>
<translation>Saturated</translation> <translation>Saturated</translation>
</message> </message>
<message>
<source>Nearest</source>
<translation>Nearest</translation>
</message>
<message>
<source>Linear</source>
<translation>Linear</translation>
</message>
<message>
<source>Cubic</source>
<translation>Cubic</translation>
</message>
<message>
<source>Smooth thumbnails</source>
<translation>Smooth thumbnails</translation>
</message>
<message>
<source>Use box filter when downsampling thumbnails instead of nearest. Slightly slower.</source>
<translation>Use box filter when downsampling thumbnails instead of nearest. Slightly slower.</translation>
</message>
<message>
<source>Slideshow interval</source>
<translation>Slideshow interval</translation>
</message>
<message>
<source>Image filtering</source>
<translation type="obsolete">Image sampling</translation>
</message>
<message>
<source>Image interpolation</source>
<translation>Image interpolation</translation>
</message>
</context> </context>
<context> <context>
<name>StretchToolbar</name> <name>StretchToolbar</name>
@@ -581,5 +707,13 @@ For RAW files you may set 22%</translation>
<source>Debayer CFA</source> <source>Debayer CFA</source>
<translation>Debayer CFA</translation> <translation>Debayer CFA</translation>
</message> </message>
<message>
<source>False colors</source>
<translation>False colors</translation>
</message>
<message>
<source>Linked channels</source>
<translation>Linked channels</translation>
</message>
</context> </context>
</TS> </TS>
Binary file not shown.
+162 -28
View File
@@ -8,6 +8,89 @@
<translation>A propos de Tenmon</translation> <translation>A propos de Tenmon</translation>
</message> </message>
</context> </context>
<context>
<name>BatchProcessing</name>
<message>
<source>Batch Processing</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Input files and directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add files</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove all</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Browse</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Scripts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open scripts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Log</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Start script</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Stop script</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Close</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Interrupt running script?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select files</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Output directory path doesn&apos;t exist or is not writable</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>DataBaseView</name> <name>DataBaseView</name>
<message> <message>
@@ -106,6 +189,13 @@
<translation>Aide</translation> <translation>Aide</translation>
</message> </message>
</context> </context>
<context>
<name>Histogram</name>
<message>
<source>Logarithmic scale</source>
<translation>Échelle logarithmique</translation>
</message>
</context>
<context> <context>
<name>ImageInfo</name> <name>ImageInfo</name>
<message> <message>
@@ -158,6 +248,10 @@
<source>R:%1 G:%2 B:%3</source> <source>R:%1 G:%2 B:%3</source>
<translation>R:%1 G:%2 B:%3</translation> <translation>R:%1 G:%2 B:%3</translation>
</message> </message>
<message>
<source>Failed to load image</source>
<translation>Échec du chargement de l&apos;image</translation>
</message>
</context> </context>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@@ -225,10 +319,6 @@
<source>100%</source> <source>100%</source>
<translation>100%</translation> <translation>100%</translation>
</message> </message>
<message>
<source>Fullscreen</source>
<translation type="vanished">Plein écran</translation>
</message>
<message> <message>
<source>Select</source> <source>Select</source>
<translation>Sélectionner</translation> <translation>Sélectionner</translation>
@@ -337,10 +427,6 @@
<source>Edit</source> <source>Edit</source>
<translation>Éditer</translation> <translation>Éditer</translation>
</message> </message>
<message>
<source>FITS header editor</source>
<translation type="vanished">Éditeur d&apos;en-tête FITS</translation>
</message>
<message> <message>
<source>Settings</source> <source>Settings</source>
<translation>Réglages</translation> <translation>Réglages</translation>
@@ -405,6 +491,34 @@
<source>CSV file (*.csv)</source> <source>CSV file (*.csv)</source>
<translation>Fichiers CSV (*.csv)</translation> <translation>Fichiers CSV (*.csv)</translation>
</message> </message>
<message>
<source>Histogram</source>
<translation>Histogramme</translation>
</message>
<message>
<source>Bayer mask</source>
<translation>Masque Bayer</translation>
</message>
<message>
<source>RGGB</source>
<translation>RGGB</translation>
</message>
<message>
<source>GRBG</source>
<translation>GRBG</translation>
</message>
<message>
<source>GBRG</source>
<translation>GBRG</translation>
</message>
<message>
<source>BGGR</source>
<translation>BGGR</translation>
</message>
<message>
<source>Slideshow</source>
<translation>Diaporama</translation>
</message>
</context> </context>
<context> <context>
<name>MarkedFiles</name> <name>MarkedFiles</name>
@@ -475,22 +589,6 @@
<source>MAD</source> <source>MAD</source>
<translation>MAD</translation> <translation>MAD</translation>
</message> </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> <message>
<source>Unsupported sample format</source> <source>Unsupported sample format</source>
<translation>Format non pris en charge</translation> <translation>Format non pris en charge</translation>
@@ -536,10 +634,6 @@
<source>Thumbnails size</source> <source>Thumbnails size</source>
<translation>Taille des vignette</translation> <translation>Taille des vignette</translation>
</message> </message>
<message>
<source>Changes in settings will take effect after program restart.</source>
<translation type="vanished">Les changements de paramètres prendront effet après le redémarrage du programme.</translation>
</message>
<message> <message>
<source>Don&apos;t use native file dialog</source> <source>Don&apos;t use native file dialog</source>
<translation>N&apos;utilisez pas la boîte de dialogue de fichier natif</translation> <translation>N&apos;utilisez pas la boîte de dialogue de fichier natif</translation>
@@ -554,6 +648,38 @@ Pour les fichiers RAW, vous pouvez définir 22&#xa0;%</translation>
<source>Saturation</source> <source>Saturation</source>
<translation>Saturé</translation> <translation>Saturé</translation>
</message> </message>
<message>
<source>Nearest</source>
<translation>Voisin le plus proche</translation>
</message>
<message>
<source>Linear</source>
<translation>Linéaire</translation>
</message>
<message>
<source>Cubic</source>
<translation>Cubique</translation>
</message>
<message>
<source>Smooth thumbnails</source>
<translation>Miniatures fluides</translation>
</message>
<message>
<source>Use box filter when downsampling thumbnails instead of nearest. Slightly slower.</source>
<translation>Utilisez un filtre encadré lors du sous-échantillonnage des vignettes au lieu des plus proches. Un peu plus lent.</translation>
</message>
<message>
<source>Slideshow interval</source>
<translation>Intervalle du diaporama</translation>
</message>
<message>
<source>Image filtering</source>
<translation type="obsolete">Échantillonnage d&apos;images</translation>
</message>
<message>
<source>Image interpolation</source>
<translation>Interpolation d&apos;images</translation>
</message>
</context> </context>
<context> <context>
<name>StretchToolbar</name> <name>StretchToolbar</name>
@@ -581,5 +707,13 @@ Pour les fichiers RAW, vous pouvez définir 22&#xa0;%</translation>
<source>Debayer CFA</source> <source>Debayer CFA</source>
<translation>Débayeriser CFA</translation> <translation>Débayeriser CFA</translation>
</message> </message>
<message>
<source>False colors</source>
<translation>Fausses couleurs</translation>
</message>
<message>
<source>Linked channels</source>
<translation>Chaînes liées</translation>
</message>
</context> </context>
</TS> </TS>
Binary file not shown.
+158 -28
View File
@@ -8,6 +8,89 @@
<translation>O Tenmon</translation> <translation>O Tenmon</translation>
</message> </message>
</context> </context>
<context>
<name>BatchProcessing</name>
<message>
<source>Batch Processing</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Input files and directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add files</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove all</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Browse</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Scripts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open scripts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Log</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Start script</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Stop script</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Close</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Interrupt running script?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select files</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid output directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Output directory path doesn&apos;t exist or is not writable</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>DataBaseView</name> <name>DataBaseView</name>
<message> <message>
@@ -107,6 +190,13 @@
<translation>Pomoc</translation> <translation>Pomoc</translation>
</message> </message>
</context> </context>
<context>
<name>Histogram</name>
<message>
<source>Logarithmic scale</source>
<translation>Logaritmická stupnica</translation>
</message>
</context>
<context> <context>
<name>ImageInfo</name> <name>ImageInfo</name>
<message> <message>
@@ -159,6 +249,10 @@
<source>R:%1 G:%2 B:%3</source> <source>R:%1 G:%2 B:%3</source>
<translation>R:%1 G:%2 B:%3</translation> <translation>R:%1 G:%2 B:%3</translation>
</message> </message>
<message>
<source>Failed to load image</source>
<translation>Zlyhalo načítanie obrázka</translation>
</message>
</context> </context>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@@ -222,10 +316,6 @@
<source>100%</source> <source>100%</source>
<translation>100%</translation> <translation>100%</translation>
</message> </message>
<message>
<source>Fullscreen</source>
<translation type="vanished">Celá obrazovka</translation>
</message>
<message> <message>
<source>Select</source> <source>Select</source>
<translation>Výber</translation> <translation>Výber</translation>
@@ -338,10 +428,6 @@
<source>Edit</source> <source>Edit</source>
<translation>Upraviť</translation> <translation>Upraviť</translation>
</message> </message>
<message>
<source>FITS header editor</source>
<translation type="vanished">Editor FITS hlavičky</translation>
</message>
<message> <message>
<source>Settings</source> <source>Settings</source>
<translation>Nastavenia</translation> <translation>Nastavenia</translation>
@@ -406,6 +492,34 @@
<source>CSV file (*.csv)</source> <source>CSV file (*.csv)</source>
<translation>Súbory CSV (*.csv)</translation> <translation>Súbory CSV (*.csv)</translation>
</message> </message>
<message>
<source>Histogram</source>
<translation>Histogram</translation>
</message>
<message>
<source>Bayer mask</source>
<translation>Bayerova maska</translation>
</message>
<message>
<source>RGGB</source>
<translation>RGGB</translation>
</message>
<message>
<source>GRBG</source>
<translation>GRBG</translation>
</message>
<message>
<source>GBRG</source>
<translation>GBRG</translation>
</message>
<message>
<source>BGGR</source>
<translation>BGGR</translation>
</message>
<message>
<source>Slideshow</source>
<translation>Prezentácia</translation>
</message>
</context> </context>
<context> <context>
<name>MarkedFiles</name> <name>MarkedFiles</name>
@@ -480,22 +594,6 @@
<source>MAD</source> <source>MAD</source>
<translation>MAD</translation> <translation>MAD</translation>
</message> </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> <message>
<source>Saturated</source> <source>Saturated</source>
<translation>Saturované</translation> <translation>Saturované</translation>
@@ -537,10 +635,6 @@
<source>Thumbnails size</source> <source>Thumbnails size</source>
<translation>Veľkosť náhľadu</translation> <translation>Veľkosť náhľadu</translation>
</message> </message>
<message>
<source>Changes in settings will take effect after program restart.</source>
<translation type="vanished">Zmeny v nastaveniach sa prejavia po reštarte programu.</translation>
</message>
<message> <message>
<source>Don&apos;t use native file dialog</source> <source>Don&apos;t use native file dialog</source>
<translation>Nepoužívať natívny súborový dialóg</translation> <translation>Nepoužívať natívny súborový dialóg</translation>
@@ -555,6 +649,34 @@ Pre RAW súbory možno treba nastaviť 22%</translation>
<source>Saturation</source> <source>Saturation</source>
<translation>Saturované</translation> <translation>Saturované</translation>
</message> </message>
<message>
<source>Nearest</source>
<translation>Žiadna</translation>
</message>
<message>
<source>Linear</source>
<translation>Lineárna</translation>
</message>
<message>
<source>Cubic</source>
<translation>Kubická</translation>
</message>
<message>
<source>Smooth thumbnails</source>
<translation>Hladké náhľady</translation>
</message>
<message>
<source>Use box filter when downsampling thumbnails instead of nearest. Slightly slower.</source>
<translation>Pri zemnšovaní obrázkov na náhľady sa pixely spriemerujú namiesto najblžšej hodnoty. Trocha pomalšie.</translation>
</message>
<message>
<source>Slideshow interval</source>
<translation>Interval medzi obrázkami</translation>
</message>
<message>
<source>Image interpolation</source>
<translation>Interpolácia obrázku</translation>
</message>
</context> </context>
<context> <context>
<name>StretchToolbar</name> <name>StretchToolbar</name>
@@ -582,5 +704,13 @@ Pre RAW súbory možno treba nastaviť 22%</translation>
<source>Debayer CFA</source> <source>Debayer CFA</source>
<translation>Preveď CFA na farbu</translation> <translation>Preveď CFA na farbu</translation>
</message> </message>
<message>
<source>False colors</source>
<translation>Falošné farby</translation>
</message>
<message>
<source>Linked channels</source>
<translation>Prepojené kanály</translation>
</message>
</context> </context>
</TS> </TS>