Compare commits
7 Commits
f1b5ec81c0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| cb1237cbc4 | |||
| dbc9baba13 | |||
| bd99501616 | |||
| fccbf4d810 | |||
| 0a11289f34 | |||
| 53167e7bb5 | |||
| 1e940de31b |
@@ -29,6 +29,7 @@ add_subdirectory(libXISF)
|
|||||||
|
|
||||||
set(TENMON_SRC
|
set(TENMON_SRC
|
||||||
src/about.cpp src/about.h
|
src/about.cpp src/about.h
|
||||||
|
src/application.cpp src/application.h
|
||||||
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
|
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
|
||||||
src/chartgraph.h src/chartgraph.cpp
|
src/chartgraph.h src/chartgraph.cpp
|
||||||
src/database.cpp src/database.h
|
src/database.cpp src/database.h
|
||||||
@@ -39,6 +40,7 @@ set(TENMON_SRC
|
|||||||
src/filemanager.h src/filemanager.cpp src/filemanager.ui
|
src/filemanager.h src/filemanager.cpp src/filemanager.ui
|
||||||
src/filesystemwidget.cpp src/filesystemwidget.h
|
src/filesystemwidget.cpp src/filesystemwidget.h
|
||||||
src/fitskeyword.ui
|
src/fitskeyword.ui
|
||||||
|
src/fitswrapper.h src/fitswrapper.cpp
|
||||||
src/histogram.cpp src/histogram.h
|
src/histogram.cpp src/histogram.h
|
||||||
src/httpdownloader.h src/httpdownloader.cpp
|
src/httpdownloader.h src/httpdownloader.cpp
|
||||||
src/imageinfo.cpp src/imageinfo.h
|
src/imageinfo.cpp src/imageinfo.h
|
||||||
@@ -73,6 +75,9 @@ elseif(APPLE)
|
|||||||
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/tenmon.icns)
|
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/tenmon.icns)
|
||||||
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
||||||
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE "tenmon.icns")
|
||||||
|
set(MACOSX_BUNDLE_BUNDLE_NAME "tenmon")
|
||||||
|
set(MACOSX_BUNDLE_GUI_IDENTIFIER "space.nouspiro.tenmon")
|
||||||
else()
|
else()
|
||||||
set(tenmon_ICON "")
|
set(tenmon_ICON "")
|
||||||
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
||||||
@@ -111,6 +116,7 @@ endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
|||||||
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml Qt6::Charts Qt6::Svg ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml Qt6::Charts Qt6::Svg ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
||||||
|
set_target_properties(tenmon PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in")
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
target_link_libraries(tenmon PRIVATE Qt6::DBus)
|
target_link_libraries(tenmon PRIVATE Qt6::DBus)
|
||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
@@ -133,8 +139,12 @@ if(UNIX AND NOT APPLE)
|
|||||||
install(SCRIPT install.cmake)
|
install(SCRIPT install.cmake)
|
||||||
else()
|
else()
|
||||||
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
|
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
|
||||||
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
|
install(FILES resources/space.nouspiro.tenmon_16.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/16x16/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
|
install(FILES resources/space.nouspiro.tenmon_32.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
|
install(FILES resources/space.nouspiro.tenmon_64.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
|
install(FILES resources/space.nouspiro.tenmon_256.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
|
install(FILES resources/space.nouspiro.tenmon_512.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/512x512/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
install(FILES space.nouspiro.tenmon.xisf.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages")
|
install(FILES space.nouspiro.tenmon.xisf.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages")
|
||||||
endif()
|
endif()
|
||||||
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>tenmon</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLongVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>fits</string>
|
||||||
|
<string>fit</string>
|
||||||
|
<string>xisf</string>
|
||||||
|
<string>png</string>
|
||||||
|
<string>jpg</string>
|
||||||
|
<string>jpeg</string>
|
||||||
|
<string>tiff</string>
|
||||||
|
<string>tif</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<table><tr>
|
<table><tr>
|
||||||
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon.png"></td>
|
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon_64.png"></td>
|
||||||
<td><h3>Tenmon</h3>
|
<td><h3>Tenmon</h3>
|
||||||
Tenmon is FITS/XISF image viewer and converter. It also index FITS keywords.<br>
|
Tenmon is FITS/XISF image viewer and converter. It also index FITS keywords.<br>
|
||||||
v@GITVERSION@ Copyright © 2026 Dušan Poizl<br><br>
|
v@GITVERSION@ Copyright © 2026 Dušan Poizl<br><br>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
|||||||
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
|
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
|
||||||
find_program(XDG-MIME xdg-mime)
|
find_program(XDG-MIME xdg-mime)
|
||||||
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 16 resources/space.nouspiro.tenmon_16.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 32 resources/space.nouspiro.tenmon_32.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_64.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})
|
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})
|
||||||
|
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 256 resources/space.nouspiro.tenmon_256.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 512 resources/space.nouspiro.tenmon_512.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
execute_process(COMMAND ${XDG-MIME} install --novendor space.nouspiro.tenmon.xisf.xml WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
execute_process(COMMAND ${XDG-MIME} install --novendor space.nouspiro.tenmon.xisf.xml WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 1.4 MiB |
@@ -15,7 +15,7 @@
|
|||||||
<file>bggr.png</file>
|
<file>bggr.png</file>
|
||||||
<file>grbg.png</file>
|
<file>grbg.png</file>
|
||||||
<file>gbrg.png</file>
|
<file>gbrg.png</file>
|
||||||
<file>space.nouspiro.tenmon.png</file>
|
<file>space.nouspiro.tenmon_64.png</file>
|
||||||
<file>../translations/tenmon_pt_BR.qm</file>
|
<file>../translations/tenmon_pt_BR.qm</file>
|
||||||
<file alias="help">../about/help_en</file>
|
<file alias="help">../about/help_en</file>
|
||||||
<file>colormap.png</file>
|
<file>colormap.png</file>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
#include "application.h"
|
||||||
|
|
||||||
|
#include <QFileOpenEvent>
|
||||||
|
|
||||||
|
Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::event(QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::FileOpen) {
|
||||||
|
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
|
||||||
|
emit openFileEvent(openEvent->file());
|
||||||
|
}
|
||||||
|
|
||||||
|
return QApplication::event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef APPLICATION_H
|
||||||
|
#define APPLICATION_H
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
class Application : public QApplication
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Application(int &argc, char **argv);
|
||||||
|
bool event(QEvent *event) override;
|
||||||
|
signals:
|
||||||
|
void openFileEvent(const QString &file);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPLICATION_H
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#include "fitswrapper.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
FITSWrapper::FITSWrapper(const QString &path, int mode, bool open)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
_mode = mode;
|
||||||
|
|
||||||
|
if(open)
|
||||||
|
fits_open_diskfile(&_file, path.toLocal8Bit().data(), mode, &_status);
|
||||||
|
else
|
||||||
|
fits_create_diskfile(&_file, path.toLocal8Bit().data(), &_status);
|
||||||
|
|
||||||
|
if(_status == FILE_NOT_OPENED || _status == FILE_NOT_CREATED)
|
||||||
|
{
|
||||||
|
qWarning() << "Could not open file directly trying memfile workaround" << path;
|
||||||
|
if(open)
|
||||||
|
{
|
||||||
|
QFile fr(path);
|
||||||
|
if(fr.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
_bufferSize = fr.size();
|
||||||
|
_bufferPtr = malloc(_bufferSize);
|
||||||
|
fr.read((char*)_bufferPtr, _bufferSize);
|
||||||
|
_status = 0;
|
||||||
|
fits_open_memfile(&_file, "memfile", mode, &_bufferPtr, &_bufferSize, 0, realloc, &_status);
|
||||||
|
if(_status)
|
||||||
|
{
|
||||||
|
free(_bufferPtr);
|
||||||
|
_bufferPtr = nullptr;
|
||||||
|
_bufferSize = 0;
|
||||||
|
qWarning() << "fits_open_memfile failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "QFile failed to open file" << path;
|
||||||
|
_status = FILE_NOT_OPENED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_bufferSize = 2880;
|
||||||
|
_bufferPtr = malloc(_bufferSize);
|
||||||
|
fits_create_memfile(&_file, &_bufferPtr, &_bufferSize, 0, realloc, &_status);
|
||||||
|
if(_status)
|
||||||
|
{
|
||||||
|
free(_bufferPtr);
|
||||||
|
_bufferPtr = nullptr;
|
||||||
|
_bufferSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FITSWrapper::~FITSWrapper()
|
||||||
|
{
|
||||||
|
if(_file)
|
||||||
|
{
|
||||||
|
_status = 0;
|
||||||
|
fits_close_file(_file, &_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_mode == READWRITE && _bufferPtr)
|
||||||
|
{
|
||||||
|
qDebug() << "Writing FITS memfile" << _bufferSize;
|
||||||
|
QFile fw(_path);
|
||||||
|
if(fw.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||||
|
{
|
||||||
|
if(fw.write((char*)_bufferPtr, _bufferSize) != (qint64)_bufferSize)
|
||||||
|
qWarning() << "Failed to write to file";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qWarning() << "Failed to open fits file for writing";
|
||||||
|
}
|
||||||
|
|
||||||
|
free(_bufferPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FITSWrapper::status() const
|
||||||
|
{
|
||||||
|
return _status;
|
||||||
|
}
|
||||||
|
|
||||||
|
FITSWrapper::operator fitsfile *()
|
||||||
|
{
|
||||||
|
return _file;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef FITSWRAPPER_H
|
||||||
|
#define FITSWRAPPER_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <fitsio.h>
|
||||||
|
|
||||||
|
class FITSWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FITSWrapper(const QString &path, int mode, bool open);
|
||||||
|
~FITSWrapper();
|
||||||
|
FITSWrapper(FITSWrapper &other) = delete;
|
||||||
|
int status() const;
|
||||||
|
operator fitsfile*();
|
||||||
|
private:
|
||||||
|
QString _path;
|
||||||
|
int _status = 0;
|
||||||
|
int _mode = 0;
|
||||||
|
fitsfile *_file;
|
||||||
|
size_t _bufferSize = 0;
|
||||||
|
void *_bufferPtr = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FITSWRAPPER_H
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#include "loadimage.h"
|
#include "loadimage.h"
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QDir>
|
|
||||||
#include <libraw/libraw.h>
|
|
||||||
#include <fitsio2.h>
|
|
||||||
#include "libxisf.h"
|
#include "libxisf.h"
|
||||||
#include <libexif/exif-data.h>
|
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <libexif/exif-data.h>
|
||||||
|
#include <libraw/libraw.h>
|
||||||
|
#include "fitswrapper.h"
|
||||||
|
|
||||||
QString makeUNCPath(const QString &path)
|
QString makeUNCPath(const QString &path)
|
||||||
{
|
{
|
||||||
@@ -85,7 +85,6 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
|
|||||||
|
|
||||||
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
|
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
|
||||||
{
|
{
|
||||||
fitsfile *file;
|
|
||||||
int status = 0;
|
int status = 0;
|
||||||
int num = 0;
|
int num = 0;
|
||||||
long naxes[3] = {0};
|
long naxes[3] = {0};
|
||||||
@@ -99,29 +98,9 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
|
FITSWrapper file(path, READONLY, true);
|
||||||
|
status = file.status();
|
||||||
|
|
||||||
QByteArray buffer;
|
|
||||||
size_t bufferSize = 0;
|
|
||||||
void *bufferPtr = nullptr;
|
|
||||||
if(status == FILE_NOT_OPENED)
|
|
||||||
{
|
|
||||||
qWarning() << "Could not open file directly tring memfile workaround" << path;
|
|
||||||
QFile fr(path);
|
|
||||||
if(fr.open(QIODevice::ReadOnly))
|
|
||||||
{
|
|
||||||
buffer = fr.readAll();
|
|
||||||
bufferSize = buffer.size();
|
|
||||||
bufferPtr = buffer.data();
|
|
||||||
status = 0;
|
|
||||||
fits_open_memfile(&file, "memfile", READONLY, &bufferPtr, &bufferSize, 0, NULL, &status);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qWarning() << "QFile failed to open file" << path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(status)return checkError();
|
if(status)return checkError();
|
||||||
fits_get_num_hdus(file, &num, &status);
|
fits_get_num_hdus(file, &num, &status);
|
||||||
if(status)return checkError();
|
if(status)return checkError();
|
||||||
@@ -244,7 +223,6 @@ noload:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fits_close_file(file, &status);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +231,8 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
LibXISF::XISFReader xisf;
|
LibXISF::XISFReader xisf;
|
||||||
xisf.open(path.toLocal8Bit().data());
|
QFileInfo fileInfo(path);
|
||||||
|
xisf.open(fileInfo.filesystemFilePath());
|
||||||
|
|
||||||
if(index >= (uint32_t)xisf.imagesCount())return false;
|
if(index >= (uint32_t)xisf.imagesCount())return false;
|
||||||
const LibXISF::Image &xisfImage = xisf.getImage(index);
|
const LibXISF::Image &xisfImage = xisf.getImage(index);
|
||||||
@@ -366,37 +345,14 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
|
|||||||
|
|
||||||
bool readFITSHeader(const QString &path, ImageInfoData &info)
|
bool readFITSHeader(const QString &path, ImageInfoData &info)
|
||||||
{
|
{
|
||||||
fitsfile *file;
|
|
||||||
int status = 0;
|
|
||||||
QString path2 = makeUNCPath(path);
|
QString path2 = makeUNCPath(path);
|
||||||
fits_open_diskfile(&file, path2.toLocal8Bit().data(), READONLY, &status);
|
|
||||||
|
|
||||||
QByteArray buffer;
|
FITSWrapper file(path, READONLY, true);
|
||||||
size_t bufferSize = 0;
|
int status = file.status();
|
||||||
void *bufferPtr = nullptr;
|
|
||||||
if(status == FILE_NOT_OPENED)
|
|
||||||
{
|
|
||||||
qWarning() << "Could not open file directly tring memfile workaround" << path;
|
|
||||||
QFile fr(path);
|
|
||||||
if(fr.open(QIODevice::ReadOnly))
|
|
||||||
{
|
|
||||||
buffer = fr.readAll();
|
|
||||||
bufferSize = buffer.size();
|
|
||||||
bufferPtr = buffer.data();
|
|
||||||
status = 0;
|
|
||||||
fits_open_memfile(&file, "memfile", READONLY, &bufferPtr, &bufferSize, 0, NULL, &status);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qWarning() << "QFile failed to open file" << path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(file && status == 0)
|
if(file && status == 0)
|
||||||
{
|
{
|
||||||
status = loadFITSHeader(file, info);
|
status = loadFITSHeader(file, info);
|
||||||
fits_close_file(file, &status);
|
|
||||||
}
|
}
|
||||||
return status == 0;
|
return status == 0;
|
||||||
}
|
}
|
||||||
@@ -407,7 +363,8 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
LibXISF::XISFReader xisf;
|
LibXISF::XISFReader xisf;
|
||||||
xisf.open(path2.toLocal8Bit().data());
|
QFileInfo fileInfo(path2);
|
||||||
|
xisf.open(fileInfo.filesystemFilePath());
|
||||||
const LibXISF::Image &image = xisf.getImage(0, false);
|
const LibXISF::Image &image = xisf.getImage(0, false);
|
||||||
|
|
||||||
auto fitskeywords = image.fitsKeywords();
|
auto fitskeywords = image.fitsKeywords();
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#include "loadrunable.h"
|
#include "loadrunable.h"
|
||||||
|
#include "fitswrapper.h"
|
||||||
#include "imageringlist.h"
|
#include "imageringlist.h"
|
||||||
|
#include "loadimage.h"
|
||||||
|
#include "rawimage.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fitsio2.h>
|
#include <fitsio2.h>
|
||||||
#include "rawimage.h"
|
|
||||||
#include "loadimage.h"
|
|
||||||
#include <lcms2.h>
|
#include <lcms2.h>
|
||||||
|
|
||||||
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail) :
|
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail) :
|
||||||
@@ -269,7 +270,8 @@ void ConvertRunable::run()
|
|||||||
image.setByteshuffling(true);
|
image.setByteshuffling(true);
|
||||||
|
|
||||||
xisf.writeImage(image);
|
xisf.writeImage(image);
|
||||||
xisf.save(m_outfile.toLocal8Bit().data());
|
QFileInfo fileInfo(makeUNCPath(m_outfile));
|
||||||
|
xisf.save(fileInfo.filesystemFilePath());
|
||||||
}
|
}
|
||||||
catch(LibXISF::Error &err)
|
catch(LibXISF::Error &err)
|
||||||
{
|
{
|
||||||
@@ -281,9 +283,10 @@ void ConvertRunable::run()
|
|||||||
if(m_format == "fits")
|
if(m_format == "fits")
|
||||||
{
|
{
|
||||||
int status = 0;
|
int status = 0;
|
||||||
fitsfile *fw;
|
|
||||||
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
|
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
|
||||||
fits_create_diskfile(&fw, m_outfile.toLocal8Bit().data(), &status);
|
FITSWrapper fw(m_outfile, READWRITE, false);
|
||||||
|
if(fw.status())return;
|
||||||
|
|
||||||
if(!m_params.compressionType.isEmpty())
|
if(!m_params.compressionType.isEmpty())
|
||||||
{
|
{
|
||||||
if(m_params.compressionType == "gzip")
|
if(m_params.compressionType == "gzip")
|
||||||
@@ -292,7 +295,6 @@ void ConvertRunable::run()
|
|||||||
fits_set_compression_type(fw, RICE_1, &status);
|
fits_set_compression_type(fw, RICE_1, &status);
|
||||||
}
|
}
|
||||||
writeFITSImage(fw, rawimage, imageinfo);
|
writeFITSImage(fw, rawimage, imageinfo);
|
||||||
fits_close_file(fw, &status);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include <QApplication>
|
#include "application.h"
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
@@ -102,11 +102,10 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
QSurfaceFormat::setDefaultFormat(format);
|
QSurfaceFormat::setDefaultFormat(format);
|
||||||
|
|
||||||
|
Application a(argc, argv);
|
||||||
QApplication a(argc, argv);
|
|
||||||
a.setOrganizationName("nou");
|
a.setOrganizationName("nou");
|
||||||
a.setApplicationName("Tenmon");
|
a.setApplicationName("Tenmon");
|
||||||
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon.png"));
|
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon_64.png"));
|
||||||
|
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
QTranslator translator2;
|
QTranslator translator2;
|
||||||
@@ -127,6 +126,7 @@ int main(int argc, char *argv[])
|
|||||||
a.installTranslator(&translator2);
|
a.installTranslator(&translator2);
|
||||||
|
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
|
QObject::connect(&a, &Application::openFileEvent, &w, static_cast<void (MainWindow::*)(const QString&)>(&MainWindow::loadFile));
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
if(!cmd.positionalArguments().isEmpty() && !cmd.isSet("script"))
|
if(!cmd.positionalArguments().isEmpty() && !cmd.isSet("script"))
|
||||||
|
|||||||
@@ -405,6 +405,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
if(_plateSolving)_plateSolving->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
if(_plateSolving)_plateSolving->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||||
m_stretchPanel->setFloatable(false);
|
m_stretchPanel->setFloatable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(settings.value("settings/checkupdate", false).toBool())
|
||||||
|
{
|
||||||
|
QDateTime lastcheck = settings.value("settings/lastcheck").toDateTime();
|
||||||
|
QDateTime weekago = QDateTime::currentDateTimeUtc().addDays(-7);
|
||||||
|
if(!lastcheck.isValid() || lastcheck < weekago)
|
||||||
|
checkNewVersion(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
@@ -823,21 +831,27 @@ void MainWindow::exportCSV()
|
|||||||
m_databaseView->exportCSV(file);
|
m_databaseView->exportCSV(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::checkNewVersion()
|
void MainWindow::checkNewVersion(bool autocheck)
|
||||||
{
|
{
|
||||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||||
QNetworkRequest request(QUrl("https://gitea.nouspiro.space/api/v1/repos/nou/tenmon/releases/latest"));
|
QNetworkRequest request(QUrl("https://gitea.nouspiro.space/api/v1/repos/nou/tenmon/releases/latest"));
|
||||||
request.setRawHeader("accept", "application/json");
|
request.setRawHeader("accept", "application/json");
|
||||||
QNetworkReply *reply = manager->get(request);
|
QNetworkReply *reply = manager->get(request);
|
||||||
connect(reply, &QNetworkReply::finished, [this, manager, reply](){
|
connect(reply, &QNetworkReply::finished, [this, manager, reply, autocheck](){
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error);
|
QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||||
if(json.isObject() && json.object().contains("tag_name"))
|
if(json.isObject() && json.object().contains("tag_name"))
|
||||||
{
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("settings/lastcheck", QDateTime::currentDateTimeUtc());
|
||||||
|
|
||||||
QString tag = json.object().value("tag_name").toString();
|
QString tag = json.object().value("tag_name").toString();
|
||||||
QString version = getVersion();
|
QString version = getVersion();
|
||||||
if(version >= tag)
|
if(version >= tag)
|
||||||
|
{
|
||||||
|
if(!autocheck)
|
||||||
QMessageBox::information(this, tr("Update check"), tr("You have newest version"));
|
QMessageBox::information(this, tr("Update check"), tr("You have newest version"));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(QMessageBox::question(this, tr("Update check"), tr("New version %1 is available. Do you want to download it now?").arg(tag)) == QMessageBox::Yes)
|
if(QMessageBox::question(this, tr("Update check"), tr("New version %1 is available. Do you want to download it now?").arg(tag)) == QMessageBox::Yes)
|
||||||
@@ -851,6 +865,7 @@ void MainWindow::checkNewVersion()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if(!autocheck)
|
||||||
QMessageBox::warning(this, tr("Update check"), tr("Failed to check version"));
|
QMessageBox::warning(this, tr("Update check"), tr("Failed to check version"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public slots:
|
|||||||
void showMarkFilesDialog();
|
void showMarkFilesDialog();
|
||||||
void showSettingsDialog();
|
void showSettingsDialog();
|
||||||
void exportCSV();
|
void exportCSV();
|
||||||
void checkNewVersion();
|
void checkNewVersion(bool autocheck = false);
|
||||||
void openFileManager();
|
void openFileManager();
|
||||||
void runScript(const QString &script, const QString &outdir, const QStringList &paths, const QString &arg, bool exit);
|
void runScript(const QString &script, const QString &outdir, const QStringList &paths, const QString &arg, bool exit);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
#include "scriptengine.h"
|
#include "scriptengine.h"
|
||||||
#include <QDir>
|
#include "batchprocessing.h"
|
||||||
#include <QFileInfo>
|
#include "fitswrapper.h"
|
||||||
#include <QDebug>
|
#include "libxisf.h"
|
||||||
#include <QInputDialog>
|
#include "loadimage.h"
|
||||||
#include <QJsonValue>
|
|
||||||
#include <QJSValueIterator>
|
|
||||||
#include "loadrunable.h"
|
#include "loadrunable.h"
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
#include "loadimage.h"
|
#include <QDebug>
|
||||||
#include "batchprocessing.h"
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QJSValueIterator>
|
||||||
|
#include <QJsonValue>
|
||||||
#include <fitsio2.h>
|
#include <fitsio2.h>
|
||||||
#include "libxisf.h"
|
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
#include "solver.h"
|
#include "solver.h"
|
||||||
#endif // PLATESOLVER
|
#endif // PLATESOLVER
|
||||||
@@ -672,31 +673,11 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
|
|
||||||
if(isFITS(suffix()))
|
if(isFITS(suffix()))
|
||||||
{
|
{
|
||||||
fitsfile *file;
|
|
||||||
int status = 0;
|
int status = 0;
|
||||||
QString path = makeUNCPath(_path);
|
QString path = makeUNCPath(_path);
|
||||||
fits_open_diskfile(&file, path.toLocal8Bit().data(), READWRITE, &status);
|
FITSWrapper file(path, READWRITE, true);
|
||||||
QByteArray buffer;
|
status = file.status();
|
||||||
size_t bufferSize = 0;
|
|
||||||
void *bufferPtr = nullptr;
|
|
||||||
if(status == FILE_NOT_OPENED)
|
|
||||||
{
|
|
||||||
qWarning() << "Could not open file directly tring memfile workaround" << path;
|
|
||||||
QFile fr(path);
|
|
||||||
if(fr.open(QIODevice::ReadOnly))
|
|
||||||
{
|
|
||||||
buffer = fr.readAll();
|
|
||||||
bufferSize = buffer.size();
|
|
||||||
bufferPtr = buffer.data();
|
|
||||||
status = 0;
|
|
||||||
fits_open_memfile(&file, "memfile", READONLY, &bufferPtr, &bufferSize, 0, NULL, &status);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qWarning() << "QFile failed to open file" << path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int num = 0;
|
int num = 0;
|
||||||
fits_get_num_hdus(file, &num, &status);
|
fits_get_num_hdus(file, &num, &status);
|
||||||
if(status)
|
if(status)
|
||||||
@@ -816,8 +797,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fits_close_file(file, &status);
|
if(status)qWarning() << "Failed to modify FITS header" << status;
|
||||||
|
|
||||||
return status == 0;
|
return status == 0;
|
||||||
}
|
}
|
||||||
else if(isXISF(suffix()))
|
else if(isXISF(suffix()))
|
||||||
@@ -825,9 +805,9 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
LibXISF::XISFModify modifyXISF;
|
LibXISF::XISFModify modifyXISF;
|
||||||
QString in = makeUNCPath(absoluteFilePath());
|
QFileInfo in(makeUNCPath(absoluteFilePath()));
|
||||||
QString out = in + "~";
|
QFileInfo out(in.absoluteFilePath() + "~");
|
||||||
modifyXISF.open(in.toLocal8Bit().data());
|
modifyXISF.open(in.filesystemFilePath());
|
||||||
qDebug() << "modify" << in << out;
|
qDebug() << "modify" << in << out;
|
||||||
|
|
||||||
for(auto &remove : modify->_remove)
|
for(auto &remove : modify->_remove)
|
||||||
@@ -842,9 +822,9 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
for(auto &property : modify->_property)
|
for(auto &property : modify->_property)
|
||||||
modifyXISF.updateProperty(modify->_imageIdx, property);
|
modifyXISF.updateProperty(modify->_imageIdx, property);
|
||||||
|
|
||||||
modifyXISF.save(out.toLocal8Bit().toStdString());
|
modifyXISF.save(out.filesystemFilePath());
|
||||||
modifyXISF.close();
|
modifyXISF.close();
|
||||||
std::filesystem::rename(out.toLocal8Bit().toStdString(), in.toLocal8Bit().toStdString());
|
std::filesystem::rename(out.filesystemFilePath(), in.filesystemFilePath());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch(std::filesystem::filesystem_error &err)
|
catch(std::filesystem::filesystem_error &err)
|
||||||
|
|||||||
@@ -147,6 +147,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
|||||||
else if(lang == "sk")m_lang->setCurrentIndex(2);
|
else if(lang == "sk")m_lang->setCurrentIndex(2);
|
||||||
else if(lang == "pt_BR")m_lang->setCurrentIndex(3);
|
else if(lang == "pt_BR")m_lang->setCurrentIndex(3);
|
||||||
|
|
||||||
|
m_checkUpdate = new QCheckBox(tr("Check for new version"), this);
|
||||||
|
m_checkUpdate->setToolTip(tr("Check for new version every week"));
|
||||||
|
m_checkUpdate->setChecked(settings.value("settings/checkupdate", false).toBool());
|
||||||
|
|
||||||
layout->addRow(tr("Image preload count"), m_preloadImages);
|
layout->addRow(tr("Image preload count"), m_preloadImages);
|
||||||
layout->addRow(tr("Thumbnails size"), m_thumSize);
|
layout->addRow(tr("Thumbnails size"), m_thumSize);
|
||||||
layout->addRow(tr("Saturation"), m_saturation);
|
layout->addRow(tr("Saturation"), m_saturation);
|
||||||
@@ -156,6 +160,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
|||||||
layout->addRow(m_qualityThumbnail);
|
layout->addRow(m_qualityThumbnail);
|
||||||
layout->addRow(m_useNativeDialog);
|
layout->addRow(m_useNativeDialog);
|
||||||
layout->addRow(m_bestFit);
|
layout->addRow(m_bestFit);
|
||||||
|
layout->addRow(m_checkUpdate);
|
||||||
layout->addRow(new QLabel(tr("FITS header highlight"), this));
|
layout->addRow(new QLabel(tr("FITS header highlight"), this));
|
||||||
layout->addRow(m_headerHighlight);
|
layout->addRow(m_headerHighlight);
|
||||||
layout->addRow(m_keyword, color);
|
layout->addRow(m_keyword, color);
|
||||||
@@ -265,4 +270,5 @@ void SettingsDialog::saveSettings()
|
|||||||
case 3: lang = "pt_BR"; break;
|
case 3: lang = "pt_BR"; break;
|
||||||
}
|
}
|
||||||
settings.setValue("settings/lang", lang);
|
settings.setValue("settings/lang", lang);
|
||||||
|
settings.setValue("settings/checkupdate", m_checkUpdate->isChecked());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ private:
|
|||||||
QColor m_color = Qt::yellow;
|
QColor m_color = Qt::yellow;
|
||||||
QLineEdit *m_keyword;
|
QLineEdit *m_keyword;
|
||||||
QComboBox *m_lang;
|
QComboBox *m_lang;
|
||||||
|
QCheckBox *m_checkUpdate;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGSDIALOG_H
|
#endif // SETTINGSDIALOG_H
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
#include "../src/rawimage.h"
|
#include "../src/rawimage.h"
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
#include "stb_image_write.h"
|
#include "stb_image_write.h"
|
||||||
|
|||||||