Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e952873e3 | |||
| fb24800050 | |||
| ea0dcc226a | |||
| 6a7b677b95 | |||
| 0cee4c9c53 | |||
| d5f2351905 | |||
| 18732a8cbf | |||
| 8c9c1d8d06 | |||
| e5f425ff8d | |||
| 428f9c360a | |||
| a8a1509db7 | |||
| 6539c78c57 | |||
| 0e0d29320e | |||
| 1efe8e6974 | |||
| dae10182d1 | |||
| ed5fc9c1c2 | |||
| cd6a64a98b | |||
| 67355a82b7 | |||
| 8fc2078a3a | |||
| da9b389409 | |||
| 7818b8d3e9 | |||
| 11294bfcb0 | |||
| faecb385aa | |||
| e5be04926b | |||
| eaf2c7094b | |||
| aef41f5f6b | |||
| 2134f13b06 | |||
| 0e9c980325 | |||
| b9bf6bf183 |
@@ -17,7 +17,7 @@ if(SANITIZE_ADDRESS_LEAK)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
|
||||
endif(SANITIZE_ADDRESS_LEAK)
|
||||
|
||||
find_package(Qt5 COMPONENTS Widgets Sql OpenGL REQUIRED)
|
||||
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
|
||||
find_library(GSL_LIB gsl REQUIRED)
|
||||
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
|
||||
find_library(EXIF_LIB exif REQUIRED)
|
||||
@@ -29,6 +29,7 @@ add_subdirectory(libXISF)
|
||||
|
||||
set(TENMON_SRC
|
||||
about.cpp about.h
|
||||
batchprocessing.cpp batchprocessing.h batchprocessing.ui
|
||||
database.cpp database.h
|
||||
databaseview.cpp databaseview.h
|
||||
delete.cpp
|
||||
@@ -44,6 +45,7 @@ set(TENMON_SRC
|
||||
markedfiles.cpp markedfiles.h
|
||||
rawimage.cpp rawimage.h
|
||||
rawimage_sse.cpp
|
||||
scriptengine.cpp scriptengine.h
|
||||
settingsdialog.cpp settingsdialog.h
|
||||
starfit.cpp starfit.h
|
||||
statusbar.cpp statusbar.h
|
||||
@@ -52,28 +54,28 @@ set(TENMON_SRC
|
||||
)
|
||||
|
||||
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)
|
||||
add_compile_definitions("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)
|
||||
list(APPEND TENMON_SRC icon.rc)
|
||||
list(APPEND TENMON_SRC resources/icon.rc)
|
||||
set(tenmon_ICON "")
|
||||
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")
|
||||
else()
|
||||
set(tenmon_ICON "")
|
||||
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_search_module(GIO REQUIRED gio-2.0)
|
||||
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)
|
||||
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR})
|
||||
@@ -82,16 +84,16 @@ if(UNIX AND NOT APPLE)
|
||||
target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${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)
|
||||
target_link_libraries(tenmon "-framework CoreFoundation")
|
||||
else()
|
||||
target_link_libraries(tenmon ${GIO_LDFLAGS})
|
||||
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
||||
elseif(UNIX)
|
||||
target_link_libraries(tenmon PRIVATE Qt6::DBus ${GIO_LDFLAGS})
|
||||
endif(APPLE)
|
||||
|
||||
if(LIBRAW_STATIC)
|
||||
add_compile_definitions("LIBRAW_NODLL")
|
||||
target_link_libraries(tenmon jasper)
|
||||
target_link_libraries(tenmon PRIVATE jasper)
|
||||
endif()
|
||||
|
||||
install(TARGETS tenmon BUNDLE DESTINATION .)
|
||||
@@ -102,8 +104,8 @@ if(UNIX AND NOT APPLE)
|
||||
install(SCRIPT install.cmake)
|
||||
else()
|
||||
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 space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
||||
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
|
||||
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
||||
endif()
|
||||
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
@@ -2,20 +2,20 @@ FITS/XISF image viewer with multithreaded image loading
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
QDir dir(path);
|
||||
|
||||
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE");
|
||||
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
|
||||
|
||||
if(!dir.mkpath("."))
|
||||
return false;
|
||||
|
||||
@@ -24,7 +24,7 @@ class Database : public QObject
|
||||
int m_progress;
|
||||
public:
|
||||
explicit Database(QObject *parent = 0);
|
||||
bool init();
|
||||
bool init(const QLatin1String &connectionName = QLatin1String(QSqlDatabase::defaultConnection));
|
||||
bool mark(const QString &filename);
|
||||
bool unmark(const QString &filename);
|
||||
bool mark(const QStringList &filenames);
|
||||
|
||||
@@ -201,7 +201,8 @@ void FITSFileModel::prepareQuery()
|
||||
if(lastError().type() != QSqlError::NoError)
|
||||
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)
|
||||
|
||||
@@ -81,7 +81,7 @@ void ImageInfo::setInfo(const ImageInfoData &info)
|
||||
QTreeWidgetItem *fitsHeader = new QTreeWidgetItem({tr("FITS Header")});
|
||||
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);
|
||||
}
|
||||
@@ -279,7 +279,7 @@ QString SkyPoint::toString() const
|
||||
t = t.addSecs(ra * 240);
|
||||
|
||||
double deg, min, sec;
|
||||
min = std::modf(dec, °) * 60;
|
||||
min = std::abs(std::modf(dec, °) * 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');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "imageringlist.h"
|
||||
#include <QThreadPool>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include "loadrunable.h"
|
||||
#include "rawimage.h"
|
||||
#include "database.h"
|
||||
@@ -108,6 +110,9 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
|
||||
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
|
||||
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
|
||||
m_thumbPool = new QThreadPool(this);
|
||||
|
||||
m_slideShowTimer = new QTimer(this);
|
||||
connect(m_slideShowTimer, &QTimer::timeout, this, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
|
||||
}
|
||||
|
||||
ImageRingList::~ImageRingList()
|
||||
@@ -163,6 +168,10 @@ void ImageRingList::increment()
|
||||
{
|
||||
if(m_images.size())
|
||||
{
|
||||
//don't increment if current image was not loaded yet
|
||||
if(!(*m_currImage)->rawImage())
|
||||
return;
|
||||
|
||||
(*m_firstImage)->release();
|
||||
m_firstImage = increment(m_firstImage);
|
||||
m_currImage = increment(m_currImage);
|
||||
@@ -295,11 +304,13 @@ void ImageRingList::clearThumbnails()
|
||||
|
||||
QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return createIndex(row, column, m_images.at(row).get());
|
||||
}
|
||||
|
||||
QModelIndex ImageRingList::parent(const QModelIndex &child) const
|
||||
{
|
||||
Q_UNUSED(child);
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
@@ -313,6 +324,7 @@ int ImageRingList::rowCount(const QModelIndex &parent) const
|
||||
|
||||
int ImageRingList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -398,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 ¤tFile)
|
||||
{
|
||||
QThreadPool::globalInstance()->clear();
|
||||
|
||||
@@ -65,14 +65,13 @@ class ImageRingList : public QAbstractItemModel
|
||||
QThreadPool *m_thumbPool;
|
||||
Database *m_database;
|
||||
QStringList m_nameFilter;
|
||||
QTimer *m_slideShowTimer;
|
||||
public:
|
||||
explicit ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent = 0);
|
||||
~ImageRingList() override;
|
||||
bool setDir(const QString path, const QString ¤tFile = QString());
|
||||
void setFile(const QString &file);
|
||||
ImagePtr currentImage();
|
||||
void increment();
|
||||
void decrement();
|
||||
void setLiveMode(bool live);
|
||||
void setCalculateStats(bool stats);
|
||||
void setFindPeaks(bool findPeaks);
|
||||
@@ -96,6 +95,9 @@ public slots:
|
||||
void setPreload(int width);
|
||||
void setSort(QDir::SortFlag sort);
|
||||
void reverseSort();
|
||||
void toggleSlideshow(bool start);
|
||||
void increment();
|
||||
void decrement();
|
||||
protected:
|
||||
void setFiles(const QStringList files, const QString ¤tFile = QString());
|
||||
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
|
||||
|
||||
@@ -106,7 +106,7 @@ void ImageScrollArea::wheelEvent(QWheelEvent *event)
|
||||
m_scale = (float)size().width()/m_pixmap.size().width();
|
||||
|
||||
QPointF top(horizontalScrollBar()->value(), verticalScrollBar()->value());
|
||||
QPointF mousePos = (top + event->posF()) / m_scale;
|
||||
QPointF mousePos = (top + event->position()) / m_scale;
|
||||
|
||||
QPoint delta = event->angleDelta();
|
||||
if(delta.y() > 0)
|
||||
@@ -115,7 +115,7 @@ void ImageScrollArea::wheelEvent(QWheelEvent *event)
|
||||
setScale(m_scale - 0.1);
|
||||
|
||||
mousePos *= m_scale;
|
||||
top = mousePos - event->posF();
|
||||
top = mousePos - event->position();
|
||||
horizontalScrollBar()->setValue(top.x());
|
||||
verticalScrollBar()->setValue(top.y());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "imagescrollareagl.h"
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLVersionFunctionsFactory>
|
||||
#include <QDebug>
|
||||
#include <QKeyEvent>
|
||||
#include <QOpenGLDebugLogger>
|
||||
@@ -14,6 +15,8 @@
|
||||
#include <cmath>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
int FILTERING = 1;
|
||||
|
||||
struct RawImageType
|
||||
{
|
||||
QOpenGLTexture::PixelFormat pixelFormat;
|
||||
@@ -46,6 +49,10 @@ RawImageType getRawImageType(const RawImage *img)
|
||||
else
|
||||
type.textureFormat = QOpenGLTexture::R32F;
|
||||
type.dataType = QOpenGLTexture::Float32;
|
||||
break;
|
||||
default:
|
||||
qWarning() << "Invalid format" << img->type();
|
||||
break;
|
||||
}
|
||||
|
||||
if(img->channels() >= 3)
|
||||
@@ -83,14 +90,27 @@ ImageWidget::~ImageWidget()
|
||||
|
||||
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();
|
||||
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_imgHeight = image->height();
|
||||
m_currentImg = index;
|
||||
|
||||
|
||||
if(!m_image)return;
|
||||
|
||||
@@ -115,16 +135,11 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
|
||||
|
||||
m_unit_scale[0] = 1.0f;
|
||||
m_unit_scale[1] = 0.0f;
|
||||
auto &stats = image->imageStats();
|
||||
if(image->type() == RawImage::FLOAT32 || image->type() == RawImage::FLOAT64)
|
||||
if(image->type() == RawImage::FLOAT32)
|
||||
{
|
||||
float min = *std::min_element(stats.m_min, stats.m_min + 4);
|
||||
float max = *std::max_element(stats.m_max, stats.m_max + 4);
|
||||
if(min < 0.0f || max > 1.0f)
|
||||
{
|
||||
m_unit_scale[0] = 1.0f / (max - min);
|
||||
m_unit_scale[1] = min * m_unit_scale[0];
|
||||
}
|
||||
auto unitScaling = image->unitScale();
|
||||
m_unit_scale[0] = unitScaling.first;
|
||||
m_unit_scale[1] = unitScaling.second;
|
||||
}
|
||||
|
||||
if(m_debayerTex)
|
||||
@@ -213,6 +228,18 @@ QVector2D ImageWidget::getImagePixelCoord(const QVector2D &pos)
|
||||
return (pos + offset) / m_scale;
|
||||
}
|
||||
|
||||
void ImageWidget::setBayerMask(int mask)
|
||||
{
|
||||
m_firstRed[0] = mask & 0x1;
|
||||
m_firstRed[1] = (mask & 0x2) >> 1;
|
||||
if(m_debayerTex)
|
||||
{
|
||||
f->glDeleteTextures(1, &m_debayerTex);
|
||||
m_debayerTex = 0;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ImageWidget::setMTFParams(const MTFParam ¶ms)
|
||||
{
|
||||
m_mtfParams = params;
|
||||
@@ -276,7 +303,7 @@ void ImageWidget::thumbnailLoaded(const Image *image)
|
||||
{
|
||||
makeCurrent();
|
||||
const RawImage *raw = image->thumbnail();
|
||||
if(!raw)return;
|
||||
if(!raw || !raw->valid())return;
|
||||
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
|
||||
float a = raw->thumbAspect();
|
||||
int sizes[3] = { std::max(1, a > 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE * a)), std::max(1, a < 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE / a)), image->number() };
|
||||
@@ -361,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
|
||||
{
|
||||
debayer();
|
||||
@@ -380,6 +414,7 @@ void ImageWidget::paintGL()
|
||||
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("filtering", m_scale > 1.0f ? FILTERING : 1);
|
||||
#ifdef COLOR_MANAGMENT
|
||||
m_program->setUniformValue("srgb", m_srgb);
|
||||
#endif
|
||||
@@ -401,7 +436,7 @@ void ImageWidget::initializeGL()
|
||||
{
|
||||
f = context()->functions();
|
||||
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)
|
||||
QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed."));
|
||||
@@ -444,8 +479,8 @@ void ImageWidget::initializeGL()
|
||||
// f->glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(float)*4, 0);
|
||||
|
||||
m_program = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/image.vert");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/image.frag");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/image.vert");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/image.frag");
|
||||
|
||||
if(!m_program->link())
|
||||
{
|
||||
@@ -461,8 +496,8 @@ void ImageWidget::initializeGL()
|
||||
m_program->setUniformValue("scale", 1.0f, 0.0f);
|
||||
|
||||
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
|
||||
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/debayer.vert");
|
||||
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/debayer.frag");
|
||||
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/debayer.vert");
|
||||
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/debayer.frag");
|
||||
|
||||
m_debayerProgram->bind();
|
||||
m_debayerProgram->enableAttributeArray("qt_Vertex");
|
||||
@@ -478,8 +513,8 @@ void ImageWidget::initializeGL()
|
||||
m_vaoThumb->bind();
|
||||
|
||||
m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
|
||||
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/thumb.vert");
|
||||
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/thumb.frag");
|
||||
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/thumb.vert");
|
||||
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/thumb.frag");
|
||||
|
||||
m_thumbnailProgram->bind();
|
||||
m_thumbnailProgram->enableAttributeArray("qt_Vertex");
|
||||
@@ -559,7 +594,7 @@ void ImageWidget::mousePressEvent(QMouseEvent *event)
|
||||
else
|
||||
{
|
||||
if(event->button() == Qt::LeftButton)
|
||||
m_lastPos = event->localPos();
|
||||
m_lastPos = event->position();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,8 +606,8 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
}
|
||||
else if(!m_lastPos.isNull())
|
||||
{
|
||||
QPointF off = event->localPos() - m_lastPos;
|
||||
m_lastPos = event->localPos();
|
||||
QPointF off = event->position() - m_lastPos;
|
||||
m_lastPos = event->position();
|
||||
setOffset(m_dx - off.x(), m_dy - off.y());
|
||||
return;
|
||||
}
|
||||
@@ -638,7 +673,7 @@ void ImageWidget::wheelEvent(QWheelEvent *event)
|
||||
else
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,6 +716,7 @@ void ImageWidget::debayer()
|
||||
f->glViewport(0, 0, m_imgWidth, m_imgHeight);
|
||||
|
||||
m_debayerProgram->bind();
|
||||
f->glUniform2i(m_debayerProgram->uniformLocation("firstRed"), m_firstRed[0], m_firstRed[1]);
|
||||
m_image->bind(0);
|
||||
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
|
||||
@@ -65,9 +65,11 @@ class ImageWidget : public QOpenGLWidget
|
||||
int m_thumbnailCount = 0;
|
||||
int m_maxTextureSize = 0;
|
||||
int m_maxArrayLayers = 0;
|
||||
int m_firstRed[2] = {0, 0};
|
||||
QVector<ImageThumb> m_thumnails;
|
||||
Database *m_database = nullptr;
|
||||
QPointF m_lastPos;
|
||||
QString m_error;
|
||||
public:
|
||||
explicit ImageWidget(Database *database, QWidget *parent = nullptr);
|
||||
~ImageWidget() override;
|
||||
@@ -79,6 +81,7 @@ public:
|
||||
void blockRepaint(bool block);
|
||||
void allocateThumbnails(const QStringList &paths);
|
||||
QVector2D getImagePixelCoord(const QVector2D &pos);
|
||||
void setBayerMask(int mask);
|
||||
public slots:
|
||||
void setMTFParams(const MTFParam ¶ms);
|
||||
void setOffset(float dx, float dy);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
||||
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-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 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 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 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
@@ -274,6 +274,15 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
|
||||
if(file)
|
||||
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);
|
||||
if(status)
|
||||
{
|
||||
@@ -364,7 +373,7 @@ void LoadRunable::run()
|
||||
|
||||
std::shared_ptr<RawImage> rawImage;
|
||||
timer.start();
|
||||
if(m_file.endsWith(".CR2", Qt::CaseInsensitive) || m_file.endsWith(".NEF", Qt::CaseInsensitive) || m_file.endsWith(".DNG", Qt::CaseInsensitive))
|
||||
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);
|
||||
qDebug() << "LoadRAW" << timer.elapsed();
|
||||
@@ -478,14 +487,19 @@ void LoadRunable::run()
|
||||
|
||||
if(m_thumbnail)
|
||||
{
|
||||
if(rawImage)
|
||||
if(rawImage && rawImage->valid())
|
||||
{
|
||||
if(QUALITY_RESIZE)
|
||||
rawImage->resize(THUMB_SIZE, THUMB_SIZE);
|
||||
|
||||
rawImage->convertToThumbnail();
|
||||
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, info));
|
||||
}
|
||||
}
|
||||
catch(std::exception e)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QProgressDialog>
|
||||
#include <QDebug>
|
||||
#include <QDockWidget>
|
||||
#include <QActionGroup>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <QSettings>
|
||||
@@ -24,6 +25,7 @@
|
||||
#include "statusbar.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "histogram.h"
|
||||
#include "batchprocessing.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/ioctl.h>
|
||||
@@ -57,9 +59,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
_openFilter.append(" ");
|
||||
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 (*)"));
|
||||
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);
|
||||
QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
|
||||
@@ -150,6 +153,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir()));
|
||||
fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex()));
|
||||
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();
|
||||
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool)));
|
||||
liveModeAction->setCheckable(true);
|
||||
@@ -166,6 +174,26 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
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("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){
|
||||
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
|
||||
m_imageGL->imageWidget()->allocateThumbnails(m_ringList->imageNames());
|
||||
@@ -174,6 +202,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
else m_ringList->stopLoading();
|
||||
}, Qt::Key_F2);
|
||||
thumbnailsAction->setCheckable(true);
|
||||
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), m_ringList, &ImageRingList::toggleSlideshow, Qt::Key_F3);
|
||||
slideshowAction->setCheckable(true);
|
||||
menuBar()->addMenu(viewMenu);
|
||||
|
||||
QMenu *selectMenu = new QMenu(tr("Select"), this);
|
||||
@@ -228,6 +258,19 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
QSettings settings;
|
||||
restoreGeometry(settings.value("mainwindow/geometry").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);
|
||||
if(standardLocations.size())
|
||||
|
||||
@@ -7,9 +7,12 @@ int THUMB_SIZE = 128;
|
||||
int THUMB_SIZE_BORDER = 138;
|
||||
int THUMB_SIZE_BORDER_Y = 158;
|
||||
double SATURATION = 0.95;
|
||||
bool QUALITY_RESIZE = true;
|
||||
|
||||
#ifdef __SSE2__
|
||||
template<typename T, int ch>
|
||||
void fromPlanarSSE(const void *in, void *out, size_t count);
|
||||
#endif
|
||||
|
||||
size_t RawImage::typeSize(RawImage::DataType type)
|
||||
{
|
||||
@@ -386,7 +389,11 @@ const void *RawImage::origData(uint32_t row, uint32_t col) const
|
||||
|
||||
void RawImage::convertToThumbnail()
|
||||
{
|
||||
m_thumbAspect = (float)width() / height();
|
||||
if(!valid())
|
||||
return;
|
||||
|
||||
if(m_thumbAspect == 0.0f)
|
||||
m_thumbAspect = (float)width() / height();
|
||||
std::unique_ptr<PixelType[]> outptr = std::make_unique<PixelType[]>(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(uint16_t));
|
||||
uint16_t *out = reinterpret_cast<uint16_t*>(outptr.get());
|
||||
|
||||
@@ -398,6 +405,7 @@ void RawImage::convertToThumbnail()
|
||||
{
|
||||
int idx = (i*THUMB_SIZE + o)*4;
|
||||
int idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
|
||||
|
||||
if(m_channels == 1)
|
||||
{
|
||||
out[idx] = out[idx + 1] = out[idx + 2] = in[idx2] * scale;
|
||||
@@ -557,14 +565,107 @@ bool RawImage::pixel(int x, int y, double &r, double &g, double &b) const
|
||||
return true;
|
||||
}
|
||||
|
||||
void RawImage::downscaleTo(uint32_t size)
|
||||
template<typename T>
|
||||
void boxResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t oldh, const uint8_t *in_, uint8_t *out_)
|
||||
{
|
||||
/*if(size < width() || size < height())
|
||||
if(oldw == 0 || oldh == 0)return;
|
||||
|
||||
const T *in = reinterpret_cast<const T*>(in_);
|
||||
T *out = reinterpret_cast<T*>(out_);
|
||||
float max = 255.0f;
|
||||
if constexpr(std::is_same<T, uint16_t>::value)
|
||||
max = UINT16_MAX;
|
||||
|
||||
float sx = (float)w / oldw;
|
||||
float sy = (float)h / oldh;
|
||||
for(uint32_t y = 0; y < h; y++)//iterate over destination Y
|
||||
{
|
||||
double s = (double)size / std::max(width(), height());
|
||||
cv::Size dsize(std::floor(width() * s), std::floor(height() * s));
|
||||
cv::resize(m_img, m_img, dsize, 0, 0, cv::INTER_AREA);
|
||||
}*/
|
||||
for(uint32_t x = 0; x < w; x++)//iterate over destination X
|
||||
{
|
||||
float p[4] = {0.0f};
|
||||
uint32_t xx = x * oldw / w;//calculate source rect
|
||||
uint32_t yy = y * oldh / h;
|
||||
uint32_t xe = std::min((x + 1) * oldw / w, oldw - 1);
|
||||
uint32_t ye = std::min((y + 1) * oldh / h, oldh - 1);
|
||||
for(uint32_t o = yy; o <= ye; o++)//iterate over source Y
|
||||
{
|
||||
float cy = o * sy - y;
|
||||
if(cy < 0.0f)cy = sy + cy;
|
||||
else if(sy + cy > 1.0f)cy = 1.0f - cy;
|
||||
else cy = sy;
|
||||
if(yy==ye)cy = 1.0f;
|
||||
for(uint32_t i = xx; i <= xe; i++)//iterate over source X
|
||||
{
|
||||
float cx = i * sx - x;
|
||||
if(cx < 0.0f)cx = sx + cx;
|
||||
else if(sx + cx > 1.0f)cx = 1.0f - cx;
|
||||
else cx = sx;
|
||||
if(xx==xe)cx = 1.0f;
|
||||
for(uint32_t z = 0; z < ch; z++)
|
||||
p[z] += in[(o * oldw + i) * ch + z] * cy * cx;
|
||||
}
|
||||
}
|
||||
for(uint32_t z = 0; z < ch; z++)
|
||||
if constexpr(std::is_floating_point<T>::value)
|
||||
out[(y * w + x) * ch + z] = p[z];
|
||||
else
|
||||
out[(y * w + x) * ch + z] = std::clamp(std::round(p[z]), 0.0f, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RawImage::resize(uint32_t w, uint32_t h)
|
||||
{
|
||||
if(!valid())return;
|
||||
|
||||
std::unique_ptr<PixelType[]> old_pixels = std::move(m_pixels);
|
||||
uint32_t oldw = m_width;
|
||||
uint32_t oldh = m_height;
|
||||
m_thumbAspect = (float)m_width / m_height;
|
||||
|
||||
allocate(w, h, m_channels, m_type);
|
||||
|
||||
switch(m_type)
|
||||
{
|
||||
case RawImage::UINT8:
|
||||
boxResample<uint8_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
|
||||
break;
|
||||
case RawImage::UINT16:
|
||||
boxResample<uint16_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
|
||||
break;
|
||||
case RawImage::FLOAT32:
|
||||
boxResample<float>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
|
||||
break;
|
||||
default:
|
||||
qWarning() << "Resizing format not supported";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<float, float> RawImage::unitScale() const
|
||||
{
|
||||
float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4);
|
||||
float max = *std::max_element(m_stats.m_max, m_stats.m_max + 4);
|
||||
|
||||
if(m_origType == UINT32)
|
||||
{
|
||||
min /= (float)UINT32_MAX;
|
||||
max /= (float)UINT32_MAX;
|
||||
}
|
||||
|
||||
if(min < 0.0f || max > 1.0f)
|
||||
return {1.0f / (max - min), min / (max - min)};
|
||||
else
|
||||
return {1.0f, 0.0f};
|
||||
}
|
||||
|
||||
void RawImage::flip()
|
||||
{
|
||||
std::unique_ptr<PixelType[]> tmp = std::move(m_pixels);
|
||||
allocate(m_width, m_height, m_channels, m_type);
|
||||
uint32_t rowSize = m_width * m_ch * typeSize(m_type);
|
||||
for(uint32_t i=0; i<m_height; i++)
|
||||
std::memcpy(m_pixels.get() + rowSize * (m_height - i - 1), tmp.get() + rowSize * i, rowSize);
|
||||
}
|
||||
|
||||
std::shared_ptr<RawImage> RawImage::fromPlanar(const RawImage &img)
|
||||
@@ -673,3 +774,8 @@ std::vector<RawImage> RawImage::split() const
|
||||
|
||||
return planes;
|
||||
}
|
||||
|
||||
bool RawImage::valid() const
|
||||
{
|
||||
return m_width > 0 && m_height > 0;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
extern int THUMB_SIZE;
|
||||
extern int THUMB_SIZE_BORDER;
|
||||
extern int THUMB_SIZE_BORDER_Y;
|
||||
extern bool QUALITY_RESIZE;
|
||||
|
||||
class Peak
|
||||
{
|
||||
@@ -97,12 +98,15 @@ public:
|
||||
void convertToGLFormat();
|
||||
float thumbAspect() const;
|
||||
bool pixel(int x, int y, double &r, double &g, double &b) const;
|
||||
void downscaleTo(uint32_t size);
|
||||
void resize(uint32_t w, uint32_t h);
|
||||
std::pair<float, float> unitScale() const;
|
||||
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);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "rawimage.h"
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <x86intrin.h>
|
||||
|
||||
template<typename T, int ch>
|
||||
@@ -109,3 +111,5 @@ 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
|
||||
|
||||
@@ -1,32 +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>
|
||||
<file>falsecolor.png</file>
|
||||
<file>link.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="en">
|
||||
<file alias="help">about/help_en</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="sk">
|
||||
<file alias="help">about/help_sk</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="fr">
|
||||
<file alias="help">about/help_fr</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 454 B After Width: | Height: | Size: 454 B |
@@ -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 |
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
extern int DEFAULT_WIDTH;
|
||||
extern double SATURATION;
|
||||
extern int FILTERING;
|
||||
|
||||
class EvenNumber : public QSpinBox
|
||||
{
|
||||
@@ -39,7 +40,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
||||
QSettings settings;
|
||||
|
||||
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->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->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->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("Thumbnails size"), m_thumSize);
|
||||
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(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;
|
||||
DEFAULT_WIDTH = settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt();
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -102,6 +123,11 @@ void SettingsDialog::saveSettings()
|
||||
settings.setValue("settings/preloadimagecount", m_preloadImages->value());
|
||||
settings.setValue("settings/dontusenativedialogs", m_useNativeDialog->isChecked());
|
||||
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;
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked());
|
||||
if(DEFAULT_WIDTH != m_preloadImages->value())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QDialog>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
@@ -19,8 +20,11 @@ private:
|
||||
|
||||
QSpinBox *m_preloadImages;
|
||||
QSpinBox *m_thumSize;
|
||||
QDoubleSpinBox *m_slideShowTime;
|
||||
QDoubleSpinBox *m_saturation;
|
||||
QCheckBox *m_useNativeDialog;
|
||||
QCheckBox *m_qualityThumbnail;
|
||||
QComboBox *m_filtering;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D qt_Texture0;
|
||||
uniform ivec2 firstRed;
|
||||
in vec2 qt_TexCoord0;
|
||||
in vec2 center;
|
||||
layout(location = 0) out vec4 color;
|
||||
@@ -11,7 +12,7 @@ void main(void)
|
||||
{
|
||||
ivec2 texSize = textureSize(qt_Texture0, 0);
|
||||
ivec2 icenter = ivec2(center);
|
||||
ivec2 alternate = icenter % 2;
|
||||
ivec2 alternate = (icenter + firstRed) % 2;
|
||||
|
||||
// cross, checker, theta, phi
|
||||
const vec4 kA = vec4(-1.0, -1.5, 0.5, -1.0) / 8.0;
|
||||
|
||||
@@ -7,6 +7,7 @@ uniform bool bw;
|
||||
uniform bool invert;
|
||||
uniform bool srgb;
|
||||
uniform bool false_color;
|
||||
uniform int filtering;
|
||||
in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
@@ -35,7 +36,8 @@ vec3 falsecolor(float color)
|
||||
vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0));//red
|
||||
color *= 5.0;
|
||||
int i = int(color);
|
||||
return mix(pallete[i], pallete[i+1], fract(color));
|
||||
float f = fract(color);
|
||||
return mix(pallete[i], pallete[i+1], f);// * (f * 0.5 + 0.5);
|
||||
}
|
||||
|
||||
vec3 checker()
|
||||
@@ -44,9 +46,104 @@ vec3 checker()
|
||||
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)
|
||||
{
|
||||
color = texture(qt_Texture0, qt_TexCoord0);
|
||||
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);
|
||||
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;
|
||||
color = MTF(color, vec4(mtf_param[0], 0.0), vec4(mtf_param[1], 0.5), vec4(mtf_param[2], 1.0));
|
||||
|
||||
@@ -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>
|
||||
@@ -4,6 +4,10 @@
|
||||
<launchable type="desktop-id">space.nouspiro.tenmon.desktop</launchable>
|
||||
<name>Tenmon</name>
|
||||
<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>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<description>
|
||||
@@ -11,7 +15,7 @@
|
||||
<ul>
|
||||
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||
<li>RAW CR2, DNG, NEF</li>
|
||||
<li>RAW CR2/CR3, DNG, NEF</li>
|
||||
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
|
||||
</ul>
|
||||
<p>Features of application:</p>
|
||||
@@ -43,12 +47,35 @@
|
||||
<url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Main window with image</caption>
|
||||
<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>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<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>
|
||||
|
||||
@@ -105,43 +105,44 @@ void STFSlider::paintEvent(QPaintEvent *event)
|
||||
|
||||
void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if(std::abs(m_blackPoint*width() - event->x()) < 5 ||
|
||||
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - event->x()) < 5 ||
|
||||
std::abs(m_whitePoint*width() - event->x()) < 5)
|
||||
const qreal x = event->position().x();
|
||||
if(std::abs(m_blackPoint*width() - 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);
|
||||
else
|
||||
unsetCursor();
|
||||
|
||||
qreal x = (qreal)event->x()/width();
|
||||
qreal xw = x/width();
|
||||
if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
|
||||
{
|
||||
m_fineTune = true;
|
||||
m_fineTuneX = x;
|
||||
m_fineTuneX = xw;
|
||||
}
|
||||
if(!(event->modifiers() & Qt::ShiftModifier) && m_fineTune)
|
||||
m_fineTune = false;
|
||||
|
||||
if(m_fineTune)
|
||||
{
|
||||
x = m_fineTuneX + (x - m_fineTuneX) * 0.2;
|
||||
xw = m_fineTuneX + (xw - m_fineTuneX) * 0.2;
|
||||
}
|
||||
|
||||
switch(m_grabbed)
|
||||
{
|
||||
case 0:
|
||||
m_blackPoint = clamp(x);
|
||||
m_blackPoint = clamp(xw);
|
||||
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;
|
||||
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);
|
||||
QToolTip::showText(event->globalPos(), QString::number(m_midPoint), this);
|
||||
QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_midPoint), this);
|
||||
break;
|
||||
case 2:
|
||||
m_whitePoint = clamp(x);
|
||||
m_whitePoint = clamp(xw);
|
||||
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;
|
||||
}
|
||||
if(m_grabbed >= 0)
|
||||
@@ -153,17 +154,18 @@ void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
void STFSlider::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
const qreal x = event->position().x();
|
||||
if(event->modifiers() & Qt::ShiftModifier)
|
||||
{
|
||||
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;
|
||||
else if(std::abs(m_blackPoint*width() - event->x()) < 5)
|
||||
else if(std::abs(m_blackPoint*width() - x) < 5)
|
||||
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;
|
||||
else
|
||||
m_grabbed = -1;
|
||||
|
||||
@@ -120,12 +120,15 @@ void StretchToolbar::stretchImage(Image *img)
|
||||
mad = stats.m_mad[i];
|
||||
max = stats.m_max[i];
|
||||
median /= img->rawImage()->norm();
|
||||
bool a = median > 0.5 ? true : false;
|
||||
mad /= img->rawImage()->norm();
|
||||
max /= img->rawImage()->norm();
|
||||
float bp = median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA;
|
||||
float mid = MTF(median - bp, TARGET_BACKGROUND);
|
||||
max = 1.0f;// /= img->rawImage()->norm();
|
||||
float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
||||
if(a && mad != 0.0f)
|
||||
max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
||||
float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median);
|
||||
m_mtfParam.blackPoint[i-o] = bp;
|
||||
m_mtfParam.midPoint[i-o] = mid / max;
|
||||
m_mtfParam.midPoint[i-o] = mid;// / max;
|
||||
m_mtfParam.whitePoint[i-o] = max;
|
||||
bp2 += bp;
|
||||
mid2 += mid;
|
||||
|
||||
@@ -8,6 +8,89 @@
|
||||
<translation>About Tenmon</translation>
|
||||
</message>
|
||||
</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't exist or is not writable</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DataBaseView</name>
|
||||
<message>
|
||||
@@ -165,6 +248,10 @@
|
||||
<source>R:%1 G:%2 B:%3</source>
|
||||
<translation>R:%1 G:%2 B:%3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to load image</source>
|
||||
<translation>Failed to load image</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
@@ -408,6 +495,30 @@
|
||||
<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>
|
||||
<name>MarkedFiles</name>
|
||||
@@ -537,6 +648,38 @@ For RAW files you may set 22%</translation>
|
||||
<source>Saturation</source>
|
||||
<translation>Saturated</translation>
|
||||
</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>
|
||||
<name>StretchToolbar</name>
|
||||
|
||||
@@ -8,6 +8,89 @@
|
||||
<translation>A propos de Tenmon</translation>
|
||||
</message>
|
||||
</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't exist or is not writable</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DataBaseView</name>
|
||||
<message>
|
||||
@@ -165,6 +248,10 @@
|
||||
<source>R:%1 G:%2 B:%3</source>
|
||||
<translation>R:%1 G:%2 B:%3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to load image</source>
|
||||
<translation>Échec du chargement de l'image</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
@@ -408,6 +495,30 @@
|
||||
<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>
|
||||
<name>MarkedFiles</name>
|
||||
@@ -537,6 +648,38 @@ Pour les fichiers RAW, vous pouvez définir 22 %</translation>
|
||||
<source>Saturation</source>
|
||||
<translation>Saturé</translation>
|
||||
</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'images</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Image interpolation</source>
|
||||
<translation>Interpolation d'images</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>StretchToolbar</name>
|
||||
|
||||
@@ -8,6 +8,89 @@
|
||||
<translation>O Tenmon</translation>
|
||||
</message>
|
||||
</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't exist or is not writable</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DataBaseView</name>
|
||||
<message>
|
||||
@@ -166,6 +249,10 @@
|
||||
<source>R:%1 G:%2 B:%3</source>
|
||||
<translation>R:%1 G:%2 B:%3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to load image</source>
|
||||
<translation>Zlyhalo načítanie obrázka</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
@@ -409,6 +496,30 @@
|
||||
<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>
|
||||
<name>MarkedFiles</name>
|
||||
@@ -538,6 +649,34 @@ Pre RAW súbory možno treba nastaviť 22%</translation>
|
||||
<source>Saturation</source>
|
||||
<translation>Saturované</translation>
|
||||
</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>
|
||||
<name>StretchToolbar</name>
|
||||
|
||||