Compare commits

...

14 Commits

Author SHA1 Message Date
nou fe3e5f66be Release 20250318 2025-03-18 17:29:19 +01:00
nou 6fd17fbdf5 Use only single database 2025-03-18 14:46:08 +01:00
nou f30dd2a520 Add generating thumbnails from cmd line 2025-03-17 11:08:18 +01:00
nou 21675d9479 Add install thumbnailer button 2025-03-17 11:07:42 +01:00
nou f669baa8a6 Drop lib prefix for dll 2025-03-10 11:26:43 +01:00
nou c317012c99 Configurable threshold 2025-03-10 03:05:44 -07:00
nou d0dbef20c7 Fix handling of inf and nan in TFloat16 2025-03-10 11:03:17 +01:00
nou bd45900821 Finish standalone thumbnailer under 2025-03-10 11:01:45 +01:00
nou 96a89bff92 fixup! Fix images that have values outside of 0-1 range 2025-03-09 17:51:26 +01:00
nou c05fc36ee3 Add FITS to thumbnailer 2025-03-06 18:36:48 +01:00
nou 05b0aa9a2f Add custom implementation of half float 2025-03-06 15:35:12 +01:00
nou 7b70b6cce5 Use texture2DArray for colormap to work with OpenGL ES 2025-03-05 21:14:43 +01:00
nou 5150ec5639 Fix images that have values outside of 0-1 range 2025-03-04 16:09:33 +01:00
nou 79529552d9 Thumbnailer for windows 2025-03-04 06:53:47 -08:00
29 changed files with 1010 additions and 87 deletions
+2
View File
@@ -54,6 +54,8 @@ set(TENMON_SRC
statusbar.cpp statusbar.h
stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h
tfloat16.h
thumbnailer/genthumbnail.cpp thumbnailer/genthumbnail.h
)
qt_add_resources(TENMON_SRC resources/resources.qrc)
+3 -2
View File
@@ -67,7 +67,8 @@ void BatchProcessing::scanScriptDir()
if(idx>=0)_ui->scriptsList->setCurrentRow(idx);
}
BatchProcessing::BatchProcessing(QWidget *parent) : QDialog(parent)
BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(parent)
, _database(database)
{
_ui = new Ui::BatchProcessing;
_ui->setupUi(this);
@@ -200,7 +201,7 @@ void BatchProcessing::runScript()
auto selectedItems = _ui->scriptsList->selectedItems();
if(selectedItems.size())
{
_engineThread = new Script::ScriptEngineThread(this);
_engineThread = new Script::ScriptEngineThread(_database, this);
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
QStringList paths;
+4 -1
View File
@@ -7,6 +7,8 @@
namespace Ui { class BatchProcessing; }
class Database;
class BatchProcessing : public QDialog
{
Q_OBJECT
@@ -15,10 +17,11 @@ class BatchProcessing : public QDialog
QFileSystemWatcher _fileWatcher;
Script::ScriptEngineThread *_engineThread = nullptr;
QColor _textColor;
Database *_database;
private slots:
void scanScriptDir();
public:
explicit BatchProcessing(QWidget *parent = nullptr);
explicit BatchProcessing(Database *database, QWidget *parent = nullptr);
~BatchProcessing();
protected:
void closeEvent(QCloseEvent *event);
+12 -5
View File
@@ -15,7 +15,7 @@ bool Database::init(const QLatin1String &connectionName)
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
if(!dir.mkpath("."))
return false;
@@ -27,7 +27,7 @@ bool Database::init(const QLatin1String &connectionName)
{
QSqlQuery query(database);
query.exec("PRAGMA foreign_keys = ON");
int version = checkVersion();
int version = checkVersion(database);
if(version == 0)
{
query.exec("PRAGMA user_version = 1");
@@ -76,6 +76,14 @@ bool Database::init(const QLatin1String &connectionName)
}
qDebug() << error.text();
}
else
{
qDebug() << "Failed to open database" << connectionName;
}
}
else
{
qDebug() << "Database is invalid";
}
return false;
}
@@ -146,10 +154,9 @@ bool Database::checkError(QSqlQuery &query)
}
}
int Database::checkVersion()
int Database::checkVersion(QSqlDatabase &db)
{
QSqlDatabase db = QSqlDatabase::database();
QSqlQuery query("PRAGMA user_version");
QSqlQuery query("PRAGMA user_version", db);
if(query.next())
return query.value(0).toInt();
return -1;
+2 -1
View File
@@ -10,6 +10,7 @@
class Database : public QObject
{
Q_OBJECT
QSqlDatabase database;
QSqlQuery m_markQuery;
QSqlQuery m_unmarkQuery;
QSqlQuery m_isMarkedQuery;
@@ -40,7 +41,7 @@ protected:
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
bool indexFile(const QFileInfo &file);
bool checkError(QSqlQuery &query);
int checkVersion();
int checkVersion(QSqlDatabase &db);
signals:
void databaseChanged();
};
+17 -5
View File
@@ -113,6 +113,7 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
, m_analyzeLevel(None)
, m_database(database)
, m_nameFilter(nameFilter)
, m_fileSuffix(nameFilter)
{
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
@@ -170,8 +171,8 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile, bool
};
scanDir(path);
qDebug() << absolutePaths.size();
setFiles(absolutePaths, m_liveMode ? absolutePaths.first() : currentFile);
//qDebug() << absolutePaths.size();
setFilesPrivate(absolutePaths, m_liveMode ? absolutePaths.first() : currentFile);
m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
m_fileSystemWatcher.addPath(path);
@@ -192,6 +193,17 @@ void ImageRingList::setFile(const QString &file)
}
}
void ImageRingList::setFiles(QStringList files)
{
QRegularExpression reg("(" + m_fileSuffix.join("|") + ")");
files.removeIf([&reg](const QString &file){
QFileInfo info(file);
auto match = reg.match(info.suffix());
return !match.hasMatch() || !info.exists() || !info.isReadable() || !info.isFile();
});
setFilesPrivate(files);
}
ImagePtr ImageRingList::currentImage()
{
if(m_images.size())
@@ -237,11 +249,11 @@ void ImageRingList::decrement()
void ImageRingList::setMarked()
{
QStringList files = m_database->getMarkedFiles();
std::remove_if(files.begin(), files.end(), [](const QString &file){
files.removeIf([](const QString &file){
QFileInfo info(file);
return !info.exists() || !info.isReadable();
});
setFiles(files);
setFilesPrivate(files);
}
void ImageRingList::setLiveMode(bool live)
@@ -474,7 +486,7 @@ void ImageRingList::toggleSlideshow(bool start)
}
}
void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
void ImageRingList::setFilesPrivate(const QStringList files, const QString &currentFile)
{
m_loadPool->clear();
m_thumbPool->clear();
+3 -1
View File
@@ -68,6 +68,7 @@ class ImageRingList : public QAbstractItemModel
QThreadPool *m_thumbPool;
Database *m_database;
QStringList m_nameFilter;
QStringList m_fileSuffix;
QTimer *m_slideShowTimer;
QTimer *m_dirChangeDelay;
QString m_currentDir;
@@ -76,6 +77,7 @@ public:
~ImageRingList() override;
bool setDir(const QString path, const QString &currentFile = QString(), bool recursive = false);
void setFile(const QString &file);
void setFiles(QStringList files);
ImagePtr currentImage();
void setLiveMode(bool live);
void setCalculateStats(bool stats);
@@ -105,7 +107,7 @@ public slots:
void decrement();
void setMarked();
protected:
void setFiles(const QStringList files, const QString &currentFile = QString());
void setFilesPrivate(const QStringList files, const QString &currentFile = QString());
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
QList<ImagePtr>::iterator decrement(QList<ImagePtr>::iterator iter);
signals:
+3 -3
View File
@@ -392,9 +392,9 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
float iptr;
float fy = std::modf((y + oy) * iscale, &iptr);
int64_t py = iptr;
int64_t w = py * rawImage->widthBytes();
int64_t w = py * rawImage->widthSamples();
int64_t w2 = w;
if(py+1 < imgHeight)w2 += rawImage->widthBytes();
if(py+1 < imgHeight)w2 += rawImage->widthSamples();
if(py >= imgHeight)break;
for(int64_t x = std::max((int64_t)0, -ox); x < width; x++)
@@ -777,7 +777,7 @@ void ImageWidgetGL::initializeGL()
m_lut->bind(2);
QImage colormap = loadColormap();
m_colormap = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target1DArray);
m_colormap = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2DArray);
m_colormap->setSize(colormap.width());
m_colormap->setLayers(colormap.height());
m_colormap->setFormat(QOpenGLTexture::RGBA8_UNorm);
+1 -6
View File
@@ -293,7 +293,6 @@ void ConvertRunable::run()
// if nothing else try QImage
{
QImage::Format format = QImage::Format_Invalid;
int width = rawimage->widthBytes();
switch(rawimage->type())
{
@@ -306,7 +305,6 @@ void ConvertRunable::run()
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
width *= 2;
break;
case RawImage::FLOAT16:
case RawImage::FLOAT32:
@@ -316,15 +314,12 @@ void ConvertRunable::run()
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
width *= 2;
break;
}
if(format == QImage::Format_Invalid)return;
QImage qimage(rawimage->width(), rawimage->height(), format);
for(uint32_t i=0; i < rawimage->height(); i++)
std::memcpy(qimage.scanLine(i), rawimage->data(i), width);
QImage qimage((const uchar*)rawimage->data(), rawimage->width(), rawimage->height(), rawimage->widthBytes(), format);
qimage.save(m_outfile);
}
}
+51 -4
View File
@@ -2,7 +2,9 @@
#include <QApplication>
#include <QSurfaceFormat>
#include <QTranslator>
#include <QCommandLineParser>
#include <stdlib.h>
#include "thumbnailer/genthumbnail.h"
int main(int argc, char *argv[])
{
@@ -15,12 +17,39 @@ int main(int argc, char *argv[])
#else
bool useGLES = true;
#endif
QCommandLineParser cmd;
cmd.addOption({"gl", "Use desktop OpenGL. This is default on x86 and MacOS platform."});
cmd.addOption({"gles", "Use OpenGL ES. This is default on ARM platform."});
cmd.addOption({{"thumb", "thumbnail"}, "Generate thumbnail and save it to path.", "path"});
cmd.addOption({{"s", "size"}, "Size of the thumbnails in pixels (default: 128)", "size", "128"});
cmd.addPositionalArgument("file", "File to open");
cmd.addHelpOption();
QStringList cmdArgs;
for(int i = 0; i < argc; i++)
cmdArgs.append(argv[i]);
cmd.process(cmdArgs);
if(cmd.isSet("gl"))
useGLES = false;
if(cmd.isSet("gles"))
useGLES = true;
if(cmd.isSet("thumb"))
{
if(std::strcmp("-gl", argv[i]) == 0)
useGLES = false;
if(std::strcmp("-gles", argv[i]) == 0)
useGLES = true;
QCoreApplication app(argc, argv);
QStringList files = cmd.positionalArguments();
if(files.size() == 0)
return 1;
QString thumb = cmd.value("thumb");
int size = 128;
bool ok;
int size2 = cmd.value("s").toInt(&ok);
if(ok)
size = size2;
return generateThumbnail(files.front(), thumb, size);
}
QSurfaceFormat format;
@@ -39,6 +68,7 @@ int main(int argc, char *argv[])
}
QSurfaceFormat::setDefaultFormat(format);
QApplication a(argc, argv);
a.setOrganizationName("nou");
a.setApplicationName("Tenmon");
@@ -54,5 +84,22 @@ int main(int argc, char *argv[])
MainWindow w;
w.show();
if(!cmd.positionalArguments().isEmpty())
{
QStringList files = cmd.positionalArguments();
QStringList paths;
for(auto &arg : files)
{
QUrl url(arg);
QFileInfo info(url.isLocalFile() ? url.toLocalFile() : arg);
if(info.exists())
paths.append(info.canonicalFilePath());
}
if(paths.size() == 1)
w.loadFile(paths.front());
else if(paths.size() > 1)
w.loadFiles(paths);
}
return a.exec();
}
+6 -17
View File
@@ -167,7 +167,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex()));
fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV);
fileMenu->addAction(tr("Batch processing"), Qt::Key_B | Qt::CTRL, [this](){
BatchProcessing *batchProcessing = new BatchProcessing(this);
BatchProcessing *batchProcessing = new BatchProcessing(m_database, this);
batchProcessing->exec();
delete batchProcessing;
});
@@ -327,22 +327,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
_lastDir = settings.value("mainwindow/lastdir", _lastDir).toString();
QStringList args = QCoreApplication::arguments();
args.removeFirst();
for(auto &arg : args)
{
QUrl url(arg);
QFileInfo info(url.isLocalFile() ? url.toLocalFile() : arg);
if(info.exists())
{
m_ringList->setFile(info.canonicalFilePath());
updateWindowTitle();
_lastDir = info.absoluteDir().absolutePath();
settings.setValue("mainwindow/lastdir", _lastDir);
break;
}
}
m_image->setFocus();
// workaround for nasty wayland backend bug https://bugreports.qt.io/browse/QTBUG-87332
@@ -565,6 +549,11 @@ void MainWindow::loadFile(const QString &path)
}
}
void MainWindow::loadFiles(const QStringList &paths)
{
m_ringList->setFiles(paths);
}
void MainWindow::loadFile(int row)
{
m_ringList->loadFile(row);
+2 -1
View File
@@ -41,11 +41,12 @@ protected:
void closeEvent(QCloseEvent *event) override;
void copyOrMove(bool copy);
void copyOrMove(bool copy, const QString &dest);
protected slots:
public slots:
void socketNotify();
void updateWindowTitle();
void loadFile();
void loadFile(const QString &path);
void loadFiles(const QStringList &paths);
void loadFile(int row);
void loadDir();
void indexDir();
+93 -13
View File
@@ -1,6 +1,7 @@
#include "rawimage.h"
#include <cstring>
#include <lcms2.h>
#include <algorithm>
#ifndef NO_QT
#include <QDebug>
#include <QElapsedTimer>
@@ -8,9 +9,16 @@
#include <QColorSpace>
using F16 = qfloat16;
#else
#include <algorithm>
#define __STDC_WANT_IEC_60559_TYPES_EXT__
#include <float.h>
#ifdef FLT16_MAX
using F16 = _Float16;
#endif
#else
#include "tfloat16.h"
using F16 = TFloat16;// this is only for MXE
#endif // FLT16_MAX
#endif // NO_QT
int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138;
@@ -190,7 +198,7 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
auto findMedian = [histSize](std::vector<uint32_t> &histogram, size_t n) -> size_t
{
size_t histSum = 0;
for(size_t o=0; o < histSize; o++)
for(size_t o=1; o < histSize; o++)
{
histSum += histogram[o];
if(histSum >= n/2)
@@ -231,6 +239,57 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
}
}
if constexpr(std::is_floating_point_v<T>)
{
T mmin = *std::min_element(min, min + 4);
T mmax = *std::max_element(max, max + 4);
T a = 1.0 / (mmax - mmin);
T b = -mmin / (mmax - mmin);
auto histFunc = [&](T d, int x)
{
uint16_t idx = std::clamp((T)(d * a + b) * histSize, (T)0.0, (T)65535.0);
histogram[x][idx]++;
};
// calculate histogram again
if(mmin < 0.0 || mmax > 1.0)
{
for(int i=0; i<4; i++)
{
histogram[i].clear();
histogram[i].resize(histSize, 0);
}
for(size_t i = 0; i < n; i++)
{
histFunc(data[i*ch], 0);
if constexpr(ch >= 3)
{
histFunc(data[i*ch + 1], 1);
histFunc(data[i*ch + 2], 2);
}
}
if constexpr(ch == 1)
{
size_t h = (n / w) & (SIZE_MAX-1);
w &= (SIZE_MAX-1);
for(size_t y=0; y<h; y+=2)
{
for(size_t x=0; x<w; x+=2)
{
histFunc(data[y*w+x], 1);
histFunc(data[y*w+x+1], 2);
histFunc(data[(y+1)*w+x], 2);
histFunc(data[(y+1)*w+x+1], 3);
}
}
}
}
}
for(int i = 0; i < 4; i++)
{
stats.m_min[i] = min[i];
@@ -240,7 +299,8 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
double sum2 = (double)sum[i] * sum[i];
stats.m_stdDev[i] = std::sqrt((sumSq[i] - sum2 / na[i]) / (na[i] - 1));
uint32_t median = findMedian(histogram[i], na[i]);
size_t naclip = na[i] - histogram[i][0];
uint32_t median = findMedian(histogram[i], naclip);
stats.m_median[i] = median;
std::vector<uint32_t> madHist(histSize, 0);
madHist[0] = histogram[i][median];
@@ -249,7 +309,7 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
if(median + o < histSize)madHist[o] += histogram[i][median + o];
if(o <= median)madHist[o] += histogram[i][median - o];
}
stats.m_mad[i] = findMedian(madHist, na[i]);
stats.m_mad[i] = findMedian(madHist, naclip);
if constexpr(!std::numeric_limits<T>::is_integer)
{
stats.m_median[i] /= 65535.0;
@@ -344,6 +404,11 @@ uint32_t RawImage::norm() const
}
uint32_t RawImage::widthBytes() const
{
return m_ch * m_width * typeSize(m_type);
}
uint32_t RawImage::widthSamples() const
{
return m_ch * m_width;
}
@@ -414,13 +479,25 @@ void RawImage::convertToThumbnail()
if(m_channels == 1)
{
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2] * scale);
if(scale == 1.0f)
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2]);
else
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2] * scale);
}
else
{
out[idx] = (F16)(in[idx2] * scale);
out[idx + 1] = (F16)(in[idx2 + 1] * scale);
out[idx + 2] = (F16)(in[idx2 + 2] * scale);
if(scale == 1.0f)
{
out[idx] = (F16)(in[idx2]);
out[idx + 1] = (F16)(in[idx2 + 1]);
out[idx + 2] = (F16)(in[idx2 + 2]);
}
else
{
out[idx] = (F16)(in[idx2] * scale);
out[idx + 1] = (F16)(in[idx2 + 1] * scale);
out[idx + 2] = (F16)(in[idx2 + 2] * scale);
}
}
out[idx + 3] = (F16)1.0f;
}
@@ -496,9 +573,9 @@ void convertType2(size_t size, const T *src, U *dst)
if constexpr(std::is_integral_v<T> && (std::is_floating_point_v<U> || std::is_same_v<U, F16>))
{
U scale = (U)(1.0 / (double)std::numeric_limits<T>::max());
float scale = (float)(1.0 / (double)std::numeric_limits<T>::max());
for(size_t i = 0; i < size; i++)
dst[i] = (U)src[i] * scale;
dst[i] = (U)(src[i] * scale);
}
}
@@ -824,7 +901,7 @@ std::pair<float, float> RawImage::unitScale() const
}
if(min < 0.0f || max > 1.0f)
return {1.0f / (max - min), min / (max - min)};
return {1.0f / (max - min), -min / (max - min)};
else
return {1.0f, 0.0f};
}
@@ -1112,11 +1189,14 @@ void RawImage::applySTF(const MTFParam &mtfParams)
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)
s = (float)std::numeric_limits<std::remove_reference_t<decltype(*src)>>::max();
auto unit = unitScale();
float iscale = 1.0f / s;
size_t len = size() * m_ch;
for(size_t i = 0; i < len; i++)
{
float x = src[i] * iscale;
float x;
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
else x = src[i] * unit.first + unit.second;
x = (x - mtfParams.blackPoint[0]) / (mtfParams.whitePoint[0] - mtfParams.blackPoint[0]);
x = std::clamp(x, 0.0f, 1.0f);
x = ((mtfParams.midPoint[0] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[0] - 1.0f) * x - mtfParams.midPoint[0]);
+1
View File
@@ -98,6 +98,7 @@ public:
DataType type() const;
uint32_t norm() const;
uint32_t widthBytes() const;
uint32_t widthSamples() const;
void* data();
const void* data() const;
void* data(uint32_t row, uint32_t col = 0);
+13 -9
View File
@@ -17,9 +17,9 @@
namespace Script
{
ScriptEngine::ScriptEngine(BatchProcessing *parent)
ScriptEngine::ScriptEngine(Database *database, BatchProcessing *parent)
: _jsEngine(new QJSEngine(this))
, _database(new Database(this))
, _database(database)
, _parent(parent)
, _pool(new QThreadPool(this))
{
@@ -27,7 +27,6 @@ ScriptEngine::ScriptEngine(BatchProcessing *parent)
_jsEngine->globalObject().setProperty("core", core);
QJSValue fitsRecordObject = _jsEngine->newQMetaObject(&FITSRecordModify::staticMetaObject);
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
_database->init(QLatin1String("scriptengine"));
_semaphore.release(_pool->maxThreadCount());
_pool->setThreadPriority(QThread::LowPriority);
@@ -73,17 +72,22 @@ void ScriptEngine::log(const QString &message)
void ScriptEngine::mark(File *file)
{
_database->mark(file->absoluteFilePath());
QString path = file->absoluteFilePath();
QMetaObject::invokeMethod(_database, [this, path](){ _database->mark(path); }, Qt::QueuedConnection);
}
void ScriptEngine::unmark(File *file)
{
_database->unmark(file->absoluteFilePath());
QString path = file->absoluteFilePath();
QMetaObject::invokeMethod(_database, [this, path](){ _database->unmark(path); }, Qt::QueuedConnection);
}
bool ScriptEngine::isMarked(const File *file) const
bool ScriptEngine::isMarked(const File *file)
{
return _database->isMarked(file->absoluteFilePath());
bool ret;
QString path = file->absoluteFilePath();
QMetaObject::invokeMethod(_database, [this, path](){ return _database->isMarked(path); }, Qt::BlockingQueuedConnection, &ret);
return ret;
}
void ScriptEngine::setMaxThread(int maxthread)
@@ -765,11 +769,11 @@ QJSValue File::extractStars(bool hfr)
}
#endif // PLATESOLVER
ScriptEngineThread::ScriptEngineThread(BatchProcessing *parent) : QObject(parent)
ScriptEngineThread::ScriptEngineThread(Database *database, BatchProcessing *parent) : QObject(parent)
{
_thread = new QThread();
_thread->setObjectName("ScriptEngine");
_engine = new ScriptEngine(parent);
_engine = new ScriptEngine(database, parent);
_engine->moveToThread(_thread);
connect(_engine, &ScriptEngine::finished, _thread, &QThread::quit);
connect(_engine, &ScriptEngine::newMessage, this, &ScriptEngineThread::newMessage);
+3 -3
View File
@@ -31,7 +31,7 @@ class ScriptEngine : public QObject
QList<QPair<QString, QString>> _paths;
Solver *_solver = nullptr;
public:
explicit ScriptEngine(BatchProcessing *parent = nullptr);
explicit ScriptEngine(Database *database, BatchProcessing *parent = nullptr);
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir);
void reportError(const QString &message);
const QString& outputDir() const;
@@ -40,7 +40,7 @@ public:
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;
Q_INVOKABLE bool isMarked(const File *file);
Q_INVOKABLE void setMaxThread(int maxthread);
Q_INVOKABLE void sync();
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
@@ -71,7 +71,7 @@ class ScriptEngineThread : public QObject
QThread *_thread;
ScriptEngine *_engine;
public:
ScriptEngineThread(BatchProcessing *parent = nullptr);
ScriptEngineThread(Database *database, BatchProcessing *parent = nullptr);
~ScriptEngineThread();
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir);
void start();
+34
View File
@@ -4,6 +4,12 @@
#include <QLabel>
#include <QSettings>
#include <QApplication>
#include <QProcess>
#include <QCoreApplication>
#include <QFileInfo>
#include <QMessageBox>
#include <QDir>
#include <QPushButton>
#include "rawimage.h"
extern int DEFAULT_WIDTH;
@@ -80,6 +86,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
m_bestFit->setToolTip(tr("Set Best Fit zoom level when opening new image."));
m_bestFit->setChecked(BESTFIT);
layout->addRow(tr("Image preload count"), m_preloadImages);
layout->addRow(tr("Thumbnails size"), m_thumSize);
layout->addRow(tr("Saturation"), m_saturation);
@@ -88,8 +95,16 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
layout->addRow(m_qualityThumbnail);
layout->addRow(m_useNativeDialog);
layout->addRow(m_bestFit);
#ifdef Q_OS_WIN64
QPushButton *installThumbnailer = new QPushButton(tr("Install"), this);
installThumbnailer->setToolTip(tr("This will install thumnail generation for FITS and XISF files in File Explorer"));
connect(installThumbnailer, &QPushButton::clicked, this, &SettingsDialog::installThumbnailer);
layout->addRow(tr("Install thumbnailer"), installThumbnailer);
#endif
//layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart.")));
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
@@ -123,6 +138,25 @@ bool SettingsDialog::loadThumbsizes()
return OLD_THUMB_SIZE != THUMB_SIZE;
}
void SettingsDialog::installThumbnailer()
{
#ifdef Q_OS_WIN64
QString path = QCoreApplication::instance()->applicationDirPath() + "/tenmonthumbnailer.dll";
if(!QFileInfo::exists(path))
{
QMessageBox::critical(this, tr("Missing dll"), tr("Can't find ") + path);
return;
}
QProcess regsvr;
int ret = regsvr.execute("regsvr32.exe", {"/s", path});
if(ret)
{
QMessageBox::critical(this, tr("Error"), tr("Failed to register thumbnailer. %1").arg(ret));
}
#endif
}
void SettingsDialog::saveSettings()
{
QSettings settings;
+2
View File
@@ -13,6 +13,8 @@ public:
explicit SettingsDialog(QWidget *parent = nullptr);
static void loadSettings();
static bool loadThumbsizes();
public slots:
void installThumbnailer();
signals:
void preloadChanged(int witdth);
private:
+2 -2
View File
@@ -1,6 +1,6 @@
uniform sampler2D qt_Texture0;
uniform sampler3D lut_table;
uniform sampler1DArray colormap;
uniform sampler2DArray colormap;
uniform vec3 mtf_param[3];
uniform vec2 unit_scale;
uniform bool bw;
@@ -30,7 +30,7 @@ vec3 falsecolor(float color)
{
color *= 255.0 / 256.0;
color += 0.5 / 256.0;
return texture(colormap, vec2(color, colormapIdx)).rgb;
return texture(colormap, vec3(color, 0.5, colormapIdx)).rgb;
}
vec3 checker()
+9
View File
@@ -58,6 +58,15 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="20250318" date="2025-03-18">
<description>
<ul>
<li>Fix OpenGL ES drawings</li>
<li>Fix mark/unmark files from script</li>
<li>Fix stretching of float images with values outside of 0-1 range</li>
</ul>
</description>
</release>
<release version="20250302" date="2025-03-02">
<description>
<ul>
+68
View File
@@ -0,0 +1,68 @@
#ifndef TFLOAT16_H
#define TFLOAT16_H
// crude implementation of float16 for platforms that do not support _Float16
#include <stdint.h>
class TFloat16
{
uint16_t b16;
public:
TFloat16(){ b16 = 0; }
explicit inline TFloat16(float f)
{
uint32_t i = *reinterpret_cast<uint32_t*>(&f);
uint32_t sign = (i >> 16) & 0x8000;
uint32_t exp = (i >> 23) & 0xff;
uint32_t mantisa = (i & 0x7fffff) >> 13;
b16 = 0;
if(exp < 111)
{
// do nothing it map to 0
}
else if(exp == 111)
{
b16 |= sign;
b16 |= mantisa;
}
else if(exp == 255)//inf or nan
{
b16 = 0x7c00;
b16 |= sign;
b16 |= mantisa;
}
else if(exp > 142)
{
b16 = 0x7c00;// inf
b16 |= sign;
}
else
{
b16 |= sign;
b16 |= (exp - 112) << 10;
b16 |= mantisa;
}
}
friend TFloat16 operator*(TFloat16 a, TFloat16 b)
{
return TFloat16(static_cast<float>(a) * static_cast<float>(b));
}
operator float() const
{
uint32_t i = 0;
uint32_t sign = b16 & 0x8000;
uint32_t exp = (b16 & 0x7c00) >> 10;
if(b16)
{
i |= sign << 16;
if(exp==31)i |= 0x7f800000;
else i |= (exp + 112) << 23;
i |= (b16 & 0x3ff) << 13;
}
return *reinterpret_cast<float*>(&i);
}
};
#endif // TFLOAT16_H
+8 -3
View File
@@ -3,19 +3,24 @@ option(BUILD_THUMBNAILER "Build generator of thumbnails" OFF)
if(BUILD_THUMBNAILER)
if(WIN32)
add_library(tenmonthumbnailer SHARED
winmain.cpp
Dll.cpp
loadxisf.cpp
TenmonThumbnailProvider.cpp
../rawimage.h
../rawimage.cpp
../rawimage_sse.cpp)
set_target_properties(tenmonthumbnailer PROPERTIES PREFIX "")
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
target_link_libraries(tenmonthumbnailer PRIVATE ${LCMS2_LIB} XISF)
target_link_libraries(tenmonthumbnailer PRIVATE shlwapi ${LCMS2_LIB} ${FITS_LIB} XISF)
target_link_options(tenmonthumbnailer PRIVATE "-static")
else(WIN32)
qt_add_executable(tenmonthumbnailer
main.cpp
../loadimage.cpp
../rawimage.cpp
../rawimage_sse.cpp
../loadimage.cpp
../imageinfodata.cpp)
target_link_libraries(tenmonthumbnailer PRIVATE Qt6::Core Qt6::Gui ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
+244
View File
@@ -0,0 +1,244 @@
#include <objbase.h>
#include <shlwapi.h>
#include <thumbcache.h> // For IThumbnailProvider.
#include <shlobj.h> // For SHChangeNotify
#include <new>
extern HRESULT TenmonThumbnailer_CreateInstance(REFIID riid, void **ppv);
#define SZ_CLSID_TENMONTHUMBHANDLER L"{0f3881d7-c9f0-45cb-aadb-40192aead2b4}"
#define SZ_TENMONTHUMBHANDLER L"Tenmon Thumbnail Handler"
const CLSID CLSID_TenmonThumbHandler = {0x0f3881d7, 0xc9f0, 0x45cb, {0xaa, 0xdb, 0x40, 0x19, 0x2a, 0xea, 0xd2, 0xb4}};
typedef HRESULT (*PFNCREATEINSTANCE)(REFIID riid, void **ppvObject);
struct CLASS_OBJECT_INIT
{
const CLSID *pClsid;
PFNCREATEINSTANCE pfnCreate;
};
// add classes supported by this module here
const CLASS_OBJECT_INIT c_rgClassObjectInit[] =
{
{ &CLSID_TenmonThumbHandler, TenmonThumbnailer_CreateInstance }
};
long g_cRefModule = 0;
// Handle the the DLL's module
HINSTANCE g_hInst = NULL;
// Standard DLL functions
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
g_hInst = hInstance;
DisableThreadLibraryCalls(hInstance);
}
return TRUE;
}
STDAPI DllCanUnloadNow()
{
// Only allow the DLL to be unloaded after all outstanding references have been released
return (g_cRefModule == 0) ? S_OK : S_FALSE;
}
void DllAddRef()
{
InterlockedIncrement(&g_cRefModule);
}
void DllRelease()
{
InterlockedDecrement(&g_cRefModule);
}
class CClassFactory : public IClassFactory
{
public:
static HRESULT CreateInstance(REFCLSID clsid, const CLASS_OBJECT_INIT *pClassObjectInits, size_t cClassObjectInits, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
for (size_t i = 0; i < cClassObjectInits; i++)
{
if (clsid == *pClassObjectInits[i].pClsid)
{
IClassFactory *pClassFactory = new (std::nothrow) CClassFactory(pClassObjectInits[i].pfnCreate);
hr = pClassFactory ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pClassFactory->QueryInterface(riid, ppv);
pClassFactory->Release();
}
break; // match found
}
}
return hr;
}
CClassFactory(PFNCREATEINSTANCE pfnCreate) : _cRef(1), _pfnCreate(pfnCreate)
{
DllAddRef();
}
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void ** ppv)
{
static const QITAB qit[] =
{
QITABENT(CClassFactory, IClassFactory),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
long cRef = InterlockedDecrement(&_cRef);
if (cRef == 0)
{
delete this;
}
return cRef;
}
// IClassFactory
IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv);
}
IFACEMETHODIMP LockServer(BOOL fLock)
{
if (fLock)
{
DllAddRef();
}
else
{
DllRelease();
}
return S_OK;
}
private:
~CClassFactory()
{
DllRelease();
}
long _cRef;
PFNCREATEINSTANCE _pfnCreate;
};
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv)
{
return CClassFactory::CreateInstance(clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
}
// A struct to hold the information required for a registry entry
struct REGISTRY_ENTRY
{
HKEY hkeyRoot;
PCWSTR pszKeyName;
PCWSTR pszValueName;
PCWSTR pszData;
};
// Creates a registry key (if needed) and sets the default value of the key
HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
{
HKEY hKey;
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot, pRegistryEntry->pszKeyName,
0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL));
if (SUCCEEDED(hr))
{
hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, pRegistryEntry->pszValueName, 0, REG_SZ,
(LPBYTE) pRegistryEntry->pszData,
((DWORD) wcslen(pRegistryEntry->pszData) + 1) * sizeof(WCHAR)));
RegCloseKey(hKey);
}
return hr;
}
//
// Registers this COM server
//
STDAPI DllRegisterServer()
{
HRESULT hr;
WCHAR szModuleName[MAX_PATH];
if (!GetModuleFileNameW(g_hInst, szModuleName, ARRAYSIZE(szModuleName)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
else
{
// List of registry entries we want to create
const REGISTRY_ENTRY rgRegistryEntries[] =
{
// RootKey KeyName ValueName Data
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER, NULL, SZ_TENMONTHUMBHANDLER},
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER L"\\InProcServer32", NULL, szModuleName},
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER L"\\InProcServer32", L"ThreadingModel", L"Apartment"},
{HKEY_CURRENT_USER, L"Software\\Classes\\.xisf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER},
{HKEY_CURRENT_USER, L"Software\\Classes\\.fits\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER},
{HKEY_CURRENT_USER, L"Software\\Classes\\.fit\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER},
};
hr = S_OK;
for (int i = 0; i < ARRAYSIZE(rgRegistryEntries) && SUCCEEDED(hr); i++)
{
hr = CreateRegKeyAndSetValue(&rgRegistryEntries[i]);
}
}
if (SUCCEEDED(hr))
{
// This tells the shell to invalidate the thumbnail cache. This is important because any .xisf files
// viewed before registering this handler would otherwise show cached blank thumbnails.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
}
return hr;
}
//
// Unregisters this COM server
//
STDAPI DllUnregisterServer()
{
HRESULT hr = S_OK;
const PCWSTR rgpszKeys[] =
{
L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER,
L"Software\\Classes\\.xisf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
L"Software\\Classes\\.fits\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
L"Software\\Classes\\.fit\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
};
// Delete the registry entries
for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++)
{
hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i]));
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
// If the registry entry has already been deleted, say S_OK.
hr = S_OK;
}
}
return hr;
}
+121
View File
@@ -0,0 +1,121 @@
#include <shlwapi.h>
#include <thumbcache.h> // For IThumbnailProvider.
#include <new>
#include "libxisf.h"
bool loadXISF(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize);
bool loadFITS(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize);
class TenmonThumbProvider : public IInitializeWithStream,
public IThumbnailProvider
{
public:
TenmonThumbProvider() : _cRef(1), _pStream(NULL)
{
}
virtual ~TenmonThumbProvider()
{
if (_pStream)
{
_pStream->Release();
}
}
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(TenmonThumbProvider, IInitializeWithStream),
QITABENT(TenmonThumbProvider, IThumbnailProvider),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
ULONG cRef = InterlockedDecrement(&_cRef);
if (!cRef)
{
delete this;
}
return cRef;
}
// IInitializeWithStream
IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode);
// IThumbnailProvider
IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);
private:
long _cRef;
IStream *_pStream; // provided during initialization.
};
HRESULT TenmonThumbnailer_CreateInstance(REFIID riid, void **ppv)
{
TenmonThumbProvider *pNew = new (std::nothrow) TenmonThumbProvider();
HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pNew->QueryInterface(riid, ppv);
pNew->Release();
}
return hr;
}
// IInitializeWithStream
IFACEMETHODIMP TenmonThumbProvider::Initialize(IStream *pStream, DWORD)
{
HRESULT hr = E_UNEXPECTED; // can only be inited once
if (_pStream == NULL)
{
// take a reference to the stream if we have not been inited yet
hr = pStream->QueryInterface(&_pStream);
}
return hr;
}
// IThumbnailProvider
IFACEMETHODIMP TenmonThumbProvider::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
{
LibXISF::ByteArray data;
ULONG readSize = 0;
ULONG read;
data.resize(1024*1024);
while(_pStream->Read(data.data() + readSize, data.size() - readSize, &read) == S_OK)
{
readSize += read;
data.resize(data.size() + 1024*1024);
}
readSize += read;
*pdwAlpha = WTSAT_RGB;
data.resize(readSize);
if(data[0] == 'X' && data[1] == 'I' && data[2] == 'S' && data[3] == 'F')
{
if(loadXISF(data, phbmp, cx))
return S_OK;
else
return E_FAIL;
}
else
{
if(loadFITS(data, phbmp, cx))
return S_OK;
else
return E_FAIL;
}
return E_FAIL;
}
+37
View File
@@ -0,0 +1,37 @@
#include "genthumbnail.h"
#include "../rawimage.h"
#include "../loadimage.h"
int generateThumbnail(const QString &input, const QString &output, uint32_t size)
{
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
if(!loadImage(input, info, rawImage))
return 2;
if(!rawImage)
return 3;
QSize rect(rawImage->width(), rawImage->height());
rect.scale(size, size, Qt::KeepAspectRatio);
rawImage->calcStats();
rawImage->resize(rect.width(), rect.height());
if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.2f)
{
MTFParam mtfParams = rawImage->calcMTFParams(true);
rawImage->applySTF(mtfParams);
}
rawImage->convertToType(RawImage::UINT8);
QImage img;
if(rawImage->channels() == 1)
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_Grayscale8);
else
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_RGBA8888);
if(!img.save(output, "png"))
return 4;
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef GENTHUMBNAIL_H
#define GENTHUMBNAIL_H
#include <QString>
int generateThumbnail(const QString &input, const QString &output, uint32_t size);
#endif // GENTHUMBNAIL_H
+237
View File
@@ -0,0 +1,237 @@
#include "libxisf.h"
#include <thumbcache.h>
#include "../rawimage.h"
#include <fitsio2.h>
bool OpenGLES = false;
void RawImageToHTBITMAP(std::shared_ptr<RawImage> &rawImage, HBITMAP *hbmp, UINT thumbSize)
{
rawImage->calcStats();
DWORD thre = 10;
DWORD dataSize = 4;
//HRESULT hr = HRESULT_FROM_WIN32(RegGetValueW(HKEY_CURRENT_USER, L"SOFTWARE\\nou\\Tenmon\\settings", L"thumbnailstretchthreshold", RRF_RT_DWORD, NULL, &thre, &dataSize));
float thref = 0.1f;
/*if(hr == S_OK)
thref = thre / 100.0f;*/
if(rawImage->imageStats().m_mean[0] < rawImage->norm() * thref)
{
//OutputDebugStringA("Stretch image");
MTFParam params = rawImage->calcMTFParams();
rawImage->applySTF(params);
}
UINT w = rawImage->width();
UINT h = rawImage->height();
UINT cw = thumbSize;
UINT ch = thumbSize;
if (w > h)
ch = h * thumbSize / w;
else
cw = w * thumbSize / h;
rawImage->resize(cw, ch);
rawImage->convertToType(RawImage::UINT8);
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = cw;
bmi.bmiHeader.biHeight = -static_cast<LONG>(ch);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
UINT lw = cw * 4;
BYTE *pBits;
HBITMAP bmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&pBits), NULL, 0);
const unsigned char *p = (const unsigned char*)rawImage->data();
const unsigned short *ps = (const unsigned short*)rawImage->data();
if(rawImage->channels() == 1)
{
for(UINT y = 0; y < ch; y++)
{
for(UINT x = 0; x < cw; x++)
{
pBits[(y * lw) + x * 4 + 0] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 1] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 2] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 3] = 255;
}
}
}
else
{
for(UINT y = 0; y < ch; y++)
{
for(UINT x = 0; x < cw; x++)
{
pBits[(y * lw) + x * 4 + 0] = p[y * cw * 4 + x * 4 + 2];
pBits[(y * lw) + x * 4 + 1] = p[y * cw * 4 + x * 4 + 1];
pBits[(y * lw) + x * 4 + 2] = p[y * cw * 4 + x * 4 + 0];
pBits[(y * lw) + x * 4 + 3] = 255;
}
}
}
*hbmp = bmp;
}
bool loadXISF(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(data);
const LibXISF::Image &xisfImage = xisf.getImage(0);
RawImage::DataType type;
switch(xisfImage.sampleFormat())
{
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
default: break;
}
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
std::shared_ptr<RawImage> rawImage;
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{
rawImage = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(rawImage->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
}
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
rawImage = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
}
RawImageToHTBITMAP(rawImage, hbmp, thumbSize);
return true;
}
catch (LibXISF::Error &err)
{
char text[1024];
sprintf_s(text, 1000, "Failed to open XISF image %s", err.what());
OutputDebugStringA(text);
return false;
}
return false;
}
bool loadFITS(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize)
{
fitsfile *file;
int status = 0;
int type = -1;
int num = 0;
long naxes[3] = {0};
auto checkError = [&status]()
{
char err[100];
char text[1000];
fits_get_errstatus(status, err);
sprintf_s(text, 1000, "Failed to load FITS file %s", err);
OutputDebugStringA(text);
return false;
};
const void *dataPtr = data.data();
size_t size = data.size();
fits_open_memfile(&file, "file.fits", READONLY, (void**)&dataPtr, &size, 0, nullptr, &status);
if(status)return checkError();
fits_get_num_hdus(file, &num, &status);
if(status)return checkError();
int imgtype;
int naxis;
for(int i=1; i <= num; i++)
{
fits_movabs_hdu(file, i, IMAGE_HDU, &status);if(status)return checkError();
fits_get_hdu_type(file, &type, &status);if(status)return checkError();
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
fits_get_img_equivtype(file, &imgtype, &status);if(status)return checkError();
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
{
RawImage::DataType type;
int fitstype;
long fpixel[3] = {1,1,1};
switch(imgtype)
{
case BYTE_IMG:
type = RawImage::UINT8;
fitstype = TBYTE;
break;
case SHORT_IMG:
type = RawImage::UINT16;
fitstype = TSHORT;
break;
case USHORT_IMG:
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG:
type = RawImage::FLOAT32;
fitstype = TFLOAT;
break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default:
return false;
break;
}
size_t size = naxes[0]*naxes[1];
size_t w = naxes[0];
size_t h = naxes[1];
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
uint8_t *data = static_cast<uint8_t*>(img.data());
for (int i=1; i==1 || i<=naxes[2]; i++)
{
fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
if(status)return checkError();
}
if(fitstype == TSHORT)
{
uint16_t *s = static_cast<uint16_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
std::shared_ptr<RawImage> image;
if(img.channels() == 1)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
RawImageToHTBITMAP(image, hbmp, thumbSize);
return true;
}
}
return false;
}
+24 -10
View File
@@ -28,21 +28,35 @@ int main(int argc, char *argv[])
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
if(!loadImage(input, info, rawImage))
return 1;
if(!rawImage)
return 2;
if(!rawImage)
return 3;
bool ok;
int size = parser.value("s").toInt(&ok);
if(!ok)
size = 128;
QSize rect(rawImage->width(), rawImage->height());
rect.scale(size, size, Qt::KeepAspectRatio);
rawImage->calcStats();
rawImage->resize(rect.width(), rect.height());
if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.2f)
{
MTFParam mtfParams = rawImage->calcMTFParams(true);
rawImage->applySTF(mtfParams);
}
rawImage->convertToType(RawImage::UINT8);
QImage img((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), QImage::Format_RGBA8888);
bool ok = false;
int size = parser.value("s").toInt(&ok);
if(!ok)size = 128;
img = img.scaled(size, size, Qt::KeepAspectRatio);
img.save(output, "png");
QImage img;
if(rawImage->channels() == 1)
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_Grayscale8);
else
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_RGBA8888);
//rawImage->convertTosRGB();
if(!img.save(output, "png"))
return 4;
return 0;
}
-1
View File
@@ -1 +0,0 @@
bool OpenGLES = false;