/*
 *  $Id: wipfile.c 29571 2026-03-05 15:49:32Z yeti-dn $
 *  Copyright (C) 2010-2025 David Necas (Yeti), Petr Klapetek, Daniil Bratashov (dn2010)
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net, dn2010@gmail.com
 *
 *  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 2 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, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * Thanks to GSXM project crew for description of tag format and datatypes.
 *
 * TODO: metadata loading
 */

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-wipfile-spm">
 *   <comment>WITec Project data</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="WIT_PRCT"/>
 *     <match type="string" offset="0" value="WIT_PR06"/>
 *   </magic>
 *   <glob pattern="*.wip"/>
 *   <glob pattern="*.WIP"/>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-FILEMAGIC]
 * # WITec
 * 0 string WIT_PRCT WITec Project data
 * 0 string WIT_PR06 WITec Project data
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * WITec Project data
 * .wip
 * Read SPS Volume
 **/

#include "config.h"
#include <glib/gi18n-lib.h>
#include <string.h>
#include <stdlib.h>
#include <gwy.h>

#include "err.h"
#include "get.h"

#define MAGIC "WIT_PRCT"
#define MAGIC2 "WIT_PR06"
#define MAGIC_SIZE (8)

#define EXTENSION ".wip"

#define H (4.135667662E-15)
#define C (299792458)

typedef enum {
    WIP_TAG_LIST     = 0, /* list of other tags */
    WIP_TAG_EXTENDED = 1, /* x86 FPU native type, 10 bytes */
    WIP_TAG_DOUBLE   = 2,
    WIP_TAG_FLOAT    = 3,
    WIP_TAG_INT64    = 4,
    WIP_TAG_INT32    = 5,
    WIP_TAG_UINT32   = 6,
    WIP_TAG_CHAR     = 7,
    WIP_TAG_BOOL     = 8, /* 1 byte */
    WIP_TAG_STRING   = 9  /* int32 = nchars, n bytes = string */
} WIPTagType;

gsize WIPTagDataSize[10] = { 0, 10, 8, 4, 8, 4, 4, 1, 1, 0 };

typedef enum {
    WIP_DATA_LIST     = 0, /* list of tags */
    WIP_DATA_INT64    = 1,
    WIP_DATA_INT32    = 2,
    WIP_DATA_INT16    = 3,
    WIP_DATA_INT8     = 4,
    WIP_DATA_UINT32   = 5,
    WIP_DATA_UINT16   = 6,
    WIP_DATA_UINT8    = 7,
    WIP_DATA_BOOL     = 8, /* 1 byte */
    WIP_DATA_FLOAT    = 9,
    WIP_DATA_DOUBLE   = 10,
    WIP_DATA_EXTENDED = 11 /* x86 FPU native type, 10 bytes */
} WIPDataType;

gsize WIPDataSize[12] = { 0, 8, 4, 2, 1, 4, 2, 1, 1, 4, 8, 10 };

typedef enum {
    WIP_UNIT_NANOMETER   = 0,
    WIP_UNIT_MIKROMETER  = 1,
    WIP_UNIT_SM_1        = 2, /* 1/cm */
    WIP_UNIT_RAMAN_SHIFT = 3, /* 1/cm relative*/
    WIP_UNIT_EV          = 4,
    WIP_UNIT_MEV         = 5, /* meV m = milli*/
    WIP_UNIT_EV_REL      = 6,
    WIP_UNIT_MEV_REL     = 7
} WIPUnitIndex;

typedef struct {
    guint32       name_length;
    guchar       *name;         /* name_length bytes */
    WIPTagType    type;
    gint64        data_start;
    gint64        data_end;
    const guchar *data;
} WIPTag;

/* TD*Interpretation */
typedef struct {
    guint    id;
    gchar   *unitname;
    gdouble  unitmultiplier;
    gdouble  laser_wl; /* for 1/cm axis only */
} WIPAxis;

/* TDSpectralTransformation for optical spectra;
 * to recalculate x spectral data from
 * spectrometer calibrations */
typedef struct {
    guint    id;
    guint    transform_type; /* should be 1 */
    gdouble  polynom [3]; /* polynomial coeffs. should be zeros */
    gdouble  nc; /* central pixel number */
    gdouble  lambdac; /* central pixel lambda in nm */
    gdouble  gamma; /* angle between incident and diffracted light */
    gdouble  delta; /* CCD inclination */
    gdouble  m; /* diffraction order */
    gdouble  d; /* 1e6/lines per mm */
    gdouble  x; /* pixel size */
    gdouble  f; /* focal distance */
    gchar   *unitname; /* nm */
} WIPSpectralTransform;

/* TDSpectralInterpretation for spectra */
typedef struct {
    guint        id;
    WIPUnitIndex unitindex;
    gdouble      excitation_wavelength;
} WIPSpectralInterpretation;

typedef struct {
    guint id;
    gchar *unitname;
    gdouble scale[9];
} WIPSpaceTransform;

typedef struct {
    guint dimension;
    WIPDataType datatype;
    guint xrange, yrange;
    gpointer data;
} WIPGraphData;

typedef struct {
    guint sizex;
    guint sizey;
    guint sizegraph;
    guint spacetransformid;
    guint xtransformid;
    guint xinterpid;
    guint zinterpid;
    guint dimension;
    WIPDataType datatype;
    guint xrange;
    guint yrange;
    gsize datasize;
    const guchar *data;
} WIPGraph;

typedef struct {
    guint version;
    guint sizex;
    guint sizey;
    guint postransformid;
    guint zinterpid;
    guint dimension;
    WIPDataType datatype;
    guint xrange;
    guint yrange;
    gsize datasize;
    const guchar *data;
} WIPImage;

typedef struct {
    guint spacetransformid;
    gsize streamsize;
    gsize datasize;
    const guchar *data;
} WIPBitmap;

typedef struct {
    guint numgraph;
    guint numimages;
    guint numbricks;
    GwyFile *file;
    const gchar *filename;
} WIPFile;

typedef struct {
    guint id;
    GNode *node;
} WIPIdNode;

static gboolean       module_register          (void);
static gint           detect_file              (const GwyFileDetectInfo *fileinfo,
                                                gboolean only_name);
static GwyFile*       load_file                (const gchar *filename,
                                                GwyRunModeFlags mode,
                                                GError **error);
static WIPTag*        read_tag                 (const guchar **pos,
                                                gsize *start,
                                                gsize *end);
static void           free_wip_tag             (WIPTag *tag);
static gboolean       read_all_tags            (const guchar *buffer,
                                                gsize start,
                                                gsize end,
                                                GNode *tagtree,
                                                gint n,
                                                GError **error);
static gboolean       free_wip_leaf            (GNode *node,
                                                gpointer data);
static gboolean       read_graph_tags          (GNode *node,
                                                gpointer header);
static gboolean       read_sp_transform_tags   (GNode *node,
                                                gpointer transform);
static gboolean       read_sp_interpr_tags     (GNode *node,
                                                gpointer interp);
static gboolean       read_space_tr_tag        (GNode *node,
                                                gpointer transform);
static gboolean       read_axis_tags           (GNode *node,
                                                gpointer axis);
static gboolean       read_bitmap_tags         (GNode *node,
                                                gpointer data);
static gboolean       find_by_id               (GNode *node,
                                                gpointer idnode);
static gboolean       read_caption             (GNode *node,
                                                gpointer caption);
static GwyField*      read_bmp                 (const guchar *bmpdata,
                                                gsize datasize,
                                                gdouble xscale,
                                                gdouble yscale,
                                                gint power10xy);
static gdouble        pixel_to_lambda          (gint i,
                                                WIPSpectralTransform *transform);
static GwyGraphModel* read_graph               (GNode *node);
static GwyBrick*      read_graph_image         (GNode *node);
static GwyField*      read_image               (GNode *node);
static GwyField*      read_bitmap              (GNode *node);
static gboolean       read_data                (GNode *node,
                                                gpointer filedata);
static GwyUnit*       xtransform_data          (const WIPSpectralInterpretation *xinterp,
                                                gdouble *data,
                                                gint res);
static gboolean       fix_scale                (gdouble *scale,
                                                const gchar *name);
static GwyRawDataType wip_data_type_to_raw_type(WIPDataType type);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Imports WItec Project data files."),
    "Daniil Bratashov <dn2010@gmail.com>",
    "0.13",
    "David Nečas (Yeti) & Petr Klapetek & Daniil Bratashov",
    "2010",
};

GWY_MODULE_QUERY2(module_info, wipfile)

static gboolean
module_register(void)
{
    gwy_file_func_register("wipfile",
                           N_("WItec Project files (.wip)"),
                           detect_file, load_file, NULL, NULL);

    return TRUE;
}

static gint
detect_file(const GwyFileDetectInfo *fileinfo, gboolean only_name)
{
    gint score = 0;

    if (only_name)
        return g_str_has_suffix(fileinfo->name_lowercase, EXTENSION) ? 20 : 0;

    if (fileinfo->buffer_len > MAGIC_SIZE
        && (memcmp(fileinfo->head, MAGIC, MAGIC_SIZE) == 0 || memcmp(fileinfo->head, MAGIC2, MAGIC_SIZE) == 0))
        score = 100;

    return score;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    const guchar *p, *buffer = NULL;
    gsize size, cur;
    GError *err = NULL;
    WIPTag *tag;
    WIPFile filedata;
    GNode *tagtree = NULL;
    GwyFile *file = NULL;

    if (!gwy_map_file_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }

    if (size <= MAGIC_SIZE
        || (memcmp(buffer, MAGIC, MAGIC_SIZE) && memcmp(buffer, MAGIC2, MAGIC_SIZE))) {
        err_FILE_TYPE(error, "WITec Project");
        goto end;
    }

    p = buffer + 8; /* skip magic header */
    cur = 8;
    if (!(tag = read_tag(&p, &cur, &size))) {
        err_FILE_TYPE(error, "WITec Project");
        goto end;
    }

    if (tag->type || strncmp(tag->name, "WITec Project ", tag->name_length)) {
        err_FILE_TYPE(error, "WITec Project");
        free_wip_tag(tag);
        goto end;
    }

    tagtree = g_node_new(tag);
    if (!read_all_tags(buffer, tag->data_start, tag->data_end, tagtree, 1, error))
        goto end;

    file = gwy_file_new_in_construction();
    gwy_clear1(filedata);
    filedata.file = file;
    filedata.filename = filename;

    g_node_traverse(tagtree, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_data, &filedata);
    g_node_traverse(tagtree, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, free_wip_leaf, NULL);

    if (!filedata.numimages && !filedata.numbricks && !filedata.numgraph) {
        err_NO_DATA(error);
        g_clear_object(&file);
    }

end:
    if (tagtree)
        g_node_destroy(tagtree);
    gwy_unmap_file_contents(buffer);

    return file;
}

static WIPTag*
read_tag(const guchar **pos, gsize *start, gsize *end)
{
    WIPTag *tag;
    const guchar *p;
    gsize maxsize;

    p = *pos;
    maxsize = *end - *start;
    if (maxsize < 4)
        return NULL;
    tag = g_new0(WIPTag, 1);
    tag->name_length = gwy_get_guint32_le(&p);
    if (maxsize < 24+tag->name_length) {
        g_free(tag);
        return NULL;
    }
    tag->name = g_strndup(p, tag->name_length);
    p += tag->name_length;
    tag->type = (WIPTagType)gwy_get_guint32_le(&p);
    tag->data_start = gwy_get_gint64_le(&p);
    tag->data_end = gwy_get_gint64_le(&p);
    if ((tag->data_start < *start) || (tag->data_end > *end) || (tag->data_end - tag->data_start < 0)) {
        g_free(tag);
        return NULL;
    }
    tag->data = p;

    gwy_debug("%d %s %d %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT "",
              tag->name_length, tag->name, tag->type, tag->data_start,
              tag->data_end);

    *pos = p;

    return tag;
}

static void
free_wip_tag(WIPTag *tag)
{
    g_free(tag->name);
    g_free(tag);
}

static gboolean
read_all_tags(const guchar *buffer, gsize start,
              gsize end, GNode *tagtree, gint n,
              GError **error)
{
    const guchar *p;
    gsize cur;
    WIPTag *tag;
    GNode *tagpos;

    cur = start;
    while (cur < end) {
        p = (guchar *)(buffer + cur);
        if (!(tag = read_tag(&p, &cur, &end))) {
            g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                        _("Cannot read tag."));
            return FALSE;
        }
        else {
            tagpos = g_node_insert_data(tagtree, -1, tag);
            if (!tag->type && n < 255) {
                if (!read_all_tags(buffer, tag->data_start, tag->data_end, tagpos, n+1, error))
                    return FALSE;
            }
            cur = tag->data_end;
        }
    }
    return TRUE;
}

static gboolean
free_wip_leaf(GNode *node,
              G_GNUC_UNUSED gpointer data)
{
    free_wip_tag((WIPTag *)node->data);
    node->data = NULL;

    return FALSE;
}

static gboolean
read_graph_tags(GNode *node, gpointer header)
{
    WIPTag *tag;
    WIPGraph *graphheader;
    const guchar *p;

    tag = node->data;
    graphheader = (WIPGraph *)header;
    p = tag->data;
    if (!strncmp(tag->name, "SizeX", 5))
        graphheader->sizex = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "SizeY", 5))
        graphheader->sizey = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "SizeGraph", 9))
        graphheader->sizegraph = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "SpaceTransformationID", 21))
        graphheader->spacetransformid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "XTransformationID", 17))
        graphheader->xtransformid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "XInterpretationID", 17))
        graphheader->xinterpid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "ZInterpretationID", 17))
        graphheader->zinterpid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "Dimension", 9))
        graphheader->dimension = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "DataType", 8))
        graphheader->datatype = (WIPDataType)gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "Ranges", 6)) {
        graphheader->xrange = gwy_get_gint32_le(&p);
        graphheader->yrange = gwy_get_gint32_le(&p);
    }
    else if (!strncmp(tag->name, "Data", 4)) {
        graphheader->data = p;
        graphheader->datasize = (gsize)(tag->data_end-tag->data_start);
    }

    return FALSE;
}

static gboolean
read_image_tags(GNode *node, gpointer header)
{
    WIPTag *tag;
    WIPImage *imageheader;
    const guchar *p;

    tag = node->data;
    imageheader = (WIPImage *)header;
    p = tag->data;
    if (!strncmp(tag->name, "Version", 7))
        imageheader->version = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "SizeX", 5))
        imageheader->sizex = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "SizeY", 5))
        imageheader->sizey = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "PositionTransformationID", 24))
        imageheader->postransformid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "ZInterpretationID", 17))
        imageheader->zinterpid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "Dimension", 9))
        imageheader->dimension = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "DataType", 8))
        imageheader->datatype = (WIPDataType)gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "Ranges", 6)) {
        imageheader->xrange = gwy_get_gint32_le(&p);
        imageheader->yrange = gwy_get_gint32_le(&p);
    }
    else if (!strncmp(tag->name, "Data", 4)) {
        imageheader->data = p;
        imageheader->datasize = (gsize)(tag->data_end-tag->data_start);
    }

    return FALSE;
}

static gboolean
read_sp_transform_tags(GNode *node, gpointer transform)
{
    WIPTag *tag;
    WIPSpectralTransform *sp_transform;
    const guchar *p;
    gint i, str_len;

    tag = node->data;
    sp_transform = (WIPSpectralTransform *)transform;
    p = tag->data;
    if (!strncmp(tag->name, "SpectralTransformationType", 26))
        sp_transform->transform_type = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "Polynom", 7)) {
        for (i = 0; i < 3; i++)
            sp_transform->polynom[i] = gwy_get_gdouble_le(&p);
    }
    else if (!strncmp(tag->name, "nC", 2))
        sp_transform->nc = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "LambdaC", 7))
        sp_transform->lambdac = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "Gamma", 5))
        sp_transform->gamma = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "Delta", 5))
        sp_transform->delta = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "m", 1))
        sp_transform->m = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "d", 1))
        sp_transform->d = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "x", 1))
        sp_transform->x = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "f", 1))
        sp_transform->f = gwy_get_gdouble_le(&p);
    else if (!strncmp(tag->name, "StandardUnit", 12)) {
        str_len = gwy_get_gint32_le(&p);
        sp_transform->unitname = gwy_convert_to_utf8(p, str_len, "ISO-8859-1");
    }

    return FALSE;
}

static gboolean
read_sp_interpr_tags(GNode *node, gpointer interp)
{
    WIPTag *tag;
    WIPSpectralInterpretation *sp_interp;
    const guchar *p;

    tag = node->data;
    sp_interp = (WIPSpectralInterpretation *)interp;
    p = tag->data;
    if (!strncmp(tag->name, "UnitIndex", 9))
        sp_interp->unitindex = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "ExcitationWaveLength", 20))
        sp_interp->excitation_wavelength = gwy_get_gdouble_le(&p);

    return FALSE;
}

static gboolean
read_space_tr_tag(GNode *node, gpointer transform)
{
    WIPTag *tag;
    WIPSpaceTransform *sp_transform;
    const guchar *p;
    gint i, str_len;

    tag = node->data;
    sp_transform = (WIPSpaceTransform *)transform;
    p = tag->data;
    if (!strncmp(tag->name, "Scale", 5)) {
        for (i = 0; i < 9; i++)
            sp_transform->scale[i] = gwy_get_gdouble_le(&p);
    }
    else if (!strncmp(tag->name, "StandardUnit", 12)) {
        str_len = gwy_get_gint32_le(&p);
        sp_transform->unitname = gwy_convert_to_utf8(p, str_len, "ISO-8859-1");
    }

    return FALSE;
}

static gboolean
read_axis_tags(GNode *node, gpointer axis)
{
    WIPTag *tag;
    WIPAxis *tmp_axis;
    const guchar *p;
    gint str_len;

    tag = node->data;
    tmp_axis = (WIPAxis *)axis;
    p = tag->data;
    if (!strncmp(tag->name, "UnitName", 8)) {
        str_len = gwy_get_gint32_le(&p);
        tmp_axis->unitname = gwy_convert_to_utf8(p, str_len, "ISO-8859-1");
    }

    return FALSE;
}

static gboolean
read_bitmap_tags(GNode *node, gpointer data)
{
    WIPTag *tag;
    WIPBitmap *bitmap;
    const guchar *p;

    tag = node->data;
    bitmap = (WIPBitmap *)data;
    p = tag->data;
    if (!strncmp(tag->name, "SpaceTransformationID", 21))
        bitmap->spacetransformid = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "StreamSize", 10))
        bitmap->streamsize = gwy_get_gint32_le(&p);
    else if (!strncmp(tag->name, "StreamData", 10)) {
        bitmap->data = p;
        bitmap->datasize = (gsize)(tag->data_end-tag->data_start);
    }

    return FALSE;
}

static gboolean
find_by_id(GNode *node, gpointer idnode)
{
    WIPTag *tag;
    WIPIdNode *idnode_tmp;
    const guchar *p;
    gint id_temp;

    tag = node->data;
    p = tag->data;
    idnode_tmp = (WIPIdNode *)idnode;
    id_temp = 0;
    if (!strncmp(tag->name, "ID", 2)) {
        id_temp = gwy_get_gint32_le(&p);
        if (id_temp == idnode_tmp->id) {
            idnode_tmp->node = node;

            return TRUE;
        }
    }

    return FALSE;
}

static gboolean
read_caption(GNode *node, gpointer caption)
{
    gchar *str;
    gint str_len;
    WIPTag *tag;
    const guchar *p;

    tag = node->data;
    if (!strncmp(tag->name, "Caption", 7)) {
        p = tag->data;
        str_len = gwy_get_gint32_le(&p);
        str = g_strndup(p, str_len);
        g_string_printf(caption, "%s", str);
        g_free(str);
        return TRUE;
    }

    return FALSE;
}

// FIXME: Imported as greyscale
static GwyField*
read_bmp(const guchar *bmpdata,
         gsize datasize,
         gdouble xscale, gdouble yscale,
         gint power10xy)
{
    GwyField *dfield = NULL;
    gdouble *data;
    gint i, j, width, height, rowstride, bpp;
    GdkPixbufLoader *loader;
    GError *err = NULL;
    GdkPixbuf *pixbuf = NULL;
    guchar *pixels, *pix_p;

    if (!bmpdata)
        return NULL;

    loader = gdk_pixbuf_loader_new();
    if (!gdk_pixbuf_loader_write(loader, bmpdata, datasize, &err)) {
        g_object_unref(loader);
        g_clear_error(&err);
        return NULL;
    }
    gwy_debug("Closing the loader.");
    if (!gdk_pixbuf_loader_close(loader, &err)) {
        g_object_unref(loader);
        g_clear_error(&err);
        return NULL;
    }
    gwy_debug("Trying to get the pixbuf.");
    pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
    gwy_debug("Pixbuf is: %p.", pixbuf);
    g_return_val_if_fail(pixbuf, NULL);
    g_object_ref(pixbuf);
    gwy_debug("Finalizing loader.");
    g_object_unref(loader);

    pixels = gdk_pixbuf_get_pixels(pixbuf);
    width = gdk_pixbuf_get_width(pixbuf);
    height = gdk_pixbuf_get_height(pixbuf);
    rowstride = gdk_pixbuf_get_rowstride(pixbuf);
    bpp = gdk_pixbuf_get_has_alpha(pixbuf) ? 4 : 3;

    dfield = gwy_field_new(width, height,
                           width * xscale * gwy_exp10(power10xy), height * yscale * gwy_exp10(power10xy),
                           TRUE);
    data = gwy_field_get_data(dfield);
    for (i = 0; i < height; i++) {
        pix_p = pixels + i * rowstride;
        for (j = 0; j < width; j++) {
            guchar red = pix_p[bpp*j];
            guchar green = pix_p[bpp*j+1];
            guchar blue = pix_p[bpp*j+2];

            data[i * width + j] = (0.2126 * red + 0.7152 * green + 0.0722 * blue)/255.0;
        }
    }
    g_object_unref(pixbuf);

    return dfield;
}

/*
 * spectral transform from here:
 * http://www.horiba.com/us/en/scientific/products/optics-tutorial/wavelength-pixel-position/
 */
static gdouble
pixel_to_lambda(gint i, WIPSpectralTransform *transform)
{
    gdouble lambda, alpha, betac, hc, lh, hi, betah, betai;

    if ((transform->d == 0.0) || (transform->m == 0.0)
        || (cos(transform->gamma / 2.0) == 0.0)
        || (transform->lambdac * transform->m / transform->d / 2.0 / cos(transform->gamma / 2.0) > 1.0)
        || (transform->lambdac * transform->m / transform->d / 2.0 / cos(transform->gamma / 2.0) < -1.0))
        return i;
    alpha = asin(transform->lambdac * transform->m / transform->d / 2.0 / cos(transform->gamma / 2.0))
            - transform->gamma / 2.0;
    betac = transform->gamma + alpha;
    hc = transform->f * sin(transform->delta);
    lh = transform->f * cos(transform->delta);
    hi = transform->x * (transform->nc - i) + hc;
    betah = betac + transform->delta;
    if (lh == 0.0)
        return i;
    betai = betah - atan(hi / lh);
    lambda = transform->d / transform->m * (sin(alpha) + sin(betai));

    return lambda;
}

static GwyGraphModel*
read_graph(GNode *node)
{
    WIPGraph header;
    WIPSpectralTransform xtransform;
    WIPSpectralInterpretation xinterp;
    WIPAxis yaxis;
    WIPIdNode idnode;
    GwyGraphModel *gmodel;
    GwyGraphCurveModel *gcmodel;
    GwyUnit *siunitx, *siunity;
    gdouble *xdata, *ydata;
    gint numpoints, i;
    GString *caption;

    gwy_clear1(header);
    g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_graph_tags, &header);

    if ((header.sizex != 1) || (header.sizey != 1)) { /* image */
        return NULL;
    }

    numpoints = header.yrange;
    if ((numpoints <= 0)
        || (header.datatype != WIP_DATA_FLOAT)
        || (header.datasize != WIPDataSize[header.datatype] * numpoints)) {
        return NULL;
    }

    /* Read ydata, fallback xdata */
    xdata = gwy_math_linspace(NULL, numpoints, 0, 1);
    ydata = g_new(gdouble, numpoints);
    gwy_convert_raw_data(header.data, numpoints, 1, GWY_RAW_DATA_FLOAT, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                         ydata, 1.0, 0.0);

    /* Read caption */
    caption = g_string_new("Unnamed graph");
    g_node_traverse(node->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_caption, caption);

    /* Try to read xdata */
    gwy_clear1(idnode);
    idnode.id = header.xtransformid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);

    gwy_clear1(xtransform);
    if (idnode.node && node->parent->parent && idnode.node->parent->parent) {
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
                        read_sp_transform_tags, &xtransform);
    }
    if ((xtransform.transform_type != 1)
        || (xtransform.m < 0.01) || (xtransform.f < 0.01)
        || (xtransform.nc < 0.0) || (xtransform.nc > numpoints)) {
        /* xtransform not read correctly, fallback to point numbers */
    }
    else {
        for (i = 0; i < numpoints; i++)
            xdata[i] = pixel_to_lambda(i, &xtransform);
    }

    idnode.id = header.xinterpid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);

    gwy_clear1(xinterp);
    if (idnode.node && node->parent->parent && idnode.node->parent->parent) {
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
                        read_sp_interpr_tags, &xinterp);
    }

    /* recalculating to right units */
    if (xtransform.unitname) {
        siunitx = xtransform_data(&xinterp, xdata, numpoints);
        g_free(xtransform.unitname);
    }
    else
        siunitx = gwy_unit_new("px");

    /* Try to read y units */
    idnode.id = header.zinterpid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);
    gwy_clear1(yaxis);
    if (idnode.node && node->parent->parent && idnode.node->parent->parent)
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_axis_tags, &yaxis);
    siunity = gwy_unit_new(yaxis.unitname);
    g_free(yaxis.unitname);

    /* Packing */
    gmodel = g_object_new(GWY_TYPE_GRAPH_MODEL,
                          "title", caption->str,
                          "unit-x", siunitx,
                          "unit-y", siunity,
                          NULL);
    gcmodel = g_object_new(GWY_TYPE_GRAPH_CURVE_MODEL,
                           "description", caption->str,
                           "mode", GWY_GRAPH_CURVE_LINE,
                           "color", gwy_graph_get_preset_color(0),
                           NULL);
    g_object_unref(siunitx);
    g_object_unref(siunity);
    gwy_graph_curve_model_set_data(gcmodel, xdata, ydata, numpoints);
    g_free(xdata);
    g_free(ydata);
    gwy_graph_curve_model_enforce_order(gcmodel);
    gwy_graph_model_add_curve(gmodel, gcmodel);
    g_object_unref(gcmodel);
    g_string_free(caption, TRUE);

    return gmodel;
}

static GwyBrick*
read_graph_image(GNode *node)
{
    WIPGraph header;
    WIPSpectralTransform xtransform;
    WIPSpectralInterpretation xinterp;
    WIPAxis waxis;
    WIPSpaceTransform xyaxis;
    WIPIdNode idnode;
    GwyBrick *brick = NULL, *brick2;
    GwyLine *cal;
    GwyUnit *siunitxy = NULL, *siunitz = NULL, *siunitw = NULL;
    gdouble *data;
    gdouble xscale, yscale, wscale;
    gint i;
    gint xres, yres, zres;
    gint power10xy = 0, power10w = 0;
    GwyRawDataType rawtype;

    gwy_clear1(header);
    g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_graph_tags, &header);

    if ((header.sizex <= 1) && (header.sizey <= 1)) { /* not an image */
        return NULL;
    }

    gwy_debug("sizex = %d sizey = %d", header.sizex, header.sizey);
    gwy_debug("sizegraph = %d", header.sizegraph);
    gwy_debug("dimension=%d", header.dimension);
    gwy_debug("datatype = %d", header.datatype);
    gwy_debug("xrange = %d yrange = %d", header.xrange, header.yrange);

    xres = header.sizex;
    yres = header.sizey;
    zres = header.sizegraph;

    gwy_debug("numpoints * databytes = %" G_GSIZE_FORMAT ", datasize = %" G_GSIZE_FORMAT "",
              xres * yres * zres * WIPDataSize[header.datatype], header.datasize);
    if ((xres * yres * zres <= 0)
        || (header.datatype > 10)
        || (header.datasize != (WIPDataSize[header.datatype] * xres * yres * zres))) {
        return NULL;
    }

    /* Try to read xy units and scale */
    gwy_clear1(idnode);
    idnode.id = header.spacetransformid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);

    gwy_clear1(xyaxis);
    g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_space_tr_tag, &xyaxis);
    siunitxy = gwy_unit_new_parse(xyaxis.unitname, &power10xy);
    g_free(xyaxis.unitname);
    xscale = xyaxis.scale[0];
    yscale = xyaxis.scale[4];
    fix_scale(&xscale, "x");
    fix_scale(&yscale, "y");

    /* Try to read w units */
    idnode.id = header.zinterpid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);
    gwy_clear1(waxis);
    if (idnode.node && node->parent->parent && idnode.node->parent->parent) {
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
                        read_axis_tags, &waxis);
    }
    siunitw = gwy_unit_new_parse(waxis.unitname, &power10w);
    g_free(waxis.unitname);
    wscale = gwy_exp10(power10w);
    if (wscale == 0.0)
        wscale = 1.0;

    brick = gwy_brick_new(zres, yres, xres,
                          zres, yres * gwy_exp10(power10xy) * yscale, xres * gwy_exp10(power10xy) * xscale,
                          TRUE);
    data = gwy_brick_get_data(brick);

    if (header.datatype == WIP_DATA_LIST) {
        /* Cannot read.  FIXME: What then – leave the data as uninitialised memory? */
    }
    else if ((rawtype = wip_data_type_to_raw_type(header.datatype)) != (GwyRawDataType)-1) {
        gwy_convert_raw_data(header.data, xres*yres*zres, 1, rawtype, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                             gwy_brick_get_data(brick), wscale, 0.0);
        brick2 = gwy_brick_new_alike(brick, FALSE);
        gwy_brick_transpose(brick, brick2, GWY_BRICK_TRANSPOSE_ZYX, FALSE, FALSE, FALSE);
        GWY_SWAP(GwyBrick*, brick, brick2);
        g_object_unref(brick2);
        gwy_brick_set_xreal(brick, xres * gwy_exp10(power10xy) * xscale);
        gwy_brick_set_yreal(brick, yres * gwy_exp10(power10xy) * yscale);
        gwy_brick_set_zreal(brick, zres);
    }
    else {
        g_warning("Wrong datatype");
    }

    idnode.id = header.xinterpid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);

    gwy_clear1(xinterp);
    g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_sp_interpr_tags, &xinterp);

    /* Try to read zcalibration */
    idnode.id = header.xtransformid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);

    gwy_clear1(xtransform);
    if (idnode.node && node->parent->parent && idnode.node->parent->parent) {
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
                        read_sp_transform_tags, &xtransform);
    }
    if ((xtransform.transform_type != 1)
        || (xtransform.m < 0.01) || (xtransform.f < 0.01)
        || (xtransform.nc < 0.0) || (xtransform.nc > zres)) {
        /* xtransform not read correctly, fallback to point numbers */
    }
    else {
        cal = gwy_line_new(zres, zres, FALSE);
        data = gwy_line_get_data(cal);
        for (i = 0; i < zres; i++)
            data[i] = pixel_to_lambda(i, &xtransform);

        /* recalculating to right units */
        if (xtransform.unitname) {
            siunitz = xtransform_data(&xinterp, data, zres);
            g_free(xtransform.unitname);
        }
        else
            siunitz = gwy_unit_new("px");

        gwy_unit_assign(gwy_line_get_unit_y(cal), siunitz);
        gwy_brick_set_zcalibration(brick, cal);
        g_object_unref(cal);
    }

    if (!siunitz)
        siunitz = gwy_unit_new("px");

    gwy_unit_assign(gwy_brick_get_unit_x(brick), siunitxy);
    gwy_unit_assign(gwy_brick_get_unit_y(brick), siunitxy);
    gwy_unit_assign(gwy_brick_get_unit_z(brick), siunitz);
    gwy_unit_assign(gwy_brick_get_unit_w(brick), siunitw);

    g_object_unref(siunitxy);
    g_object_unref(siunitz);
    g_object_unref(siunitw);

    return brick;
}

static GwyField*
read_image(GNode *node)
{
    WIPImage header;
    WIPAxis zaxis;
    WIPSpaceTransform xyaxis;
    WIPIdNode idnode;
    GwyField *dfield, *dfield2;
    GwyUnit *siunitxy, *siunitz;
    gdouble xscale, yscale, zscale;
    gint power10z = 0;
    gint power10xy = 0;
    GwyRawDataType rawtype;
    gboolean mirrory = FALSE, mirrorx = FALSE;

    gwy_clear1(header);
    g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_image_tags, &header);

    if ((header.datatype > 11)
        || (header.sizex != header.xrange)
        || (header.sizey != header.yrange)
        || (header.datasize != (WIPDataSize[header.datatype] * header.sizex * header.sizey))) {
        return NULL;
    }

    /* Try to read z units */
    gwy_clear1(idnode);
    idnode.id = header.zinterpid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);
    gwy_clear1(zaxis);
    if (idnode.node && idnode.node->parent && idnode.node->parent->parent) {
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
                        read_axis_tags, &zaxis);
    }
    siunitz = gwy_unit_new_parse(zaxis.unitname, &power10z);
    g_free(zaxis.unitname);

    /* Try to read xy units and scale */
    idnode.id = header.postransformid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);
    gwy_clear1(xyaxis);
    if (idnode.node && idnode.node->parent && idnode.node->parent->parent) {
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1,
                        read_space_tr_tag, &xyaxis);
    }
    siunitxy = gwy_unit_new_parse(xyaxis.unitname, &power10xy);
    g_free(xyaxis.unitname);
    xscale = xyaxis.scale[0];
    yscale = xyaxis.scale[4];
    mirrorx = fix_scale(&xscale, "x");
    mirrory = fix_scale(&yscale, "y");

    zscale = gwy_exp10(power10z);
    if (zscale == 0.0)
        zscale = 1.0;

    /* Reading actual data */
    dfield = gwy_field_new(header.sizey, header.sizex,
                           header.sizey * gwy_exp10(power10xy) * yscale,
                           header.sizex * gwy_exp10(power10xy) * xscale,
                           TRUE);
    if (header.datatype == WIP_DATA_LIST) {
        /* Cannot read.  FIXME: What then – leave the data as uninitialised memory? */
    }
    else if ((rawtype = wip_data_type_to_raw_type(header.datatype)) != (GwyRawDataType)-1) {
        gwy_convert_raw_data(header.data, header.sizex * header.sizey, 1, rawtype, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                             gwy_field_get_data(dfield), zscale, 0.0);
        /* Data are stored in strange way in TDImage, so it is more simple to swap X and Y axes here to read them
         * correctly and rotate field in the end of procedure */
        if (header.version == 0) {
            dfield2 = gwy_field_new_rotated_90(dfield, TRUE);
            GWY_SWAP(GwyField*, dfield, dfield2);
            g_object_unref(dfield2);
        }
    }
    else {
        g_warning("Wrong datatype");
    }

    gwy_field_flip(dfield, mirrorx, mirrory);
    gwy_unit_assign(gwy_field_get_unit_z(dfield), siunitz);
    gwy_unit_assign(gwy_field_get_unit_xy(dfield), siunitxy);
    g_object_unref(siunitz);
    g_object_unref(siunitxy);

    return dfield;
}

static GwyField*
read_bitmap(GNode *node)
{
    WIPBitmap header;
    WIPSpaceTransform xyaxis;
    WIPIdNode idnode;
    GwyField *dfield;
    GwyUnit *siunitxy;
    gdouble xscale, yscale;
    gint power10xy = 0;
    gboolean mirrorx = FALSE, mirrory = FALSE;

    gwy_clear1(header);
    g_node_traverse(node, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_bitmap_tags, &header);

    /* Try to read xy units and scale */
    gwy_clear1(idnode);
    idnode.id = header.spacetransformid;
    g_node_traverse(g_node_get_root(node), G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, find_by_id, &idnode);
    if (idnode.node && node->parent->parent && idnode.node->parent->parent) {
        gwy_clear1(xyaxis);
        g_node_traverse(idnode.node->parent->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_space_tr_tag, &xyaxis);
    }
    siunitxy = gwy_unit_new_parse(xyaxis.unitname, &power10xy);
    g_free(xyaxis.unitname);
    xscale = xyaxis.scale[0];
    yscale = xyaxis.scale[4];
    mirrorx = fix_scale(&xscale, "x");
    mirrory = fix_scale(&yscale, "y");

    dfield = read_bmp(header.data, header.datasize, xscale, yscale, power10xy);
    if (!dfield) {
        /* Error: failed to read BMP data */
    }
    else {
        gwy_unit_assign(gwy_field_get_unit_xy(dfield), siunitxy);
        gwy_field_flip(dfield, mirrorx, mirrory);
    }

    g_object_unref(siunitxy);

    return dfield;
}

static gboolean
read_data(GNode *node, gpointer filedata)
{
    WIPTag *tag = node->data;
    WIPFile *filecontent = (WIPFile *)filedata;
    GString *key = g_string_new(NULL);
    GString *caption = g_string_new(NULL);
    GwyFile *file = filecontent->file;

    if (!strncmp(tag->name, "TDGraph", 7)) {
        GwyGraphModel *gmodel = read_graph(node);
        if (!gmodel) {
            GwyBrick *brick = read_graph_image(node);
            if (!brick) {
                /* some error in brick read */
            }
            else {
                gwy_file_pass_volume(file, filecontent->numbricks, brick);
                g_string_assign(caption, "Unnamed data");
                g_node_traverse(node->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_caption, caption);
                gwy_file_set_title(file, GWY_FILE_VOLUME, filecontent->numbricks, caption->str, FALSE);
                gwy_log_add_import(file, GWY_FILE_VOLUME, filecontent->numbricks, NULL, filecontent->filename);
                filecontent->numbricks++;
            }
        }
        else {
            gwy_file_pass_graph(file, filecontent->numgraph, gmodel);
            filecontent->numgraph++;
        }
    }
    else if (!strncmp(tag->name, "TDImage", 7)) {
        GwyField *image = read_image(node);
        if (!image) {
            /* some error in image read */
        }
        else {
            g_string_assign(caption, "Unnamed data");
            g_node_traverse(node->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_caption, caption);
            gwy_file_pass_image(file, filecontent->numimages, image);
            gwy_file_set_title(file, GWY_FILE_IMAGE, filecontent->numimages, caption->str, FALSE);
            gwy_log_add_import(file, GWY_FILE_IMAGE, filecontent->numimages, NULL, filecontent->filename);
            filecontent->numimages++;
        }
    }
    else if (!strncmp(tag->name, "TDBitmap", 8)) {
        GwyField *image = read_bitmap(node->parent);
        if (!image) {
            /* some error in bitmap read */
        }
        else {
            g_string_assign(caption, "Unnamed data");
            g_node_traverse(node->parent, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, read_caption, caption);
            gwy_file_pass_image(file, filecontent->numimages, image);
            gwy_file_set_title(file, GWY_FILE_IMAGE, filecontent->numimages, caption->str, FALSE);
            gwy_log_add_import(file, GWY_FILE_IMAGE, filecontent->numimages, NULL, filecontent->filename);
            filecontent->numimages++;
        }
    }

    g_string_free(caption, TRUE);
    g_string_free(key, TRUE);

    return FALSE;
}

static GwyUnit*
xtransform_data(const WIPSpectralInterpretation *xinterp,
                gdouble *data, gint res)
{
    GwyUnit *unit;
    gint i;

    if ((xinterp->unitindex == WIP_UNIT_NANOMETER) || (xinterp->unitindex == WIP_UNIT_MIKROMETER)) {
        unit = gwy_unit_new("m");
        for (i = 0; i < res; i++)
            data[i] = data[i] * 1e-9;
    }
    else if (xinterp->unitindex == WIP_UNIT_SM_1) {
        unit = gwy_unit_new("1/m");
        for (i = 0; i < res; i++)
            data[i] = 1.0/(data[i] * 1e-9);
    }
    else if (xinterp->unitindex == WIP_UNIT_RAMAN_SHIFT) {
        if (xinterp->excitation_wavelength == 0.0) {
            unit = gwy_unit_new("m");
            for (i = 0; i < res; i++)
                data[i] = data[i] * 1e-9;
        }
        else {
            unit = gwy_unit_new("1/m");
            for (i = 0; i < res; i++)
                data[i] = 1.0/(xinterp->excitation_wavelength * 1e-9) - 1.0/(data[i] * 1e-9);
        }
    }
    else if ((xinterp->unitindex == WIP_UNIT_EV) || (xinterp->unitindex == WIP_UNIT_MEV)) {
        unit = gwy_unit_new("eV");
        for (i = 0; i < res; i++)
            data[i] = H * C / (data[i] * 1e-9);
    }
    else if ((xinterp->unitindex == WIP_UNIT_EV_REL) || (xinterp->unitindex == WIP_UNIT_MEV_REL)) {
        if (xinterp->excitation_wavelength == 0.0) {
            unit = gwy_unit_new("m");
            for (i = 0; i < res; i++)
                data[i] = data[i] * 1e-9;
        }
        else {
            unit = gwy_unit_new("eV");
            for (i = 0; i < res; i++)
                data[i] = H * C / (xinterp->excitation_wavelength * 1e-9) - H * C / (data[i] * 1e-9);
        }
    }
    else {
        unit = gwy_unit_new("m");
        for (i = 0; i < res; i++)
            data[i] = data[i] * 1e-9;
    }

    return unit;
}

static gboolean
fix_scale(gdouble *scale, const gchar *name)
{
    if (*scale == 0.0 || gwy_isinf(*scale) || gwy_isnan(*scale)) {
        g_warning("Wrong %s-scale", name);
        *scale = 1.0;
        return FALSE;
    }
    if (*scale < 0.0) {
        *scale = fabs(*scale);
        return TRUE;
    }
    return FALSE;
}

static GwyRawDataType
wip_data_type_to_raw_type(WIPDataType type)
{
    static const WIPDataType wip_types[] = {
        WIP_DATA_BOOL,
        WIP_DATA_INT8, WIP_DATA_UINT8,
        WIP_DATA_INT16, WIP_DATA_UINT16,
        WIP_DATA_INT32, WIP_DATA_UINT32,
        WIP_DATA_INT64,
        WIP_DATA_FLOAT, WIP_DATA_DOUBLE, WIP_DATA_EXTENDED,
    };
    static const GwyRawDataType raw_types[] = {
        GWY_RAW_DATA_UINT8,
        GWY_RAW_DATA_SINT8, GWY_RAW_DATA_UINT8,
        GWY_RAW_DATA_SINT16, GWY_RAW_DATA_UINT16,
        GWY_RAW_DATA_SINT32, GWY_RAW_DATA_UINT32,
        GWY_RAW_DATA_SINT64,
        GWY_RAW_DATA_FLOAT, GWY_RAW_DATA_DOUBLE, GWY_RAW_DATA_EXTENDED,
    };
    guint i;

    g_assert(G_N_ELEMENTS(wip_types) == G_N_ELEMENTS(raw_types));
    for (i = 0; i < G_N_ELEMENTS(wip_types); i++) {
        if (type == wip_types[i])
            return raw_types[i];
    }
    return (GwyRawDataType)-1;
}

/* vim: set cin et columns=120 tw=118 ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
