Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6411b7cd15 | |||
| 223f7cd0ea | |||
| f8f9ee08b3 | |||
| af5aed7ef8 | |||
| 8f5249b142 | |||
| a7dc942c62 | |||
| 1a1399434b | |||
| be567841bf |
+5
-7
@@ -92,18 +92,16 @@ endif()
|
||||
|
||||
install(TARGETS tenmon BUNDLE DESTINATION .)
|
||||
if(UNIX AND NOT APPLE)
|
||||
include(GNUInstallDirs)
|
||||
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
||||
if(XDG-DESKTOP-MENU_EXECUTABLE)
|
||||
install(SCRIPT install.cmake)
|
||||
else()
|
||||
if(DEFINED ENV{FLATPAK_DEST})
|
||||
install(FILES org.nou.tenmon.desktop DESTINATION "$ENV{FLATPAK_DEST}/share/applications")
|
||||
install(FILES org.nou.tenmon.png DESTINATION "$ENV{FLATPAK_DEST}/share/icons/hicolor/32x32/apps")
|
||||
else()
|
||||
install(FILES org.nou.tenmon.desktop DESTINATION "/usr/share/applications")
|
||||
install(FILES org.nou.tenmon.png DESTINATION "/usr/share/icons/hicolor/32x32/apps")
|
||||
endif()
|
||||
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)
|
||||
endif()
|
||||
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
option(RELEASE_BUILD "Release build" OFF)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<table><tr>
|
||||
<td style="padding-right:10px"><img src=":/org.nou.tenmon.png"></td>
|
||||
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon.png"></td>
|
||||
<td><h3>Tenmon</h3>
|
||||
Tenmon is FITS/XISF image viewer and converter. It also index FITS keywords.<br>
|
||||
v@GITVERSION@ Copyright © 2022 Dušan Poizl<br><br>
|
||||
|
||||
+43
-3
@@ -11,6 +11,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QPainter>
|
||||
#include <QFileInfo>
|
||||
#include <cmath>
|
||||
|
||||
struct RawImageType
|
||||
{
|
||||
@@ -36,6 +37,8 @@ const RawImageType rawImageTypes[] = {
|
||||
{QOpenGLTexture::RGB, QOpenGLTexture::RGB32F, QOpenGLTexture::Float32, false}
|
||||
};
|
||||
|
||||
static bool MANUAL_MIPMAP_GEN = false;
|
||||
|
||||
void setScrollRange(QScrollBar *scrollBar, int newRange)
|
||||
{
|
||||
int page = scrollBar->pageStep();
|
||||
@@ -105,8 +108,11 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
|
||||
m_currentImg = index;
|
||||
|
||||
const RawImageType &rawImageType = rawImageTypes[image->type()];
|
||||
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8 || rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
|
||||
m_bwImg = rawImageType.bw;
|
||||
|
||||
m_image->destroy();
|
||||
m_image->setAutoMipMapGenerationEnabled(false);
|
||||
m_image->setFormat(rawImageType.textureFormat);
|
||||
m_image->setSize(image->width(), image->height());
|
||||
m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }());
|
||||
@@ -115,10 +121,42 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
|
||||
m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
|
||||
m_image->setBorderColor(0, 0, 0, 0);
|
||||
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data(), m_transferOptions.get());
|
||||
|
||||
auto sRGB_linear = [](cv::Point3f &pixel, const int *pos)
|
||||
{
|
||||
pixel.x = pixel.x <= 0.04045f ? pixel.x / 12.92f : std::pow((pixel.x + 0.055) / 1.055f, 2.4f);
|
||||
pixel.y = pixel.y <= 0.04045f ? pixel.y / 12.92f : std::pow((pixel.y + 0.055) / 1.055f, 2.4f);
|
||||
pixel.z = pixel.z <= 0.04045f ? pixel.z / 12.92f : std::pow((pixel.z + 0.055) / 1.055f, 2.4f);
|
||||
};
|
||||
|
||||
auto linear_sRGB = [](cv::Point3f &pixel, const int *pos)
|
||||
{
|
||||
pixel.x = pixel.x <= 0.0031308f ? pixel.x * 12.92f : 1.055f * std::pow(pixel.x , 1/2.4f) - 0.055f;
|
||||
pixel.y = pixel.y <= 0.0031308f ? pixel.y * 12.92f : 1.055f * std::pow(pixel.y , 1/2.4f) - 0.055f;
|
||||
pixel.z = pixel.z <= 0.0031308f ? pixel.z * 12.92f : 1.055f * std::pow(pixel.z , 1/2.4f) - 0.055f;
|
||||
};
|
||||
|
||||
//AMD OpenGL driver on Windows doesn't generate mipmaps for sRGB textures correctly
|
||||
if(m_srgb && MANUAL_MIPMAP_GEN)
|
||||
{
|
||||
cv::Mat img = image->mat();
|
||||
img.convertTo(img, CV_32FC3, 1/255.0);
|
||||
img.forEach<cv::Point3f>(sRGB_linear);
|
||||
cv::Size size(img.cols, img.rows);
|
||||
for(int i=1; i<m_image->mipLevels(); i++)
|
||||
{
|
||||
cv::Mat mip;
|
||||
size /= 2;
|
||||
cv::resize(img, mip, size);
|
||||
mip.copyTo(img);
|
||||
mip.forEach<cv::Point3f>(linear_sRGB);
|
||||
mip.convertTo(mip, CV_8UC3, 255);
|
||||
m_image->setData(i, rawImageType.pixelFormat, rawImageType.dataType, (const void*)mip.ptr(), m_transferOptions.get());
|
||||
}
|
||||
}
|
||||
else m_image->generateMipMaps();
|
||||
|
||||
m_image->setLevelOfDetailRange(m_superpixel ? 1 : 0, m_image->mipMaxLevel());
|
||||
m_image->generateMipMaps();
|
||||
m_bwImg = rawImageType.bw;
|
||||
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8 || rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -351,6 +389,8 @@ void ImageWidget::initializeGL()
|
||||
qDebug() << (char*)f->glGetString(GL_RENDERER);
|
||||
qDebug() << (char*)f->glGetString(GL_VERSION);
|
||||
|
||||
MANUAL_MIPMAP_GEN = QString((const char*)f->glGetString(GL_VENDOR)).startsWith("ATI Technologies Inc", Qt::CaseInsensitive);
|
||||
|
||||
qDebug() << context()->format();
|
||||
|
||||
// each vertex is x,y 2D position and s,t texture coordinates
|
||||
|
||||
+3
-2
@@ -1,4 +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 org.nou.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 32 org.nou.tenmon.png org.nou.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
+5
-4
@@ -541,9 +541,10 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
|
||||
return true;
|
||||
}
|
||||
|
||||
ConvertRunable::ConvertRunable(const QString &in, const QString &out) :
|
||||
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format) :
|
||||
m_infile(in),
|
||||
m_outfile(out)
|
||||
m_outfile(out),
|
||||
m_format(format)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -648,7 +649,7 @@ void ConvertRunable::run()
|
||||
|
||||
if(rawimage)
|
||||
{
|
||||
if(m_outfile.endsWith(".XISF", Qt::CaseInsensitive))
|
||||
if(m_format == "XISF")
|
||||
{
|
||||
pcl::XISFOptions options;
|
||||
pcl::FITSKeywordArray fitskeywords;
|
||||
@@ -675,7 +676,7 @@ void ConvertRunable::run()
|
||||
|
||||
}
|
||||
|
||||
if(m_outfile.endsWith(".FITS", Qt::CaseInsensitive) || m_outfile.endsWith(".FIT", Qt::CaseInsensitive))
|
||||
if(m_format == "FITS")
|
||||
{
|
||||
int status = 0;
|
||||
fitsfile *fw;
|
||||
|
||||
+2
-1
@@ -25,8 +25,9 @@ class ConvertRunable : public QRunnable
|
||||
{
|
||||
QString m_infile;
|
||||
QString m_outfile;
|
||||
QString m_format;
|
||||
public:
|
||||
ConvertRunable(const QString &in, const QString &out);
|
||||
ConvertRunable(const QString &in, const QString &out, const QString &format);
|
||||
void run() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ int main(int argc, char *argv[])
|
||||
QApplication a(argc, argv);
|
||||
a.setOrganizationName("nou");
|
||||
a.setApplicationName("Tenmon");
|
||||
a.setWindowIcon(QIcon(":/org.nou.tenmon.png"));
|
||||
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon.png"));
|
||||
|
||||
QTranslator translator;
|
||||
QTranslator translator2;
|
||||
|
||||
+35
-17
@@ -293,7 +293,10 @@ void MainWindow::closeEvent(QCloseEvent *event)
|
||||
|
||||
void MainWindow::copyOrMove(bool copy)
|
||||
{
|
||||
QString dest = QFileDialog::getExistingDirectory(this, tr("Select destination"), _lastDir);
|
||||
QString dest = QFileDialog::getExistingDirectory(this,
|
||||
tr("Select destination"),
|
||||
_lastDir,
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog);
|
||||
copyOrMove(copy, dest);
|
||||
}
|
||||
|
||||
@@ -366,7 +369,12 @@ void MainWindow::pixmapLoaded(Image *image)
|
||||
|
||||
void MainWindow::loadFile()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(this, tr("Open file"), _lastDir, tr("Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)"));
|
||||
QString file = QFileDialog::getOpenFileName(this,
|
||||
tr("Open file"),
|
||||
_lastDir,
|
||||
tr("Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)"),
|
||||
nullptr,
|
||||
QFileDialog::DontUseNativeDialog);
|
||||
loadFile(file);
|
||||
}
|
||||
|
||||
@@ -393,7 +401,7 @@ void MainWindow::loadFile(int row)
|
||||
|
||||
void MainWindow::indexDir()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Index directory"), _lastDir);
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Index directory"), _lastDir, QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog);
|
||||
indexDir(dir);
|
||||
}
|
||||
|
||||
@@ -417,35 +425,45 @@ void MainWindow::reindex()
|
||||
void MainWindow::saveAs()
|
||||
{
|
||||
QString selectedFilter;
|
||||
QString file = QFileDialog::getSaveFileName(this, tr("Save as"), _lastDir, tr("JPEG (*.jpg *.JPG);; PNG (*.png *.PNG);;FITS (*.fits *.FITS);;XISF (*.xisf *.XISF)"), &selectedFilter);
|
||||
QString file = QFileDialog::getSaveFileName(this,
|
||||
tr("Save as"),
|
||||
_lastDir,
|
||||
tr("JPEG (*.jpg *.JPG);; PNG (*.png *.PNG);;FITS (*.fits *.FITS);;XISF (*.xisf *.XISF)"),
|
||||
&selectedFilter);
|
||||
auto filterToFormat = [](const QString &file, const QString &filter) -> const char*
|
||||
{
|
||||
QString suffix = QFileInfo(file).suffix();
|
||||
if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "JPEG";
|
||||
if(!suffix.compare("png", Qt::CaseInsensitive))return "PNG";
|
||||
if(!suffix.compare("fits", Qt::CaseInsensitive) || !suffix.compare("fit", Qt::CaseInsensitive))return "FITS";
|
||||
if(!suffix.compare("xisf", Qt::CaseInsensitive))return "XISF";
|
||||
if(filter.contains("png"))return "PNG";
|
||||
if(filter.contains("fits"))return "FITS";
|
||||
if(filter.contains("xisf"))return "XISF";
|
||||
return "JPEG";
|
||||
};
|
||||
|
||||
if(!file.isEmpty())
|
||||
{
|
||||
QFileInfo info(file);
|
||||
if(info.suffix().isEmpty())
|
||||
{
|
||||
if(selectedFilter.contains("jpg"))file += ".jpg";
|
||||
if(selectedFilter.contains("png"))file += ".png";
|
||||
if(selectedFilter.contains("fits"))file += ".fits";
|
||||
if(selectedFilter.contains("xisf"))file += ".xisf";
|
||||
}
|
||||
QString format = filterToFormat(file, selectedFilter);
|
||||
|
||||
if(file.endsWith(".fits") || file.endsWith(".xisf"))
|
||||
if(format == "FITS" || format == "XISF")
|
||||
{
|
||||
convert(file);
|
||||
convert(file, format);
|
||||
}
|
||||
else
|
||||
{
|
||||
QImage img = m_imageGL->imageWidget()->renderToImage();
|
||||
if(!img.isNull())
|
||||
img.save(file);
|
||||
img.save(file, filterToFormat(file, selectedFilter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::convert(const QString &outfile)
|
||||
void MainWindow::convert(const QString &outfile, const QString &format)
|
||||
{
|
||||
QString file = m_ringList->currentImage()->name();
|
||||
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile));
|
||||
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format));
|
||||
}
|
||||
|
||||
void MainWindow::markImage()
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ protected slots:
|
||||
void indexDir(const QString &dir);
|
||||
void reindex();
|
||||
void saveAs();
|
||||
void convert(const QString &outfile);
|
||||
void convert(const QString &outfile, const QString &format);
|
||||
void markImage();
|
||||
void unmarkImage();
|
||||
void markAndNext();
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<file>invert.png</file>
|
||||
<file>nuke.png</file>
|
||||
<file>bayer.png</file>
|
||||
<file>org.nou.tenmon.png</file>
|
||||
<file>space.nouspiro.tenmon.png</file>
|
||||
<file>nuke_a.png</file>
|
||||
<file>about/tenmon</file>
|
||||
<file>about/pcl</file>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Exec=tenmon %U
|
||||
Icon=org.nou.tenmon
|
||||
Icon=space.nouspiro.tenmon
|
||||
Comment=FITS Image viewer
|
||||
Name=Tenmon
|
||||
Categories=Graphics;2DGraphics;RasterGraphics;Viewer;
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>space.nouspiro.tenmon</id>
|
||||
<launchable type="desktop-id">space.nouspiro.tenmon.desktop</launchable>
|
||||
<name>Tenmon</name>
|
||||
<summary>FITS/XISF image viewer, converter, index and search</summary>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<description>
|
||||
<p>
|
||||
It is intended primarily for viewing astro photos and images. It supports the following formats:
|
||||
<ul>
|
||||
<li>FITS 8, 16 bit integer and 32 bit float</li>
|
||||
<li>XISF 8, 16 bit integer and 32 bit float</li>
|
||||
<li>JPEG and PNG images</li>
|
||||
</ul>
|
||||
</p>
|
||||
Features:
|
||||
<ul>
|
||||
<li>Using same stretch function as PixInsight</li>
|
||||
<li>OpenGL accelerated drawing</li>
|
||||
<li>Index and search FITS XISF header data</li>
|
||||
<li>Quick mark images and then copy/move marked files</li>
|
||||
<li>Convert FITS <-> XISF</li>
|
||||
<li>Convert FITS/XISF -> JPEG/PNG</li>
|
||||
<li>Image statistics mean, media, min, max</li>
|
||||
<li>Support for WCS</li>
|
||||
<li>Thumbnails</li>
|
||||
</ul>
|
||||
<p>
|
||||
</p>
|
||||
</description>
|
||||
<url type="homepage">https://nouspiro.space/?page_id=206</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://nouspiro.space/wp-content/uploads/2022/04/tenmon-1024x579.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="20221126" date="2022-11-26"/>
|
||||
<release version="20221121" date="2022-11-11"/>
|
||||
<release version="20221023" date="2022-10-23"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 33.866666 33.866668"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="space.nouspiro.tenmon.svg"
|
||||
inkscape:export-filename="/home/nou/c++/tenmon/space.nouspiro.tenmon_128.png"
|
||||
inkscape:export-xdpi="96.000008"
|
||||
inkscape:export-ydpi="96.000008"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
width="128px"
|
||||
inkscape:zoom="2.6547419"
|
||||
inkscape:cx="54.430903"
|
||||
inkscape:cy="78.162024"
|
||||
inkscape:window-width="1862"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="58"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Vrstva 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#000000;stroke:#ffffff;stroke-width:0;stroke-linejoin:round"
|
||||
id="rect1196"
|
||||
width="33.866665"
|
||||
height="33.866665"
|
||||
x="5e-07"
|
||||
y="5e-07" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:17.276px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.359917"
|
||||
x="-0.41414261"
|
||||
y="23.331123"
|
||||
id="text8592"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan8590"
|
||||
style="fill:#ffffff;stroke-width:0.359917"
|
||||
x="-0.41414261"
|
||||
y="23.331123">天文</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -1,9 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Exec=Tenmon %U
|
||||
Icon=org.nou.tenmon
|
||||
Comment=FITS Image viewer
|
||||
Name=Tenmon
|
||||
Categories=Graphics;2DGraphics;RasterGraphics;Viewer;
|
||||
MimeType=image/fits;image/x-xisf;
|
||||
Terminal=false
|
||||
Reference in New Issue
Block a user