Skip to content

Commit 96b7e41

Browse files
committed
Fix #1499: Add support of FITS file format
#1499 Supports 2D images only. Disable via: meson -Dfits=disabled
1 parent c200490 commit 96b7e41

14 files changed

+294
-4
lines changed

Diff for: .github/workflows/check-build-actions.yml

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jobs:
4949
-Dexiv2=disabled
5050
-Dexr=disabled
5151
-Dextended_stacktrace=disabled
52+
-Dfits=disabled
5253
-Dgit=disabled
5354
-Dgps-map=disabled
5455
-Dgtk4=disabled

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Geeqie is a graphics file viewer. Basic features:
6666
* output: single image, anaglyph, SBS, mirror, SBS half size (3DTV)
6767

6868
* Viewing raster and vector images, in the following formats:
69-
* 3FR ANI ARW AVIF BMP CR2 CR3 CRW CUR DDS DJVU DNG ERF EXR GIF GQV HEIC HEIF ICO JP2 JPE JPEG JPG JPS JXL KDC MEF MOS MPO MRW NEF ORF PBM PDF PEF PGM PNG PNM PPM PSD QIF QTIF RAF RAW RW2 SCR SR2 SRF SVG SVGZ TGA TIF TIFF WEBP XBM XPM.
69+
* 3FR ANI ARW AVIF BMP CR2 CR3 CRW CUR DDS DJVU DNG ERF EXR FIT FITS FTS GIF GQV HEIC HEIF ICO JP2 JPE JPEG JPG JPS JXL KDC MEF MOS MPO MRW NEF NPY ORF PBM PDF PEF PGM PNG PNM PPM PSD QIF QTIF RAF RAW RW2 SCR SR2 SRF SVG SVGZ TGA TIF TIFF WEBP XBM XPM.
7070
* Display images in archive files (.ZIP, .RAR etc.).
7171
* Animated GIF and WEBP files are supported.
7272

Diff for: auto-complete/geeqie

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# bash completion for geeqie -*- shell-script -*-
22

3-
file_types='@(3fr|ani|arw|avif|bmp|cr2|cr3|crw|cur|dds|djvu|dng|erf|exr|gif|gqv|heic|heif|ico|jp2|jpe|jpeg|jpg|jps|jxl|kdc|mef|mos|mpo|mrw|nef|orf|pbm|pdf|pef|pgm|png|pnm|ppm|psd|qif|qtif|raf|raw|rw2|scr|sr2|srf|svg|svgz|tga|tif|tiff|webp|xbm|xpm)'
3+
file_types='@(3fr|ani|arw|avif|bmp|cr2|cr3|crw|cur|dds|djvu|dng|erf|exr|.fits|fit|fts|gif|gqv|heic|heif|ico|jp2|jpe|jpeg|jpg|jps|jxl|kdc|mef|mos|mpo|mrw|nef|orf|pbm|pdf|pef|pgm|png|pnm|ppm|psd|qif|qtif|raf|raw|rw2|scr|sr2|srf|svg|svgz|tga|tif|tiff|webp|xbm|xpm)'
44

55
actions='About AddMark0 AddMark1 AddMark2 AddMark3 AddMark4 AddMark5 AddMark6 AddMark7 AddMark8 AddMark9 AlterNone Animate Back ClearMarks CloseWindow ColorProfile0 ColorProfile1 ColorProfile2 ColorProfile3 ColorProfile4 ColorProfile5 ConnectZoom100 ConnectZoom200 ConnectZoom25 ConnectZoom300 ConnectZoom33 ConnectZoom400 ConnectZoom50 ConnectZoomFillHor ConnectZoomFillVert ConnectZoomFit ConnectZoomIn ConnectZoomOut Copy CopyImage CopyPath CopyPathUnquoted CropFourThree CropNone CropOneOne CropRectangle CropSixteenNine CropThreeTwo CutPath Delete DeleteWindow DrawRectangle Escape ExifRotate ExifWin FilterMark0 FilterMark1 FilterMark2 FilterMark3 FilterMark4 FilterMark5 FilterMark6 FilterMark7 FilterMark8 FilterMark9 FindDupes FirstImage FirstPage Flip FloatTools FolderTree Forward FullScreen Grayscale HelpChangeLog HelpContents HelpKbd HelpNotes HelpPdf HelpSearch HelpShortcuts HideBars HideSelectableToolbars HideTools HistogramChanB HistogramChanCycle HistogramChanG HistogramChanR HistogramChanRGB HistogramChanV HistogramModeCycle HistogramModeLin HistogramModeLog Home IgnoreAlpha ImageBack ImageForward ImageHistogram ImageOverlay ImageOverlayCycle IntMark0 IntMark1 IntMark2 IntMark3 IntMark4 IntMark5 IntMark6 IntMark7 IntMark8 IntMark9 KeywordAutocomplete LastImage LastPage LayoutConfig LogWindow Maintenance Mark0 Mark1 Mark2 Mark3 Mark4 Mark5 Mark6 Mark7 Mark8 Mark9 Mirror Move NewCollection NewFolder NewWindow NewWindowDefault NewWindowFromCurrent NextImage NextPage OpenArchive OpenCollection OpenRecent OpenWith OverUnderExposed PanView PermanentDelete Plugins Preferences PrevImage PrevPage Print Quit Rating0 Rating1 Rating2 Rating3 Rating4 Rating5 RatingM1 RectangularSelection Refresh Rename RenameWindow ResetMark0 ResetMark1 ResetMark2 ResetMark3 ResetMark4 ResetMark5 ResetMark6 ResetMark7 ResetMark8 ResetMark9 Rotate180 RotateCCW RotateCW SBar SBarSort SaveMetadata Search SearchAndRunCommand SelectAll SelectInvert SelectMark0 SelectMark1 SelectMark2 SelectMark3 SelectMark4 SelectMark5 SelectMark6 SelectMark7 SelectMark8 SelectMark9 SelectNone SetMark0 SetMark1 SetMark2 SetMark3 SetMark4 SetMark5 SetMark6 SetMark7 SetMark8 SetMark9 ShowFileFilter ShowInfoPixel ShowMarks SlideShow SlideShowFaster SlideShowPause SlideShowSlower SplitDownPane SplitHorizontal SplitNextPane SplitPaneSync SplitPreviousPane SplitQuad SplitSingle SplitTriple SplitUpPane SplitVertical StereoAuto StereoCross StereoCycle StereoOff StereoSBS Thumbnails ToggleMark0 ToggleMark1 ToggleMark2 ToggleMark3 ToggleMark4 ToggleMark5 ToggleMark6 ToggleMark7 ToggleMark8 ToggleMark9 UnselMark0 UnselMark1 UnselMark2 UnselMark3 UnselMark4 UnselMark5 UnselMark6 UnselMark7 UnselMark8 UnselMark9 Up UseColorProfiles UseImageProfile ViewIcons ViewInNewWindow ViewList WriteRotation WriteRotationKeepDate Zoom100 Zoom200 Zoom25 Zoom300 Zoom33 Zoom400 Zoom50 ZoomFillHor ZoomFillVert ZoomFit ZoomIn ZoomOut ZoomToRectangle'
66

Diff for: config.h.in

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
/* Define if ffmpegthumbnailer supports specifying size by width/height */
6969
#mesondefine HAVE_FFMPEGTHUMBNAILER_WH
7070

71+
/* Define to enable fits support */
72+
#mesondefine HAVE_FITS
73+
7174
/* Define to enable heif support */
7275
#mesondefine HAVE_HEIF
7376

Diff for: doxygen.conf

+1
Original file line numberDiff line numberDiff line change
@@ -2427,6 +2427,7 @@ PREDEFINED = DEBUG=1 \
24272427
HAVE_EXECINFO_H=1 \
24282428
HAVE_EXIV2=1 \
24292429
HAVE_FFMPEGTHUMBNAILER=1 \
2430+
HAVE_FITS=1 \
24302431
HAVE_HEIF=1 \
24312432
HAVE_J2K=1 \
24322433
HAVE_JPEG=1 \

Diff for: geeqie-install-debian.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
## Dialogs allow the user to install additional features.
99
##
1010

11-
version="2024-10-29"
11+
version="2024-11-11"
1212
description='
1313
Geeqie is an image viewer.
1414
This script will download, compile, and install Geeqie on Debian-based systems.
@@ -96,7 +96,9 @@ libchamplain-0.12-dev
9696
libpoppler (for pdf file preview)
9797
libpoppler-glib-dev
9898
libjxl (for viewing .jxl images)
99-
libjxl-dev"
99+
libjxl-dev
100+
libcfitsio (for .fits images)
101+
libcfitsio-dev"
100102

101103
####################################################################
102104
# Get System Info

Diff for: meson.build

+15
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,21 @@ else
329329
summary({'exr' : ['disabled - exr files supported:', false]}, section : 'Configuration', bool_yn : true)
330330
endif
331331

332+
conf_data.set('HAVE_FITS', 0)
333+
fits_dep = []
334+
option = get_option('fits')
335+
if not option.disabled()
336+
fits_dep = dependency('cfitsio', required: get_option('fits'))
337+
if fits_dep.found()
338+
conf_data.set('HAVE_FITS', 1)
339+
summary({'fits' : ['fits files supported:', true]}, section : 'Configuration', bool_yn : true)
340+
else
341+
summary({'fits' : ['libcfitsio not found - fits files supported:', false]}, section : 'Configuration', bool_yn : true)
342+
endif
343+
else
344+
summary({'fits' : ['disabled - fits files supported:', false]}, section : 'Configuration', bool_yn : true)
345+
endif
346+
332347
conf_data.set('HAVE_CLUTTER', 0)
333348
conf_data.set('HAVE_LIBCHAMPLAIN', 0)
334349
conf_data.set('HAVE_LIBCHAMPLAIN_GTK', 0)

Diff for: meson_options.txt

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ option('execinfo', type : 'feature', value : 'auto', description : 'execinfo.h')
3232
option('exiv2', type : 'feature', value : 'auto', description : 'exiv2')
3333
option('exr', type : 'feature', value : 'auto', description : 'exr')
3434
option('extended_stacktrace', type : 'feature', value : 'auto', description : 'extended stacktrace')
35+
option('fits', type : 'feature', value : 'auto', description : 'fits')
3536
option('git', type : 'feature', value : 'auto', description : 'lua-api and changelog.html')
3637
option('gps-map', type : 'feature', value : 'auto', description : 'gps map')
3738
option('gtk4', type : 'feature', value : 'disabled', description : 'gtk4 - do not use')

Diff for: scripts/test-all.sh

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ meson setup \
5353
-Dexiv2=disabled \
5454
-Dexr=disabled \
5555
-Dextended_stacktrace=disabled \
56+
-Dfits=disabled \
5657
-Dgit=disabled \
5758
-Dgps-map=disabled \
5859
-Dgtk4=disabled \

Diff for: src/filefilter.cc

+3
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ void filter_add_defaults()
171171
#if HAVE_EXR
172172
filter_add_if_missing("exr", "Exr Image", ".exr", FORMAT_CLASS_IMAGE, FALSE, TRUE, TRUE);
173173
#endif
174+
#if HAVE_FITS
175+
filter_add_if_missing("fits", "Fits Image", ".fits;.fit;.fts", FORMAT_CLASS_IMAGE, FALSE, TRUE, TRUE);
176+
#endif
174177
#if HAVE_HEIF
175178
filter_add_if_missing("heif/avif", "HEIF/AVIF Image", ".heif;.heic;.avif", FORMAT_CLASS_IMAGE, FALSE, TRUE, TRUE);
176179
#endif

Diff for: src/image-load-fits.cc

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/*
2+
* Copyright (C) 2024 - The Geeqie Team
3+
*
4+
* Author: Colin Clark
5+
*
6+
* This program is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License along
17+
* with this program; if not, write to the Free Software Foundation, Inc.,
18+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*/
20+
21+
#include "image-load-fits.h"
22+
23+
#include <cmath>
24+
#include <fitsio.h>
25+
#include <limits>
26+
27+
#include "debug.h"
28+
#include "image-load.h"
29+
30+
namespace
31+
{
32+
33+
struct ImageLoaderFITS : public ImageLoaderBackend
34+
{
35+
public:
36+
~ImageLoaderFITS() override;
37+
38+
void init(AreaUpdatedCb area_updated_cb, SizePreparedCb size_prepared_cb, AreaPreparedCb area_prepared_cb, gpointer data) override;
39+
gboolean write(const guchar *buf, gsize &chunk_size, gsize count, GError **error) override;
40+
GdkPixbuf *get_pixbuf() override;
41+
gchar *get_format_name() override;
42+
gchar **get_format_mime_types() override;
43+
void set_page_num(gint page_num) override;
44+
gint get_page_total() override;
45+
46+
private:
47+
AreaUpdatedCb area_updated_cb;
48+
gpointer data;
49+
50+
GdkPixbuf *pixbuf;
51+
gint page_num;
52+
gint page_total;
53+
};
54+
55+
gboolean ImageLoaderFITS::write(const guchar *buf, gsize &chunk_size, gsize count, GError **)
56+
{
57+
fitsfile *fptr; // FITS file pointer
58+
gint anynul;
59+
gint bitpix;
60+
gint naxis;
61+
gint status = 0; // cfitsio status value must be initialized to zero
62+
glong fpixel = 1;
63+
glong naxes[2] = {1, 1}; // Image width and height
64+
65+
/* Open FITS file from memory buffer */
66+
if (fits_open_memfile(&fptr, "mem://", READONLY, (void **)&buf, &count, 0, nullptr, &status))
67+
{
68+
fits_report_error(stderr, status);
69+
return FALSE;
70+
}
71+
72+
/* Check that the file is an image and get its dimensions */
73+
if (fits_get_img_param(fptr, 2, &bitpix, &naxis, naxes, &status))
74+
{
75+
fits_report_error(stderr, status);
76+
fits_close_file(fptr, &status);
77+
return FALSE;
78+
}
79+
80+
if (naxis != 2) // Ensure it's a 2D image
81+
{
82+
log_printf("Error: FITS image is not 2D");
83+
fits_close_file(fptr, &status);
84+
return FALSE;
85+
}
86+
87+
glong width = naxes[0];
88+
glong height = naxes[1];
89+
90+
/* Allocate memory for the image data */
91+
auto *image_data = (gfloat *)malloc(width * height * sizeof(gfloat));
92+
if (!image_data)
93+
{
94+
log_printf("Memory allocation error when processing .fits file");
95+
fits_close_file(fptr, &status);
96+
return FALSE;
97+
}
98+
99+
/* Read the image data */
100+
if (fits_read_img(fptr, TFLOAT, fpixel, width * height, nullptr, image_data, &anynul, &status))
101+
{
102+
fits_report_error(stderr, status);
103+
free(image_data);
104+
fits_close_file(fptr, &status);
105+
return FALSE;
106+
}
107+
108+
/* Close the FITS file */
109+
fits_close_file(fptr, &status);
110+
111+
/* Create a GdkPixbuf in RGB format (24-bit depth, 8 bits per channel) */
112+
GdkPixbuf *pixbuf_tmp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
113+
if (!pixbuf_tmp)
114+
{
115+
log_printf("Failed to create GdkPixbuf for .fits file");
116+
free(image_data);
117+
118+
return FALSE;
119+
}
120+
121+
guchar *pixels = gdk_pixbuf_get_pixels(pixbuf_tmp);
122+
gint rowstride = gdk_pixbuf_get_rowstride(pixbuf_tmp);
123+
124+
gfloat max_value = 0;
125+
gfloat min_value = std::numeric_limits<float>::max();
126+
127+
/* Get the max and min intensity values in the image */
128+
for (glong y = 0; y < height; y++)
129+
{
130+
for (glong x = 0; x < width; x++)
131+
{
132+
gfloat value = (image_data[y * width + x]);
133+
134+
max_value = std::max(max_value, value);
135+
min_value = std::min(min_value, value);
136+
}
137+
}
138+
139+
/* Map the float data to RGB and load it into the GdkPixbuf */
140+
for (glong y = 0; y < height; y++)
141+
{
142+
for (glong x = 0; x < width; x++)
143+
{
144+
gint pixel_index = y * rowstride + x * 3;
145+
gfloat value = (image_data[y * width + x]);
146+
/* fits images seem to have a large intensity range, but the useful data is mostly at the lower end.
147+
* Using linear scaling results in a black image. */
148+
auto intensity = (guchar)(255.0 * (log(value - min_value) / log((max_value - min_value))));
149+
150+
/* Set the RGB channels to the intensity value for grayscale */
151+
pixels[pixel_index] = intensity; // Red
152+
pixels[pixel_index + 1] = intensity; // Green
153+
pixels[pixel_index + 2] = intensity; // Blue
154+
}
155+
}
156+
157+
pixbuf = gdk_pixbuf_copy(pixbuf_tmp);
158+
g_object_unref(pixbuf_tmp);
159+
160+
area_updated_cb(nullptr, 0, 0, width, height, data);
161+
162+
chunk_size = count;
163+
164+
return TRUE;
165+
}
166+
167+
void ImageLoaderFITS::init(AreaUpdatedCb area_updated_cb, SizePreparedCb, AreaPreparedCb, gpointer data)
168+
{
169+
this->area_updated_cb = area_updated_cb;
170+
this->data = data;
171+
page_num = 0;
172+
}
173+
174+
GdkPixbuf *ImageLoaderFITS::get_pixbuf()
175+
{
176+
return pixbuf;
177+
}
178+
179+
gchar *ImageLoaderFITS::get_format_name()
180+
{
181+
return g_strdup("fits");
182+
}
183+
184+
gchar **ImageLoaderFITS::get_format_mime_types()
185+
{
186+
static const gchar *mime[] = {"image/fits", nullptr};
187+
return g_strdupv(const_cast<gchar **>(mime));
188+
}
189+
190+
void ImageLoaderFITS::set_page_num(gint page_num)
191+
{
192+
this->page_num = page_num;
193+
}
194+
195+
gint ImageLoaderFITS::get_page_total()
196+
{
197+
return page_total;
198+
}
199+
200+
ImageLoaderFITS::~ImageLoaderFITS()
201+
{
202+
if (pixbuf) g_object_unref(pixbuf);
203+
}
204+
205+
} // namespace
206+
207+
std::unique_ptr<ImageLoaderBackend> get_image_loader_backend_fits()
208+
{
209+
return std::make_unique<ImageLoaderFITS>();
210+
}
211+
212+
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */

Diff for: src/image-load-fits.h

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (C) 2024 - The Geeqie Team
3+
*
4+
* Author: Colin Clark
5+
*
6+
* This program is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License along
17+
* with this program; if not, write to the Free Software Foundation, Inc.,
18+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*/
20+
21+
#ifndef IMAGE_LOAD_FITS_H
22+
#define IMAGE_LOAD_FITS_H
23+
24+
#include <memory>
25+
26+
struct ImageLoaderBackend;
27+
28+
std::unique_ptr<ImageLoaderBackend> get_image_loader_backend_fits();
29+
30+
#endif /* IMAGE_LOAD_FITS_H */
31+
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */

Diff for: src/image-load.cc

+12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
#if HAVE_FFMPEGTHUMBNAILER
4444
# include "image-load-ffmpegthumbnailer.h"
4545
#endif
46+
#if HAVE_FITS
47+
# include "image-load-fits.h"
48+
#endif
4649
#include "image-load-gdk.h"
4750
#if HAVE_HEIF
4851
# include "image-load-heif.h"
@@ -680,6 +683,15 @@ static void image_loader_setup_loader(ImageLoader *il)
680683
}
681684
else
682685
#endif
686+
#if HAVE_FITS
687+
if (il->bytes_total >= 6 &&
688+
(memcmp(il->mapped_file, "SIMPLE", 6) == 0))
689+
{
690+
DEBUG_1("Using custom fits loader");
691+
il->backend = get_image_loader_backend_fits();
692+
}
693+
else
694+
#endif
683695
#if HAVE_PDF
684696
if (il->bytes_total >= 4 &&
685697
(memcmp(il->mapped_file + 0, "%PDF", 4) == 0))

0 commit comments

Comments
 (0)