/* STARTHEADER
 *
 * File :       dted.c
 *
 * Author :     Paul Obermeier (paul@poSoft.de)
 *
 * Date :       Tue Nov 20 21:24:26 CET 2001
 *
 * Copyright :  (C) 2001-2003 Paul Obermeier
 *
 * Description :
 *
 * A photo image handler for DTED elevation data interpreted as image files.
 *
 * The following image types are supported:
 *
 * Grayscale image: Load DTED data as grayscale image.
 *
 * List of currently supported features:
 *
 * Type   |     Read      |     Write     |
 *        | -file | -data | -file | -data |
 * ----------------------------------------
 * Gray   | Yes   | Yes   | No    | No   |
 *
 * The following format options are available:
 *
 * Read  DTED image: "dted -verbose <bool> -nchan <int> -nomap <bool>
 *                         -gamma <float> -min <float> -max <float>"
 *
 * -verbose <bool>:     If set to true, additional information about the file
 *                      format is printed to stdout. Default is false.
 * -nchan <int>:        Specify the number of channels of the generated image.
 *			Default is 1, i.e. generated a grayscale image.
 * -gamma <float>:      Specify a gamma correction to be applied when mapping
 *			the input data to 8-bit image values.
 *                      Default is 1.0.
 * -nomap <bool>:       If set to true, no mapping of input values is done.
 * 			Use this option, if your image already contains RGB
 * 			values in the range of 0 ..255.
 *                      Default is false.
 * -min <short>:        Specify the minimum pixel value to be used for mapping
 *			the input data to 8-bit image values.
 *                      Default is the minimum value found in the image data.
 * -max <short>:        Specify the maximum pixel value to be used for mapping
 *			the input data to 8-bit image values.
 *                      Default is the maximum value found in the image data.
 *
 * Notes:
 * 			Currently only reading DTED files as grayscale images
 *			is implemented. Color mapped images and writing will be
 *			implemented when needed.
 *			Syntax checking of DTED files is rudimentary, too.
 *			Only file reading tested right now.
 *
 * ENDHEADER
 *
 * $Id$
 *
 */

#include <stdlib.h>
#include <math.h>

/*
 * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
 */

#include "init.c"


/* #define DEBUG_LOCAL */

#define strIntel    "Intel"
#define strMotorola "Motorola"

#define MAXCHANS  4

/* Some general defines and typedefs. */
#define TRUE  1
#define FALSE 0
#define MIN(a,b) ((a)<(b)? (a): (b))
#define MAX(a,b) ((a)>(b)? (a): (b))

typedef unsigned char Boln;	/* Boolean value: TRUE or FALSE */
typedef unsigned char UByte;	/* Unsigned  8 bit integer */
typedef char  Byte;		/* Signed    8 bit integer */
typedef unsigned short UShort;	/* Unsigned 16 bit integer */
typedef short Short;		/* Signed   16 bit integer */
typedef int UInt;		/* Unsigned 32 bit integer */
typedef int Int;		/* Signed   32 bit integer */
typedef float Float;		/* IEEE     32 bit floating point */
typedef double Double;		/* IEEE     64 bit floating point */

#define MAX_SHORT   32767
#define MIN_SHORT  -32768

#define ELEV_UNDEFINED -32000 /* All elevations smaller than this value are
			         considered undefined, and are set to the
				 minimum value. */

/* DTED file header structures */

typedef struct {
    Byte uhl_tag[3];        /* 'UHL' sentinel tag */
    Byte reserved1[1];
    Byte origin_long[8];    /* Longitude of origin */
    Byte origin_lat[8];     /* Latitude of origin */
    Byte ew_interval[4];    /* East-west data interval (tenths second) */
    Byte ns_interval[4];    /* North-south data interval (tenths second) */
    Byte accuracy[4];       /* Absolute vertical accuracy (meters) */
    Byte security[3];
    Byte reserved2[45];
} UHL_STRUCT;

typedef struct {
    Byte dsi_tag[3];              /* 'DSI' sentinel tag */
    Byte security_class[1];       /* Security classification */
    Byte security_mark[2];        /* Security control & release mark */
    Byte security_desc[27];       /* Security handling description */
    Byte reserved1[26];
    Byte level[5];                /* DMA series designator for level */
    Byte ref_num[15];             /* Reference number */
    Byte reserved2[8];
    Byte edition[2];              /* Data edition */
    Byte merge_version[1];        /* Match/merge version */
    Byte maintenance_date[4];     /* Maintenance date (YYMM) */
    Byte merge_date[4];           /* Match/Merge date (YYMM) */
    Byte maintenance_desc[4];     /* Maintenance description */
    Byte producer[8];             /* Producer */
    Byte reserved3[16];
    Byte product_num[9];          /* Product specification stock number */
    Byte product_change[2];       /* Product specification change number */
    Byte product_date[4];         /* Product specification date (YYMM) */
    Byte vertical_datum[3];       /* Vertical datum */
    Byte horizontal_datum[5];     /* Horizontal datum */
    Byte collection_sys[10];      /* Digitizing collection system */
    Byte compilation_date[4];     /* Compilation date (YYMM) */
    Byte reserved4[22];
    Byte origin_lat[9];           /* Latitude of data origin */
    Byte origin_long[10];         /* Longitude of data origin */
    Byte sw_corner_lat[7];        /* Latitude of SW corner */
    Byte sw_corner_long[8];       /* Longitude of SW corner */
    Byte nw_corner_lat[7];        /* Latitude of NW corner */
    Byte nw_corner_long[8];       /* Longitude of NW corner */
    Byte ne_corner_lat[7];        /* Latitude of NE corner */
    Byte ne_corner_long[8];       /* Longitude of NE corner */
    Byte se_corner_lat[7];        /* Latitude of SE corner */
    Byte se_corner_long[8];       /* Longitude of SE corner */
    Byte orientation[9];          /* Orientation angle */
    Byte ns_spacing[4];           /* North-south data spacing (tenths sec) */
    Byte ew_spacing[4];           /* East-west data spacing (tenths sec) */
    Byte rows[4];                 /* Number of data rows */
    Byte cols[4];                 /* Number of data cols */
    Byte cell_coverage[2];        /* Partial cell indicator */
    Byte reserved5[357];
} DSI_STRUCT;

typedef struct {
    Byte abs_horiz_acc[4];	/* Absolute horizontal accuracy (meters) */
    Byte abs_vert_acc[4];	/* Absolute vertical accuracy (meters) */
    Byte rel_horiz_acc[4];	/* Relative horizontal accuracy (meters) */
    Byte rel_vert_acc[4];	/* Relative vertical accuracy (meters) */
} ACCURACY_STRUCT;

typedef struct {
    Byte latitude[9];		/* Latitude */
    Byte longitude[10];		/* Longitude */
} COORD_STRUCT;

typedef struct {
    ACCURACY_STRUCT acc;	/* Accuracy of subregion */
    Byte no_coords[2];		/* Number of coordinates (03-14) */
    COORD_STRUCT coords[14];	/* Outline of subregion */
} SUBREGION_STRUCT;

typedef struct {
    Byte acc_tag[3];		     /* 'ACC' sentinel tag */
    ACCURACY_STRUCT global_acc;      /* Accuracy of product */
    Byte reserved1[36];
    Byte no_acc_subregions[2];	     /* Number of accuracy subregions
                                        (00 = no, 02-09) */
    SUBREGION_STRUCT subregions[9];  /* Accuracy subregions */
    Byte reserved2[87];
} ACC_STRUCT;

typedef struct {
    UHL_STRUCT uhl;
    DSI_STRUCT dsi;
    ACC_STRUCT acc;
} DTEDHEADER;

/* DTED file format options structure for use with ParseFormatOpts */
typedef struct {
    Int   nchan;
    Short minVal;
    Short maxVal;
    Float gamma;
    Boln  nomap;
    Boln  verbose;
} FMTOPT;

/* Structure to hold information about the DTED file being processed. */
typedef struct {
    DTEDHEADER th;
    UByte *pixbuf;
    Short *rawbuf;
} DTEDFILE;

#define MINGAMMA 0.01
#define MAXGAMMA 100.0
#define GTABSIZE 257

/* Given a pixel value in Float format, "fin", and a gamma-correction
lookup table, "ftab", macro "gcorrectFloat" returns the gamma-corrected pixel
value in "fout". */

#define gcorrectFloat(fin,ftab,fout)                                    \
    {                                                                   \
        Int     gc_i;                                                   \
        Float   gc_t;                                                   \
        gc_t = (fin) * (GTABSIZE - 2);                                  \
        gc_i = gc_t;                                                    \
        gc_t -= gc_i;                                                   \
        (fout) = (ftab)[gc_i] * (1.0-gc_t) + (ftab)[gc_i+1] * gc_t;     \
    }

static Boln gtableFloat (Float gamma, Float table[])
{
    Int i;

    if (gamma < MINGAMMA || gamma > MAXGAMMA) {
        printf ("Invalid gamma value %f\n", gamma);
        return FALSE;
    }
    for (i = 0; i < GTABSIZE - 1; ++i) {
        table[i] = pow ((Float) i / (Float) (GTABSIZE - 2), 1.0 / gamma);
#ifdef DEBUG_LOCAL
	    printf ("gammatable[%d] = %f\n", i, table[i]);
#endif
    }
    table[GTABSIZE - 1] = 1.0;
#ifdef DEBUG_LOCAL
	printf ("gammatable[%d] = %f\n", GTABSIZE-1, table[GTABSIZE-1]);
#endif
    return TRUE;
}

static void gammaShortUByte (Int n, const Short s_in[],
                             const Float gtable[], UByte ub_out[])
{
    const Short *ssrc, *sstop;
    Float       ftmp;
    Int         itmp;
    UByte       *ubdest;

    ssrc = s_in;
    sstop = s_in + n;
    ubdest = ub_out;

    /* Handle a gamma value of 1.0 (gtable == NULL) as a special case.
       Quite nice speed improvement for the maybe most used case. */
    if (gtable) {
	while (ssrc < sstop) {
	    /* Map short values from the range [MIN_SHORT .. MAX_SHORT] to
	       the range [0.0 .. 1.0], do gamma correction and then map into
	       the displayable range [0 .. 255]. */
	    ftmp = (Float)(*ssrc * 1.0 / 65535.0  + 0.5);
	    gcorrectFloat (ftmp, gtable, ftmp);
	    itmp = (Int)(ftmp * 255.0 + 0.5);
	    *ubdest = MAX (0, MIN (itmp, 255));
#ifdef DEBUG_LOCAL
		printf ("Gamma %d --> %f --> %d --> %d\n",
		        *ssrc, ftmp, itmp, *ubdest);
#endif
	    ++ubdest;
	    ++ssrc;
	}
    } else {
	while (ssrc < sstop) {
	    /* Map short values from the range [MIN_SHORT .. MAX_SHORT] to
	       the displayable range [0 .. 255]. */
	    itmp = (Int)(*ssrc * 255.0 / 65535.0  + 128);
	    *ubdest = MAX (0, MIN (itmp, 255));
#ifdef DEBUG_LOCAL
		printf ("NoGamma %d --> %d --> %d\n", *ssrc, itmp, *ubdest);
#endif
	    ++ubdest;
	    ++ssrc;
	}
    }
    return;
}

/* This function determines at runtime, whether we are on an Intel system. */

static int isIntel (void)
{
    unsigned long val = 513;
    /* On Intel (little-endian) systems this value is equal to "\01\02\00\00".
       On big-endian systems this value equals "\00\00\02\01" */
    return memcmp(&val, "\01\02", 2) == 0;
}

static void dtedClose (DTEDFILE *tf)
{
    if (tf->pixbuf) ckfree ((char *)tf->pixbuf);
    if (tf->rawbuf) ckfree ((char *)tf->rawbuf);
    return;
}

/* Read 1 byte, representing an unsigned integer number. */

#if 0 /* unused */
static Boln readUByte (tkimg_MFile *handle, UByte *b)
{
    char buf[1];
    if (1 != tkimg_Read (handle, buf, 1))
        return FALSE;
    *b = buf[0];
    return TRUE;
}
#endif  /* unused */

/* Read 2 bytes, representing a signed 16 bit integer in the form
   <LowByte, HighByte>, from a file and convert them into the current
   machine's format. */

static Boln readShort (tkimg_MFile *handle, Short *s)
{
    char buf[2];
    if (2 != tkimg_Read (handle, buf, 2))
        return FALSE;
    *s = (buf[0] & 0xFF) | (buf[1] << 8);
    return TRUE;
}

/* Read 4 bytes, representing a signed 32 bit integer in the form
   <LowByte, HighByte>, from a file and convert them into the current
   machine's format. */

static Boln readInt (tkimg_MFile *handle, Int *i)
{
    char buf[4];
    if (4 != tkimg_Read (handle, buf, 4))
        return FALSE;
    *i = ((((Int)buf[0] & 0x000000FFU) << 24) | \
          (((Int)buf[1] & 0x0000FF00U) <<  8) | \
          (((Int)buf[2] & 0x00FF0000U) >>  8) | \
          (((Int)buf[3] & 0x0000FF00U) >> 24));
    return TRUE;
}

/* Write a byte, representing an unsigned integer to a file. */

#if 0 /* unused */
static Boln writeUByte (tkimg_MFile *handle, UByte b)
{
    UByte buf[1];
    buf[0] = b;
    if (1 != tkimg_Write (handle, (const char *)buf, 1))
        return FALSE;
    return TRUE;
}
#endif /* unused */

/* Write a byte, representing a signed integer to a file. */

#if 0 /* unused */
static Boln writeByte (tkimg_MFile *handle, Byte b)
{
    Byte buf[1];
    buf[0] = b;
    if (1 != tkimg_Write (handle, buf, 1))
        return FALSE;
    return TRUE;
}
#endif /* unused */

/* Convert a signed 16 bit integer number into the format
   <LowByte, HighByte> (an array of 2 bytes) and write the array to a file. */

#if 0 /* unused */
static Boln writeShort (tkimg_MFile *handle, Short s)
{
    Byte buf[2];
    buf[0] = s;
    buf[1] = s >> 8;
    if (2 != tkimg_Write (handle, buf, 2))
        return FALSE;
    return TRUE;
}
#endif /* unused */

/* Convert a unsigned 16 bit integer number into the format
   <LowByte, HighByte> (an array of 2 bytes) and write the array to a file. */

#if 0 /* unused */
static Boln writeUShort (tkimg_MFile *handle, UShort s)
{
    Byte buf[2];
    buf[0] = s;
    buf[1] = s >> 8;
    if (2 != tkimg_Write (handle, buf, 2))
        return FALSE;
    return TRUE;
}
#endif /* unused */

#define OUT Tcl_WriteChars (outChan, str, -1)
static void printImgInfo (DTEDHEADER *th, FMTOPT *opts,
                          const char *filename, const char *msg)
{
    Tcl_Channel outChan;
    char str[256];

    outChan = Tcl_GetStdChannel (TCL_STDOUT);
    if (!outChan) {
        return;
    }
    sprintf (str, "%s\n", msg);                                              OUT;
    sprintf (str, "\tLongitude of origin  : %.8s\n", th->uhl.origin_long);   OUT;
    sprintf (str, "\tLatitude of origin   : %.8s\n", th->uhl.origin_lat);    OUT;
    sprintf (str, "\tEast-West interval   : %.4s\n", th->uhl.ew_interval);   OUT;
    sprintf (str, "\tNorth-South interval : %.4s\n", th->uhl.ns_interval);   OUT;
    sprintf (str, "\tVertical accuracy    : %.4s\n", th->uhl.accuracy);      OUT;
    sprintf (str, "\tSecurity Code        : %.3s\n", th->uhl.security);      OUT;
    sprintf (str, "\tDTED level           : %.5s\n", th->dsi.level);         OUT;
    sprintf (str, "\tNumber of rows       : %.4s\n", th->dsi.rows);          OUT;
    sprintf (str, "\tNumber of columns    : %.4s\n", th->dsi.cols);          OUT;
    sprintf (str, "\tCell coverage        : %.2s\n", th->dsi.cell_coverage); OUT;
    sprintf (str, "\tNo. of channels      : %d\n", opts->nchan);             OUT;
    sprintf (str, "\tGamma correction     : %f\n", opts->gamma);             OUT;
    sprintf (str, "\tMinimum map value    : %d\n", opts->minVal);            OUT;
    sprintf (str, "\tMaximum map value    : %d\n", opts->maxVal);            OUT;
    sprintf (str, "\tHost byte order      : %s\n", isIntel ()?
                                                   strIntel: strMotorola);   OUT;
    Tcl_Flush (outChan);
}
#undef OUT
static Boln readHeader (tkimg_MFile *handle, DTEDHEADER *th)
{
    if (sizeof (DTEDHEADER) != tkimg_Read (handle, (char *)th, sizeof(DTEDHEADER))) {
        return FALSE;
    }
    if (strncmp ((char *)th->uhl.uhl_tag, "UHL", 3) != 0) {
	return FALSE;
    }

    /* OPA: More tests to follow. */
    return TRUE;
}

#if 0 /* unused */
static Boln writeHeader (tkimg_MFile *handle, DTEDHEADER *th)
{
    return TRUE;
}
#endif /* unused */

#if 0 /* unused */
static void initHeader (DTEDHEADER *th)
{
    th->uhl.uhl_tag[0] = 'U';
    th->uhl.uhl_tag[1] = 'H';
    th->uhl.uhl_tag[2] = 'L';
    /* OPA: More to follow for DTED writing */
    return;
}
#endif /* unused */

static Boln readDtedColumn (tkimg_MFile *handle, Short *pixels, Int nRows,
                            Int nCols, Int curCol, char *buf, Boln hostIsIntel)
{
    Int   i, nBytes;
    Short *mPtr;
    char  *bufPtr = buf;
    Short meridian, parallel;
    Int   block_count;
    UByte *cp;
    Int  checksum, checksum1 = 0;

    /* Read data column header. */
    if (!readInt   (handle, &block_count) ||
        !readShort (handle, &meridian) ||
        !readShort (handle, &parallel)) {
        printf ("Error reading column header\n");
	return FALSE;
    }

    /* Calculate checksum, part 1 */
    cp = (UByte *) &block_count;
    checksum1 += cp[0] + cp[1] + cp[2] + cp[3];
    cp = (UByte *) &meridian;
    checksum1 += cp[0] + cp[1];
    cp = (UByte *) &parallel;
    checksum1 += cp[0] + cp[1];

    if (hostIsIntel) {
	block_count = block_count & 0x00ffffff;
    } else {
	block_count = (block_count & 0xffffff00) >> 8;
    }

    /* Read the elevation data into the supplied column buffer "buf". */
    nBytes = sizeof (Short) * nRows;
    if (nBytes != tkimg_Read (handle, buf, nBytes)) {
        printf ("Error reading elevation data\n");
        return FALSE;
    }

    /* Copy (and swap bytes, if needed) from the column buffer into the
       pixel array (shorts) . */
    if (hostIsIntel) {
        for (i=0; i<nRows; i++) {
	    mPtr = pixels + (i * nCols) + curCol;
	    ((char *)mPtr)[0] = bufPtr[1];
            ((char *)mPtr)[1] = bufPtr[0];
            bufPtr += sizeof (Short);
        }
    } else {
        for (i=0; i<nRows; i++) {
	    mPtr = pixels + (i * nCols) + curCol;
	    ((char *)mPtr)[0] = bufPtr[0];
            ((char *)mPtr)[1] = bufPtr[1];
            bufPtr += sizeof (Short);
        }
    }

    /* Read the checksum */
    if (!readInt (handle, &checksum)) {
        printf ("Error reading checksum\n");
	return FALSE;
    }

    /* Calculate checksum, part 2. OPA TODO Incorrect  */
    cp = (UByte *) pixels;
    for (i=0; i<nRows*2; i++, cp++) {
	checksum1 += *cp;
    }

    if (checksum != checksum1) {
	/* printf ("DTED Checksum Error (%d vs. %d).\n", checksum, checksum1); */
        /* return FALSE; */
    }
    return TRUE;
}

static Boln readDtedFile (tkimg_MFile *handle, Short *buf, Int width, Int height,
                          Int nchan, Boln hostIsIntel, Boln verbose,
                          Short minVals[], Short maxVals[])
{
    Int x, y, c;
    Short *bufPtr = buf;
    char  *colBuf;

#ifdef DEBUG_LOCAL
	printf ("readDtedFile: Width=%d Height=%d nchan=%d hostIsIntel=%s\n",
                 width, height, nchan, hostIsIntel? "yes": "no");
#endif
    for (c=0; c<nchan; c++) {
	minVals[c] =  MAX_SHORT;
	maxVals[c] =  MIN_SHORT;
    }
    colBuf = ckalloc (sizeof (Short) * nchan * height);

    /* Read the elevation data column by column. */
    for (x=0; x<width; x++) {
	if (!readDtedColumn (handle, buf, height, width,
	                     x, colBuf, hostIsIntel)) {
	    return FALSE;
	}
    }

    /* Loop through the elevation data and find minimum and maximum values.
       Ignore elevation values equal to -32767, because these indicate, no
       elevation data available. See also function remapShortValues.
       Note: We extend the range of undefined elevations to all values
       smaller than ELEV_UNDEFINED, because of DTED files not fully
       compliant to the specification. */
    bufPtr = buf;
    for (x=0; x<width; x++) {
	for (y=0; y<height; y++) {
	    for (c=0; c<nchan; c++) {
		if ( *bufPtr >= ELEV_UNDEFINED ) {
		    if (*bufPtr > maxVals[c]) maxVals[c] = *bufPtr;
		    if (*bufPtr < minVals[c]) minVals[c] = *bufPtr;
		}
		bufPtr++;
	    }
	}
    }
    if (verbose) {
	printf ("\tMinimum pixel values :");
	for (c=0; c<nchan; c++) {
	    printf (" %d", minVals[c]);
	}
	printf ("\n");
	printf ("\tMaximum pixel values :");
	for (c=0; c<nchan; c++) {
	    printf (" %d", maxVals[c]);
	}
	printf ("\n");
	fflush (stdout);
    }
    ckfree (colBuf);
    return TRUE;
}

/* Map the original short values into the range [MIN_SHORT .. MAX_SHORT].
   We must take care of values equal to -32767, which indicate that no
   elevation data is available. So we map this value to the minimum value.
   See also function readDtedFile. */

static Boln remapShortValues (Short *buf, Int width, Int height, Int nchan,
                              Short minVals[], Short maxVals[])
{
    Int   x, y, c;
    Int   tmpInt;
    Short tmpShort;
    Short *bufPtr = buf;
    Float m[MAXCHANS], t[MAXCHANS];

    for (c=0; c<nchan; c++) {
	m[c] = (Float)(MAX_SHORT - MIN_SHORT) /
	       (Float)(maxVals[c] - minVals[c]);
	t[c] = MIN_SHORT - m[c] * minVals[c];
    }
    for (y=0; y<height; y++) {
	for (x=0; x<width; x++) {
	    for (c=0; c<nchan; c++) {
		tmpShort = (*bufPtr >= ELEV_UNDEFINED? *bufPtr: minVals[c]);
		tmpInt = (Int)(tmpShort * m[c] + t[c] + 0.5);
#ifdef DEBUG_LOCAL
		    printf ("Remap %d --> %d --> %d --> ",
		             *bufPtr, tmpShort, tmpInt);
#endif
		if (tmpInt < MIN_SHORT) {
		    *bufPtr = MIN_SHORT;
		} else if (tmpInt > MAX_SHORT) {
		    *bufPtr = MAX_SHORT;
		} else {
		    *bufPtr = tmpInt;
		}
#ifdef DEBUG_LOCAL
		    printf ("%d (%f %f)\n", *bufPtr, m[c], t[c]);
#endif
	        bufPtr++;
	    }
	}
    }
    return TRUE;
}

/*
 * Here is the start of the standard functions needed for every image format.
 */

/*
 * Prototypes for local procedures defined in this file:
 */

static int ParseFormatOpts(Tcl_Interp *interp, Tcl_Obj *format,
	FMTOPT *opts);
static int CommonMatch(Tcl_Interp *interp, tkimg_MFile *handle,
	Tcl_Obj *format, int *widthPtr, int *heightPtr,
	DTEDHEADER *dtedHeaderPtr);
static int CommonRead(Tcl_Interp *interp, tkimg_MFile *handle,
	const char *filename, Tcl_Obj *format,
	Tk_PhotoHandle imageHandle, int destX, int destY,
	int width, int height, int srcX, int srcY);
static int CommonWrite(Tcl_Interp *interp,
	const char *filename, Tcl_Obj *format,
	tkimg_MFile *handle, Tk_PhotoImageBlock *blockPtr);

static int ParseFormatOpts (interp, format, opts)
    Tcl_Interp *interp;
    Tcl_Obj *format;
    FMTOPT *opts;
{
    static const char *const dtedOptions[] = {
         "-verbose", "-nchan", "-min", "-max", "-gamma", "-nomap", NULL
    };
    int objc, length, i, index;
    Tcl_Obj **objv;
    const char *nchanStr, *verboseStr, *minStr, *maxStr, *gammaStr, *nomapStr;

    /* Initialize format options with default values. */
    verboseStr   = "0";
    nchanStr     = "1";
    minStr       = "0.0";
    maxStr       = "0.0";
    gammaStr     = "1.0";
    nomapStr     = "0";

    if (tkimg_ListObjGetElements (interp, format, &objc, &objv) != TCL_OK)
	return TCL_ERROR;
    if (objc) {
	for (i=1; i<objc; i++) {
	    if (Tcl_GetIndexFromObj (interp, objv[i], (CONST84 char *CONST86 *)dtedOptions,
		    "format option", 0, &index) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (++i >= objc) {
		Tcl_AppendResult (interp, "No value for option \"",
			Tcl_GetStringFromObj (objv[--i], (int *) NULL),
			"\"", (char *) NULL);
		return TCL_ERROR;
	    }
	    switch(index) {
		case 0:
		    verboseStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
		    break;
		case 1:
		    nchanStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
		    break;
		case 2:
		    minStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
		    break;
		case 3:
		    maxStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
		    break;
		case 4:
		    gammaStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
		    break;
		case 5:
		    nomapStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
		    break;
	    }
	}
    }

    /* OPA TODO: Check for valid integer and float strings. */
    opts->nchan  = atoi (nchanStr);
    opts->minVal = atoi (minStr);
    opts->maxVal = atoi (maxStr);
    opts->gamma  = atof (gammaStr);

    length = strlen (verboseStr);
    if (!strncmp (verboseStr, "1", length) || \
	!strncmp (verboseStr, "true", length) || \
	!strncmp (verboseStr, "on", length)) {
	opts->verbose = 1;
    } else if (!strncmp (verboseStr, "0", length) || \
	!strncmp (verboseStr, "false", length) || \
	!strncmp (verboseStr, "off", length)) {
	opts->verbose = 0;
    } else {
	Tcl_AppendResult (interp, "invalid verbose mode \"", verboseStr,
			  "\": should be 1 or 0, on or off, true or false",
			  (char *) NULL);
	return TCL_ERROR;
    }

    length = strlen (nomapStr);
    if (!strncmp (nomapStr, "1", length) || \
	!strncmp (nomapStr, "true", length) || \
	!strncmp (nomapStr, "on", length)) {
	opts->nomap = 1;
    } else if (!strncmp (nomapStr, "0", length) || \
	!strncmp (nomapStr, "false", length) || \
	!strncmp (nomapStr, "off", length)) {
	opts->nomap = 0;
    } else {
	Tcl_AppendResult (interp, "invalid nomap mode \"", nomapStr,
			  "\": should be 1 or 0, on or off, true or false",
			  (char *) NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

static int ChnMatch(
    Tcl_Channel chan,
    const char *filename,
    Tcl_Obj *format,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp
) {
    tkimg_MFile handle;

    handle.data = (char *) chan;
    handle.state = IMG_CHAN;

    return CommonMatch(interp, &handle, format, widthPtr, heightPtr, NULL);
}

static int ObjMatch(
    Tcl_Obj *data,
    Tcl_Obj *format,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp
) {
    tkimg_MFile handle;

    tkimg_ReadInit(data, 'U', &handle);
    return CommonMatch (interp, &handle, format, widthPtr, heightPtr, NULL);
}

static int CommonMatch (interp, handle, format,
                        widthPtr, heightPtr, dtedHeaderPtr)
    Tcl_Interp *interp;
    tkimg_MFile *handle;
    Tcl_Obj *format;
    int *widthPtr;
    int *heightPtr;
    DTEDHEADER *dtedHeaderPtr;
{
    DTEDHEADER th;
    FMTOPT opts;
    Int nRows, nCols;

    if (ParseFormatOpts (interp, format, &opts) != TCL_OK) {
	return TCL_ERROR;
    }

    if (!readHeader (handle, &th)) {
	return 0;
    }
    sscanf (th.dsi.rows, "%4d", &nRows);
    sscanf (th.dsi.cols, "%4d", &nCols);
    *widthPtr  = nCols;
    *heightPtr = nRows;
    if (dtedHeaderPtr)
	*dtedHeaderPtr = th;
    return 1;
}

static int ChnRead (interp, chan, filename, format, imageHandle,
                    destX, destY, width, height, srcX, srcY)
    Tcl_Interp *interp;         /* Interpreter to use for reporting errors. */
    Tcl_Channel chan;           /* The image channel, open for reading. */
    const char *filename;       /* The name of the image file. */
    Tcl_Obj *format;            /* User-specified format object, or NULL. */
    Tk_PhotoHandle imageHandle; /* The photo image to write into. */
    int destX, destY;           /* Coordinates of top-left pixel in
				 * photo image to be written to. */
    int width, height;          /* Dimensions of block of photo image to
				 * be written to. */
    int srcX, srcY;             /* Coordinates of top-left pixel to be used
			         * in image being read. */
{
    tkimg_MFile handle;

    handle.data = (char *) chan;
    handle.state = IMG_CHAN;

    return CommonRead (interp, &handle, filename, format, imageHandle,
                       destX, destY, width, height, srcX, srcY);
}

static int ObjRead (interp, data, format, imageHandle,
	            destX, destY, width, height, srcX, srcY)
    Tcl_Interp *interp;
    Tcl_Obj *data;
    Tcl_Obj *format;
    Tk_PhotoHandle imageHandle;
    int destX, destY;
    int width, height;
    int srcX, srcY;
{
    tkimg_MFile handle;

    tkimg_ReadInit (data, 'U', &handle);
    return CommonRead (interp, &handle, "InlineData", format, imageHandle,
                       destX, destY, width, height, srcX, srcY);
}

static int CommonRead (interp, handle, filename, format, imageHandle,
                       destX, destY, width, height, srcX, srcY)
    Tcl_Interp *interp;         /* Interpreter to use for reporting errors. */
    tkimg_MFile *handle;              /* The image file, open for reading. */
    const char *filename;       /* The name of the image file. */
    Tcl_Obj *format;            /* User-specified format object, or NULL. */
    Tk_PhotoHandle imageHandle; /* The photo image to write into. */
    int destX, destY;           /* Coordinates of top-left pixel in
				 * photo image to be written to. */
    int width, height;          /* Dimensions of block of photo image to
			         * be written to. */
    int srcX, srcY;             /* Coordinates of top-left pixel to be used
			         * in image being read. */
{
	Tk_PhotoImageBlock block;
    Int y, c;
    Int fileWidth, fileHeight;
    Short minVals[MAXCHANS], maxVals[MAXCHANS];
    int stopY, outY, outWidth, outHeight;
    DTEDFILE tf;
    FMTOPT   opts;
    Boln hostIsIntel;
    Int matte = 0;
    UByte *pixbufPtr;
    Short *rawbufPtr;
    Float gtable[GTABSIZE];

    memset (&tf, 0, sizeof (DTEDFILE));
    CommonMatch (interp, handle, format, &fileWidth, &fileHeight, &tf.th);

    if (ParseFormatOpts (interp, format, &opts) != TCL_OK) {
	return TCL_ERROR;
    }

    gtableFloat (opts.gamma, gtable);

    if (opts.verbose)
	printImgInfo (&tf.th, &opts, filename, "Reading image:");

    if ((srcX + width) > fileWidth) {
	outWidth = fileWidth - srcX;
    } else {
	outWidth = width;
    }
    if ((srcY + height) > fileHeight) {
	outHeight = fileHeight - srcY;
    } else {
	outHeight = height;
    }
    if ((outWidth <= 0) || (outHeight <= 0)
	|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
	return TCL_OK;
    }

    hostIsIntel = isIntel ();

    tf.rawbuf = (Short *)ckalloc (fileWidth * fileHeight *
                                  opts.nchan * sizeof (Short));
    readDtedFile (handle, tf.rawbuf, fileWidth, fileHeight, opts.nchan,
                  hostIsIntel, opts.verbose, minVals, maxVals);
    if (opts.nomap) {
	for (c=0; c<opts.nchan; c++) {
	    minVals[c] = 0;
	    maxVals[c] = 255;
	}
    } else if (opts.minVal != 0 || opts.maxVal != 0) {
	for (c=0; c<opts.nchan; c++) {
	    minVals[c] = opts.minVal;
	    maxVals[c] = opts.maxVal;
	}
    }
    remapShortValues (tf.rawbuf, fileWidth, fileHeight, opts.nchan,
                      minVals, maxVals);

    if (tkimg_PhotoExpand(interp, imageHandle, destX + outWidth, destY + outHeight) == TCL_ERROR) {
        dtedClose(&tf);
    	return TCL_ERROR;
    }

    tf.pixbuf = (UByte *) ckalloc (fileWidth * opts.nchan);

    block.pixelSize = opts.nchan;
    block.pitch = fileWidth * opts.nchan;
    block.width = outWidth;
    block.height = 1;
    block.offset[0] = 0;
    block.offset[1] = (opts.nchan > 1? 1: 0);
    block.offset[2] = (opts.nchan > 1? 2: 0);
    block.offset[3] = (opts.nchan == 4 && matte? 3: 0);
    block.pixelPtr = tf.pixbuf + srcX * opts.nchan;

    stopY = srcY + outHeight;
    outY = destY;

    for (y=0; y<stopY; y++) {
	pixbufPtr = tf.pixbuf;
	rawbufPtr = tf.rawbuf + (fileHeight - 1 - y) * fileWidth * opts.nchan;
	gammaShortUByte (fileWidth * opts.nchan, rawbufPtr,
                         opts.gamma != 1.0? gtable: NULL, pixbufPtr);
	rawbufPtr += fileWidth * opts.nchan;
	if (y >= srcY) {
	    if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY,
                width, 1, TK_PHOTO_COMPOSITE_OVERLAY) == TCL_ERROR) {
                dtedClose(&tf);
                return TCL_ERROR;
        }
	    outY++;
	}
    }
    dtedClose(&tf);
    return TCL_OK;
}

static int ChnWrite (interp, filename, format, blockPtr)
    Tcl_Interp *interp;
    const char *filename;
    Tcl_Obj *format;
    Tk_PhotoImageBlock *blockPtr;
{
    Tcl_Channel chan;
    tkimg_MFile handle;
    int result;

    chan = tkimg_OpenFileChannel (interp, filename, 0644);
    if (!chan) {
	return TCL_ERROR;
    }

    handle.data = (char *) chan;
    handle.state = IMG_CHAN;

    result = CommonWrite (interp, filename, format, &handle, blockPtr);
    if (Tcl_Close(interp, chan) == TCL_ERROR) {
	return TCL_ERROR;
    }
    return result;
}

static int StringWrite(
    Tcl_Interp *interp,
    Tcl_Obj *format,
    Tk_PhotoImageBlock *blockPtr
) {
    tkimg_MFile handle;
    int result;
    Tcl_DString data;

    Tcl_DStringInit(&data);
    tkimg_WriteInit (&data, &handle);
    result = CommonWrite (interp, "InlineData", format, &handle, blockPtr);
    tkimg_Putc(IMG_DONE, &handle);

    if (result == TCL_OK) {
	Tcl_DStringResult(interp, &data);
    } else {
	Tcl_DStringFree(&data);
    }
    return result;
}

static int CommonWrite (interp, filename, format, handle, blockPtr)
    Tcl_Interp *interp;
    const char *filename;
    Tcl_Obj *format;
    tkimg_MFile *handle;
    Tk_PhotoImageBlock *blockPtr;
{
    return TCL_OK;
}
