1068 lines
34 KiB
C
1068 lines
34 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
* FITS file plugin
|
|
* reading and writing code Copyright (C) 1997 Peter Kirchgessner
|
|
* e-mail: peter@kirchgessner.net, WWW: http://www.kirchgessner.net
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/* Event history:
|
|
* V 1.00, PK, 05-May-97: Creation
|
|
* V 1.01, PK, 19-May-97: Problem with compilation on Irix fixed
|
|
* V 1.02, PK, 08-Jun-97: Bug with saving gray images fixed
|
|
* V 1.03, PK, 05-Oct-97: Parse rc-file
|
|
* V 1.04, PK, 12-Oct-97: No progress bars for non-interactive mode
|
|
* V 1.05, nn, 20-Dec-97: Initialize image_ID in run()
|
|
* V 1.06, PK, 21-Nov-99: Internationalization
|
|
* Fix bug with pika_export_image()
|
|
* (moved it from load to save)
|
|
* V 1.07, PK, 16-Aug-06: Fix problems with internationalization
|
|
* (writing 255,0 instead of 255.0)
|
|
* Fix problem with not filling up properly last record
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#include <fitsio.h>
|
|
|
|
#include "libpika/stdplugins-intl.h"
|
|
|
|
|
|
#define LOAD_PROC "file-fits-load"
|
|
#define SAVE_PROC "file-fits-save"
|
|
#define PLUG_IN_BINARY "file-fits"
|
|
#define PLUG_IN_ROLE "pika-file-fits"
|
|
|
|
|
|
typedef struct _Fits Fits;
|
|
typedef struct _FitsClass FitsClass;
|
|
|
|
struct _Fits
|
|
{
|
|
PikaPlugIn parent_instance;
|
|
};
|
|
|
|
struct _FitsClass
|
|
{
|
|
PikaPlugInClass parent_class;
|
|
};
|
|
|
|
|
|
#define FITS_TYPE (fits_get_type ())
|
|
#define FITS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FITS_TYPE, Fits))
|
|
|
|
GType fits_get_type (void) G_GNUC_CONST;
|
|
|
|
typedef struct
|
|
{
|
|
gint naxis;
|
|
glong naxisn[3];
|
|
gint bitpix;
|
|
gint bpp;
|
|
gint datatype;
|
|
} FitsHduData;
|
|
|
|
static GList * fits_query_procedures (PikaPlugIn *plug_in);
|
|
static PikaProcedure * fits_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name);
|
|
|
|
static PikaValueArray * fits_load (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaMetadataLoadFlags *flags,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
static PikaValueArray * fits_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
|
|
static PikaImage * load_image (GFile *file,
|
|
GObject *config,
|
|
PikaRunMode run_mode,
|
|
GError **error);
|
|
static gint save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable,
|
|
GError **error);
|
|
|
|
static gint save_fits (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable);
|
|
|
|
static PikaImage * create_new_image (GFile *file,
|
|
guint pagenum,
|
|
guint width,
|
|
guint height,
|
|
PikaImageBaseType itype,
|
|
PikaImageType dtype,
|
|
PikaPrecision iprecision,
|
|
PikaLayer **layer,
|
|
GeglBuffer **buffer);
|
|
|
|
static gboolean load_dialog (PikaProcedure *procedure,
|
|
GObject *config);
|
|
static void show_fits_errors (gint status);
|
|
|
|
|
|
G_DEFINE_TYPE (Fits, fits, PIKA_TYPE_PLUG_IN)
|
|
|
|
PIKA_MAIN (FITS_TYPE)
|
|
DEFINE_STD_SET_I18N
|
|
|
|
|
|
static void
|
|
fits_class_init (FitsClass *klass)
|
|
{
|
|
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
|
|
|
|
plug_in_class->query_procedures = fits_query_procedures;
|
|
plug_in_class->create_procedure = fits_create_procedure;
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
|
}
|
|
|
|
static void
|
|
fits_init (Fits *fits)
|
|
{
|
|
}
|
|
|
|
static GList *
|
|
fits_query_procedures (PikaPlugIn *plug_in)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
list = g_list_append (list, g_strdup (LOAD_PROC));
|
|
list = g_list_append (list, g_strdup (SAVE_PROC));
|
|
|
|
return list;
|
|
}
|
|
|
|
static PikaProcedure *
|
|
fits_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name)
|
|
{
|
|
PikaProcedure *procedure = NULL;
|
|
|
|
if (! strcmp (name, LOAD_PROC))
|
|
{
|
|
procedure = pika_load_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
fits_load, NULL, NULL);
|
|
|
|
pika_procedure_set_menu_label (procedure,
|
|
_("Flexible Image Transport System"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Load file of the FITS file format"),
|
|
_("Load file of the FITS file format "
|
|
"(Flexible Image Transport System)"),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Peter Kirchgessner",
|
|
"Peter Kirchgessner (peter@kirchgessner.net), "
|
|
"Alex Sa.",
|
|
"1997 - 2023");
|
|
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/x-fits");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"fit,fits");
|
|
pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
|
|
"0,string,SIMPLE");
|
|
|
|
PIKA_PROC_AUX_ARG_INT (procedure, "replace",
|
|
_("Replacement for undefined pixels"),
|
|
_("Replacement for undefined pixels"),
|
|
0, 255, 0,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_AUX_ARG_INT (procedure, "use-data-min-max",
|
|
_("Pixel value scaling"),
|
|
_("Use DATAMIN/DATAMAX-scaling if possible"),
|
|
0, 1, 0,
|
|
G_PARAM_READWRITE);
|
|
}
|
|
else if (! strcmp (name, SAVE_PROC))
|
|
{
|
|
procedure = pika_save_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
FALSE, fits_save, NULL, NULL);
|
|
|
|
pika_procedure_set_image_types (procedure, "RGB, GRAY, INDEXED");
|
|
|
|
pika_procedure_set_menu_label (procedure,
|
|
_("Flexible Image Transport System"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Export file in the FITS file format"),
|
|
_("FITS exporting handles all image "
|
|
"types except those with alpha channels."),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Peter Kirchgessner",
|
|
"Peter Kirchgessner (peter@kirchgessner.net), "
|
|
"Alex Sa.",
|
|
"1997 - 2023");
|
|
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/x-fits");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"fit,fits");
|
|
}
|
|
|
|
return procedure;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
fits_load (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaMetadataLoadFlags *flags,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaValueArray *return_vals;
|
|
PikaImage *image;
|
|
GError *error = NULL;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
if (run_mode == PIKA_RUN_INTERACTIVE)
|
|
{
|
|
if (! load_dialog (procedure, G_OBJECT (config)))
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CANCEL,
|
|
NULL);
|
|
}
|
|
|
|
image = load_image (file, G_OBJECT (config), run_mode, &error);
|
|
|
|
if (! image)
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_EXECUTION_ERROR,
|
|
error);
|
|
|
|
return_vals = pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_SUCCESS,
|
|
NULL);
|
|
|
|
PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
|
|
|
|
return return_vals;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
fits_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaImage *duplicate_image;
|
|
PikaItem **flipped_drawables;
|
|
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
|
|
PikaExportReturn export = PIKA_EXPORT_CANCEL;
|
|
GError *error = NULL;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
switch (run_mode)
|
|
{
|
|
case PIKA_RUN_INTERACTIVE:
|
|
case PIKA_RUN_WITH_LAST_VALS:
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
export = pika_export_image (&image, &n_drawables, &drawables, "FITS",
|
|
PIKA_EXPORT_CAN_HANDLE_RGB |
|
|
PIKA_EXPORT_CAN_HANDLE_GRAY |
|
|
PIKA_EXPORT_CAN_HANDLE_INDEXED);
|
|
|
|
if (export == PIKA_EXPORT_CANCEL)
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CANCEL,
|
|
NULL);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (n_drawables != 1)
|
|
{
|
|
g_set_error (&error, G_FILE_ERROR, 0,
|
|
_("FITS format does not support multiple layers."));
|
|
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CALLING_ERROR,
|
|
error);
|
|
}
|
|
|
|
/* Flip image vertical since FITS writes from bottom to top */
|
|
duplicate_image = pika_image_duplicate (image);
|
|
pika_image_flip (duplicate_image, PIKA_ORIENTATION_VERTICAL);
|
|
flipped_drawables =
|
|
pika_image_get_selected_drawables (duplicate_image, &n_drawables);
|
|
|
|
if (! save_image (file, image, PIKA_DRAWABLE (flipped_drawables[0]), &error))
|
|
status = PIKA_PDB_EXECUTION_ERROR;
|
|
|
|
pika_image_delete (duplicate_image);
|
|
g_free (flipped_drawables);
|
|
|
|
if (export == PIKA_EXPORT_EXPORT)
|
|
{
|
|
pika_image_delete (image);
|
|
g_free (drawables);
|
|
}
|
|
|
|
return pika_procedure_new_return_values (procedure, status, error);
|
|
}
|
|
|
|
static PikaImage *
|
|
load_image (GFile *file,
|
|
GObject *config,
|
|
PikaRunMode run_mode,
|
|
GError **error)
|
|
{
|
|
PikaImage *image = NULL;
|
|
PikaLayer *layer;
|
|
GeglBuffer *buffer;
|
|
FILE *fp;
|
|
fitsfile *ifp;
|
|
FitsHduData hdu;
|
|
gint n_pics;
|
|
gint count = 1;
|
|
gint width;
|
|
gint height;
|
|
gint row_length;
|
|
int status = 0;
|
|
glong fpixel[3] = {1, 1, 1};
|
|
PikaImageBaseType itype = PIKA_GRAY;
|
|
PikaImageType dtype = PIKA_GRAYA_IMAGE;
|
|
PikaPrecision iprecision = PIKA_PRECISION_U16_NON_LINEAR;
|
|
const Babl *type = NULL;
|
|
const Babl *format = NULL;
|
|
gdouble *pixels;
|
|
gdouble datamin = 1.0E30f;
|
|
gdouble datamax = -1.0E30f;
|
|
gint channels = 1;
|
|
gint replace;
|
|
gdouble replace_val = 0;
|
|
gboolean use_datamin;
|
|
|
|
g_object_get (config,
|
|
"replace", &replace,
|
|
"use-data-min-max", &use_datamin,
|
|
NULL);
|
|
|
|
fp = g_fopen (g_file_peek_path (file), "rb");
|
|
|
|
if (! fp)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
|
_("Could not open '%s' for reading: %s"),
|
|
pika_file_get_utf8_name (file), g_strerror (errno));
|
|
return NULL;
|
|
}
|
|
|
|
fclose (fp);
|
|
|
|
if (fits_open_diskfile (&ifp, g_file_peek_path (file), READONLY, &status))
|
|
show_fits_errors (status);
|
|
|
|
if (! ifp)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"%s", _("Error during opening of FITS file"));
|
|
return NULL;
|
|
}
|
|
|
|
/* Get first item */
|
|
fits_get_num_hdus (ifp, &n_pics, &status);
|
|
|
|
if (status)
|
|
show_fits_errors (status);
|
|
|
|
if (n_pics <= 0)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"%s", _("FITS file keeps no displayable images"));
|
|
fits_close_file (ifp, &status);
|
|
return NULL;
|
|
}
|
|
|
|
while (count <= n_pics)
|
|
{
|
|
hdu.naxis = 0;
|
|
|
|
fits_movabs_hdu (ifp, count, NULL, &status);
|
|
|
|
fits_get_img_param (ifp, 3, &hdu.bitpix, &hdu.naxis, hdu.naxisn,
|
|
&status);
|
|
|
|
width = hdu.naxisn[0];
|
|
height = hdu.naxisn[1];
|
|
|
|
/* Skip if invalid dimensions; possibly header data */
|
|
if (hdu.naxis < 2)
|
|
{
|
|
count++;
|
|
continue;
|
|
}
|
|
|
|
type = babl_type ("double");
|
|
switch (hdu.bitpix)
|
|
{
|
|
case 8:
|
|
iprecision = PIKA_PRECISION_U8_LINEAR;
|
|
if (replace)
|
|
replace_val = 255;
|
|
break;
|
|
case 16:
|
|
iprecision = PIKA_PRECISION_U16_NON_LINEAR;
|
|
if (replace)
|
|
replace_val = G_MAXSHORT;
|
|
break;
|
|
case 32:
|
|
iprecision = PIKA_PRECISION_U32_LINEAR;
|
|
if (replace)
|
|
replace_val = G_MAXINT;
|
|
break;
|
|
case -32:
|
|
iprecision = PIKA_PRECISION_FLOAT_LINEAR;
|
|
if (replace)
|
|
replace_val = G_MAXFLOAT;
|
|
break;
|
|
case -64:
|
|
iprecision = PIKA_PRECISION_DOUBLE_LINEAR;
|
|
if (replace)
|
|
replace_val = G_MAXDOUBLE;
|
|
break;
|
|
}
|
|
|
|
if (hdu.naxis == 2)
|
|
{
|
|
itype = PIKA_GRAY;
|
|
dtype = PIKA_GRAYA_IMAGE;
|
|
format = babl_format_new (babl_model ("Y'"),
|
|
type,
|
|
babl_component ("Y'"),
|
|
NULL);
|
|
}
|
|
else if (hdu.naxisn[2])
|
|
{
|
|
/* Original RGB format */
|
|
if (hdu.naxisn[0] == 3)
|
|
{
|
|
width = hdu.naxisn[1];
|
|
height = hdu.naxisn[2];
|
|
}
|
|
channels = 3;
|
|
|
|
itype = PIKA_RGB;
|
|
dtype = PIKA_RGB_IMAGE;
|
|
format = babl_format_new (babl_model ("R'G'B'"),
|
|
type,
|
|
babl_component ("R'"),
|
|
babl_component ("G'"),
|
|
babl_component ("B'"),
|
|
NULL);
|
|
}
|
|
|
|
/* If RGB FITS image, we need to read in the whole image so we can convert
|
|
* the planes format to RGB */
|
|
if (hdu.naxis == 2)
|
|
pixels = (gdouble *) malloc (width * sizeof (gdouble) * channels);
|
|
else
|
|
pixels = (gdouble *) malloc (width * height * sizeof (gdouble) * channels);
|
|
|
|
if (! image)
|
|
{
|
|
image = create_new_image (file, count, width, height,
|
|
itype, dtype, iprecision,
|
|
&layer, &buffer);
|
|
}
|
|
else
|
|
{
|
|
layer = pika_layer_new (image, _("FITS HDU"), width, height,
|
|
dtype, 100,
|
|
pika_image_get_default_new_layer_mode (image));
|
|
pika_image_insert_layer (image, layer, NULL, 0);
|
|
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));
|
|
}
|
|
|
|
row_length = width * channels;
|
|
|
|
/* Calculate min/max pixel value for normalizing */
|
|
for (fpixel[1] = height; fpixel[1] >= 1; fpixel[1]--)
|
|
{
|
|
if (fits_read_pix (ifp, TDOUBLE, fpixel, row_length, NULL,
|
|
pixels, NULL, &status))
|
|
break;
|
|
|
|
for (gint ii = 0; ii < row_length; ii++)
|
|
{
|
|
if (pixels[ii] < datamin)
|
|
datamin = pixels[ii];
|
|
|
|
if (pixels[ii] > datamax)
|
|
datamax = pixels[ii];
|
|
}
|
|
}
|
|
|
|
if (status)
|
|
show_fits_errors (status);
|
|
|
|
/* Read pixel values in */
|
|
if (hdu.naxis == 2)
|
|
{
|
|
for (fpixel[1] = height; fpixel[1] >= 1; fpixel[1]--)
|
|
{
|
|
if (fits_read_pix (ifp, TDOUBLE, fpixel, row_length, &replace_val,
|
|
pixels, NULL, &status))
|
|
break;
|
|
|
|
if (datamin < datamax)
|
|
{
|
|
for (gint ii = 0; ii < row_length; ii++)
|
|
pixels[ii] = (pixels[ii] - datamin) / (datamax - datamin);
|
|
}
|
|
|
|
gegl_buffer_set (buffer,
|
|
GEGL_RECTANGLE (0, height - fpixel[1],
|
|
width, 1), 0,
|
|
format, pixels, GEGL_AUTO_ROWSTRIDE);
|
|
}
|
|
}
|
|
else if (hdu.naxisn[2] && hdu.naxisn[2] == 3)
|
|
{
|
|
gint total_size = width * height * channels;
|
|
|
|
fits_read_img (ifp, TDOUBLE, 1, total_size, &replace_val,
|
|
pixels, NULL, &status);
|
|
|
|
if (! status)
|
|
{
|
|
gdouble *temp;
|
|
|
|
temp = (gdouble *) malloc (width * height * sizeof (gdouble) * channels);
|
|
|
|
if (datamin < datamax)
|
|
{
|
|
for (gint ii = 0; ii < total_size; ii++)
|
|
pixels[ii] = (pixels[ii] - datamin) / (datamax - datamin);
|
|
}
|
|
|
|
for (gint ii = 0; ii < (total_size / 3); ii++)
|
|
{
|
|
temp[(ii * 3)] = pixels[ii];
|
|
temp[(ii * 3) + 1] = pixels[ii + (total_size / 3)];
|
|
temp[(ii * 3) + 2] = pixels[ii + ((total_size / 3) * 2)];
|
|
}
|
|
|
|
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
|
|
format, temp, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
if (temp)
|
|
g_free (temp);
|
|
}
|
|
}
|
|
|
|
if (status)
|
|
show_fits_errors (status);
|
|
|
|
g_object_unref (buffer);
|
|
|
|
if (hdu.naxisn[2] && hdu.naxisn[2] == 3)
|
|
{
|
|
/* Per SiriL developers, FITS images should be loaded from the
|
|
* bottom up. fits_read_img () loads them from top down, so we
|
|
* should flip the layer. */
|
|
pika_item_transform_flip_simple (PIKA_ITEM (layer),
|
|
PIKA_ORIENTATION_VERTICAL,
|
|
TRUE, -1.0);
|
|
}
|
|
|
|
if (pixels)
|
|
g_free (pixels);
|
|
|
|
count++;
|
|
}
|
|
|
|
/* As there might be different sized layers,
|
|
* we need to resize the canvas afterwards */
|
|
pika_image_resize_to_layers (image);
|
|
|
|
fits_close_file (ifp, &status);
|
|
|
|
if (! image)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"%s", _("FITS file keeps no displayable images"));
|
|
fits_close_file (ifp, &status);
|
|
return NULL;
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
static gint
|
|
save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable,
|
|
GError **error)
|
|
{
|
|
PikaImageType drawable_type;
|
|
gint retval;
|
|
|
|
drawable_type = pika_drawable_type (drawable);
|
|
|
|
/* Make sure we're not exporting an image with an alpha channel */
|
|
if (pika_drawable_has_alpha (drawable))
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"%s",
|
|
_("FITS export cannot handle images with alpha channels"));
|
|
return FALSE;
|
|
}
|
|
|
|
switch (drawable_type)
|
|
{
|
|
case PIKA_INDEXED_IMAGE: case PIKA_INDEXEDA_IMAGE:
|
|
case PIKA_GRAY_IMAGE: case PIKA_GRAYA_IMAGE:
|
|
case PIKA_RGB_IMAGE: case PIKA_RGBA_IMAGE:
|
|
break;
|
|
default:
|
|
g_message (_("Cannot operate on unknown image types."));
|
|
return (FALSE);
|
|
break;
|
|
}
|
|
|
|
pika_progress_init_printf (_("Exporting '%s'"),
|
|
pika_file_get_utf8_name (file));
|
|
|
|
|
|
|
|
retval = save_fits (file, image, drawable);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */
|
|
static PikaImage *
|
|
create_new_image (GFile *file,
|
|
guint pagenum,
|
|
guint width,
|
|
guint height,
|
|
PikaImageBaseType itype,
|
|
PikaImageType dtype,
|
|
PikaPrecision iprecision,
|
|
PikaLayer **layer,
|
|
GeglBuffer **buffer)
|
|
{
|
|
PikaImage *image;
|
|
|
|
image = pika_image_new_with_precision (width, height, itype, iprecision);
|
|
|
|
pika_image_undo_disable (image);
|
|
*layer = pika_layer_new (image, _("Background"), width, height,
|
|
dtype, 100,
|
|
pika_image_get_default_new_layer_mode (image));
|
|
pika_image_insert_layer (image, *layer, NULL, 0);
|
|
|
|
*buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (*layer));
|
|
|
|
return image;
|
|
}
|
|
|
|
/* Save direct colors (GRAY, GRAYA, RGB, RGBA) */
|
|
static gint
|
|
save_fits (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable)
|
|
{
|
|
fitsfile *fptr;
|
|
gint status = 0;
|
|
gint height, width, channelnum;
|
|
gint bitpix;
|
|
gint naxis = 2;
|
|
glong naxes[3];
|
|
gint export_type;
|
|
gint nelements;
|
|
void *data = NULL;
|
|
GeglBuffer *buffer;
|
|
const Babl *format, *type;
|
|
|
|
buffer = pika_drawable_get_buffer (drawable);
|
|
|
|
width = gegl_buffer_get_width (buffer);
|
|
height = gegl_buffer_get_height (buffer);
|
|
|
|
format = gegl_buffer_get_format (buffer);
|
|
type = babl_format_get_type (format, 0);
|
|
|
|
naxes[0] = width;
|
|
naxes[1] = height;
|
|
nelements = width * height;
|
|
|
|
if (type == babl_type ("u8"))
|
|
{
|
|
bitpix = 8;
|
|
export_type = TBYTE;
|
|
}
|
|
else if (type == babl_type ("u16"))
|
|
{
|
|
bitpix = 16;
|
|
export_type = TSHORT;
|
|
}
|
|
else if (type == babl_type ("u32"))
|
|
{
|
|
bitpix = 32;
|
|
export_type = TLONG;
|
|
}
|
|
else if (type == babl_type ("half") ||
|
|
type == babl_type ("float"))
|
|
{
|
|
bitpix = -32;
|
|
type = babl_type ("float");
|
|
export_type = TFLOAT;
|
|
}
|
|
else if (type == babl_type ("double"))
|
|
{
|
|
bitpix = -64;
|
|
export_type = TDOUBLE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
switch (pika_drawable_type (drawable))
|
|
{
|
|
case PIKA_GRAY_IMAGE:
|
|
format = babl_format_new (babl_model ("Y'"),
|
|
type,
|
|
babl_component ("Y'"),
|
|
NULL);
|
|
break;
|
|
|
|
case PIKA_GRAYA_IMAGE:
|
|
format = babl_format_new (babl_model ("Y'A"),
|
|
type,
|
|
babl_component ("Y'"),
|
|
babl_component ("A"),
|
|
NULL);
|
|
break;
|
|
|
|
case PIKA_RGB_IMAGE:
|
|
case PIKA_INDEXED_IMAGE:
|
|
format = babl_format_new (babl_model ("R'G'B'"),
|
|
type,
|
|
babl_component ("R'"),
|
|
babl_component ("G'"),
|
|
babl_component ("B'"),
|
|
NULL);
|
|
naxis = 3;
|
|
naxes[2] = 3;
|
|
break;
|
|
|
|
case PIKA_RGBA_IMAGE:
|
|
case PIKA_INDEXEDA_IMAGE:
|
|
format = babl_format_new (babl_model ("R'G'B'A"),
|
|
type,
|
|
babl_component ("R'"),
|
|
babl_component ("G'"),
|
|
babl_component ("B'"),
|
|
babl_component ("A"),
|
|
NULL);
|
|
naxis = 4;
|
|
naxes[2] = 4;
|
|
break;
|
|
}
|
|
|
|
channelnum = babl_format_get_n_components (format);
|
|
|
|
/* allocate a buffer for retrieving information from the pixel region */
|
|
if (export_type == TFLOAT)
|
|
data = (gfloat *) g_malloc (width * height * sizeof (gfloat) * channelnum);
|
|
else if (export_type == TDOUBLE)
|
|
data = (gdouble *) g_malloc (width * height * sizeof (gdouble) *
|
|
channelnum);
|
|
else
|
|
data = (guchar *) g_malloc (width * height * sizeof (guchar *) *
|
|
(bitpix / 8) * channelnum);
|
|
|
|
/* CFITSIO can't overwrite files unless you start the filename
|
|
* with a "!". Instead, we'll try to open the existing file
|
|
* in READWRITE mode, clear it, and then recreate it.
|
|
*/
|
|
if (fits_create_file (&fptr, g_file_peek_path (file), &status))
|
|
{
|
|
/* You have to set status back to 0 - subsequent successful
|
|
functions do not remove the error value */
|
|
status = 0;
|
|
|
|
if (fits_open_file (&fptr, g_file_peek_path (file), READWRITE, &status))
|
|
{
|
|
show_fits_errors (status);
|
|
return FALSE;
|
|
}
|
|
fits_delete_file (fptr, &status);
|
|
|
|
if (fits_create_file (&fptr, g_file_peek_path (file), &status))
|
|
{
|
|
show_fits_errors (status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (fits_create_img (fptr, bitpix, naxis, naxes, &status))
|
|
{
|
|
show_fits_errors (status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* FITS uses signed 16/32 integers, so we need to convert the unsigned
|
|
* values to that range via an offset */
|
|
if (bitpix == 16 || bitpix == 32)
|
|
{
|
|
GeglBufferIterator *iter;
|
|
|
|
iter = gegl_buffer_iterator_new (buffer,
|
|
GEGL_RECTANGLE (0, 0, width, height), 0,
|
|
format,
|
|
GEGL_BUFFER_READWRITE,
|
|
GEGL_ABYSS_NONE, 1);
|
|
|
|
while (gegl_buffer_iterator_next (iter))
|
|
{
|
|
gint length = iter->length;
|
|
|
|
if (bitpix == 16)
|
|
{
|
|
gushort *pixel = iter->items[0].data;
|
|
gushort offset = pow (2, 15);
|
|
|
|
while (length--)
|
|
{
|
|
for (gint i = 0; i < channelnum; i++)
|
|
pixel[i] -= offset;
|
|
|
|
pixel += channelnum;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
guint32 *pixel = iter->items[0].data;
|
|
guint32 offset = pow (2, 31);
|
|
|
|
while (length--)
|
|
{
|
|
for (gint i = 0; i < channelnum; i++)
|
|
pixel[i] -= offset;
|
|
|
|
pixel += channelnum;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Grayscale images can be exported as-is. RGB images must be
|
|
* converted into planes of RRR...GGG...BBB... */
|
|
if (naxis == 2)
|
|
{
|
|
gegl_buffer_get (buffer,
|
|
GEGL_RECTANGLE (0, 0, width, height), 1.0,
|
|
format, data,
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
|
|
|
if (fits_write_img (fptr, export_type, 1, nelements, data, &status))
|
|
{
|
|
show_fits_errors (status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gdouble *rgb_data;
|
|
gdouble *rgb_output;
|
|
const Babl *rgb_format;
|
|
const Babl *output_format = babl_format ("Y' double");
|
|
const Babl *converted_format;
|
|
|
|
rgb_format = (channelnum == 3) ? babl_format ("R'G'B' double") :
|
|
babl_format ("R'G'B'A double");
|
|
|
|
/* We export a single channel at a time, so we need a
|
|
* an output format with a single channel */
|
|
converted_format = babl_format_new (babl_model ("Y'"), type,
|
|
babl_component ("Y'"),
|
|
NULL);
|
|
|
|
rgb_data = (gdouble *) g_malloc (width * height * sizeof (gdouble) *
|
|
channelnum);
|
|
rgb_output = (gdouble *) g_malloc (width * height * sizeof (gdouble));
|
|
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0,
|
|
rgb_format, rgb_data,
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
|
|
|
for (gint rgb = 0; rgb < channelnum; rgb++)
|
|
{
|
|
gint offset = 0;
|
|
gint src_offset = 0;
|
|
void *converted_output = NULL;
|
|
|
|
for (gint i = 0; i < height - 1; i++)
|
|
{
|
|
for (gint j = 0; j < (width * channelnum); j += channelnum)
|
|
{
|
|
rgb_output[(j / channelnum) + offset] =
|
|
rgb_data[j + src_offset + rgb];
|
|
}
|
|
|
|
src_offset += width * channelnum;
|
|
offset += width;
|
|
}
|
|
|
|
if (export_type == TFLOAT)
|
|
converted_output = (gfloat *) g_malloc (width * height *
|
|
sizeof (gfloat));
|
|
else if (export_type == TDOUBLE)
|
|
converted_output = (gdouble *) g_malloc (width * height *
|
|
sizeof (gdouble));
|
|
else
|
|
converted_output = (guchar *) g_malloc (width * height *
|
|
sizeof (guchar *) *
|
|
(bitpix / 8));
|
|
|
|
babl_process (babl_fish (output_format, converted_format),
|
|
rgb_output, converted_output, nelements);
|
|
|
|
if (fits_write_img (fptr, export_type, 1, nelements,
|
|
converted_output, &status))
|
|
{
|
|
show_fits_errors (status);
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (converted_output);
|
|
}
|
|
|
|
g_free (rgb_data);
|
|
g_free (rgb_output);
|
|
}
|
|
|
|
g_free (data);
|
|
|
|
g_object_unref (buffer);
|
|
|
|
/* Add history of file update */
|
|
fits_write_history (fptr,
|
|
"THIS FITS FILE WAS GENERATED BY PIKA USING CFITSIO",
|
|
&status);
|
|
|
|
pika_progress_update (1.0);
|
|
|
|
if (fits_close_file (fptr, &status))
|
|
{
|
|
show_fits_errors (status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Load interface functions */
|
|
|
|
static gboolean
|
|
load_dialog (PikaProcedure *procedure,
|
|
GObject *config)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkListStore *store;
|
|
GtkWidget *frame;
|
|
gboolean run;
|
|
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
dialog = pika_procedure_dialog_new (procedure,
|
|
PIKA_PROCEDURE_CONFIG (config),
|
|
_("Open FITS File"));
|
|
|
|
pika_window_set_transient (GTK_WINDOW (dialog));
|
|
|
|
store = pika_int_store_new (_("Black"), 0,
|
|
_("White"), 255,
|
|
NULL);
|
|
frame = pika_procedure_dialog_get_int_radio (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"replace",
|
|
PIKA_INT_STORE (store));
|
|
gtk_widget_set_margin_bottom (frame, 12);
|
|
|
|
store = pika_int_store_new (_("Automatic"), 0,
|
|
_("By DATAMIN/DATAMAX"), 1,
|
|
NULL);
|
|
frame = pika_procedure_dialog_get_int_radio (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"use-data-min-max",
|
|
PIKA_INT_STORE (store));
|
|
gtk_widget_set_margin_bottom (frame, 12);
|
|
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
NULL);
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
return run;
|
|
}
|
|
|
|
static void
|
|
show_fits_errors (gint status)
|
|
{
|
|
gchar status_str[FLEN_STATUS];
|
|
|
|
/* Write out error messages of FITS-Library */
|
|
fits_get_errstatus (status, status_str);
|
|
g_message ("FITS: %s\n", status_str);
|
|
}
|