/***********************************************************************
*   Author: Philip Thompson (phils@athena.mit.edu)
*   - convert from RADIANCE image file format to Xim and vice-versa.
*  Copyright (c) 1990, Massachusetts Institute of Technology
*   			 Philip R. Thompson (phils@athena.mit.edu)
*                Computer Resource Laboratory (CRL)
*                Dept. of Architecture and Planning
*                M.I.T., Rm 9-526
*                Cambridge, MA  02139
*   This  software and its documentation may be used, copied, modified,
*   and distributed for any purpose without fee, provided:
*       --  The above copyright notice appears in all copies.
*       --  This disclaimer appears in all source code copies.
*       --  The names of M.I.T. and the CRL are not used in advertising
*           or publicity pertaining to distribution of the software
*           without prior specific written permission from me or CRL.
*   I provide this software freely as a public service.  It is NOT
*   public domain nor a commercial product, and therefore is not subject
*   to an an implied warranty of merchantability or fitness for a
*   particular purpose.  I provide it as is, without warranty.
*   This software is furnished  only on the basis that any party who
*   receives it indemnifies and holds harmless the parties who furnish
*   it against any claims, demands, or liabilities connected with using
*   it, furnishing it to others, or providing it to a third party.
***********************************************************************/
#if (!defined(lint) && !defined(SABER))
static char  radiance_rcsid[] =
    "$Header: /mit/phils/X/lib/RCS/radiance_io.c,v 1.2 90/09/10 14:37:21 phils Exp Locker: phils $";
#endif

#include <stdio.h>
#include <math.h>
#include <pwd.h>
#include <sys/types.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#include <sys/time.h>
#endif

/* XimHeader.h */

/*
*  Author: Philip R. Thompson
*  Address:  phils@athena.mit.edu, 9-526 
*  $Header: /mit/phils/X/RCS/XimHeader.h,v 1.11 90/08/09 20:48:33 phils Exp Locker: phils $
*  $Date: 90/08/09 20:48:33 $
*  $Source: /mit/phils/X/RCS/XimHeader.h,v $
*  Copyright (c) 1989, 1990 Massachusetts Institute of Technology
*                1988, Philip R. Thompson (phils@athena.mit.edu)
*                Computer Resource Laboratory (CRL)
*                Dept. of Architecture and Planning
*                M.I.T., Rm 9-526
*                Cambridge, MA  02139
*   This  software and its documentation may be used, copied, modified,
*   and distributed for any purpose without fee, provided:
*       --  The above copyright notice appears in all copies.
*       --  This disclaimer appears in all source code copies.
*       --  The names of M.I.T. and the CRL are not used in advertising
*           or publicity pertaining to distribution of the software
*           without prior specific written permission from me or CRL.
*   I provide this software freely as a public service.  It is NOT
*   public domain nor a commercial product, and therefore is not subject
*   to an an implied warranty of merchantability or fitness for a
*   particular purpose.  I provide it as is, without warranty.
*
*   This software is furnished  only on the basis that any party who
*   receives it indemnifies and holds harmless the parties who furnish
*   it against any claims, demands, or liabilities connected with using
*   it, furnishing it to others, or providing it to a third party.
*/

#define IMAGE_VERSION    3
#ifndef _BYTE
typedef unsigned char  byte;
#define _BYTE  1
#endif
#include <sys/types.h>


/* External ascii file format
*  Note:  size of header should be 1024 (1K) bytes.
*/
typedef struct ImageHeader {
    char file_version[8];   /* header version */
    char header_size[8];    /* Size of file header in bytes (1024) */
    char image_width[8];    /* Width of the raster image */
    char image_height[8];   /* Height of the raster imgage */
    char num_colors[4];     /* Actual number of entries in c_map */
    char greyscale[2];      /* Flag if no c_map needed */
	char flag[2];     		/* Not used */
    char num_channels[3];   /* 0 or 1 = pixmap, 3 = RG&B buffers */
    char bytes_per_line[5]; /* bytes per scanline */
    char num_pictures[4];   /* Number of pictures in file */
    char bits_per_channel[4]; /* usually 1 or 8 */
    char alpha_channel[4];  /* Alpha channel flag */
    char runlength[4];      /* Runlength encoded flag */
    char author[48];        /* Name of who made it */
    char date[32];          /* Date and time image was made */
    char program[16];       /* Program that created this file */
    char comment[96];       /* other viewing info. for this image */
    unsigned char c_map[256][3]; /* RGB values of the pixmap indices */
} XimAsciiHeader;


/* Internal binary format
*/
typedef struct Color {
    byte red, grn, blu, pixel;
} Color;

typedef struct XimImage {
    int  width;             /* width of the image in pixels */
    int  height;            /* height of the image in pixels */
    int  bytes_line;        /* bytes to hold one scanline */
    unsigned  datasize;     /* size of one channel of data */
    short  nchannels;       /* data channels per pixel, usually 1 or3 */
    short  bits_channel;    /* depth of a channel, usually 1 or 8 */
    byte  *data;            /* pixmap or red channel data */
    byte  *grn_data;        /* green channel data */
    byte  *blu_data;        /* blue  channel data */
    byte  *other;           /* other (alpha) data */
    short  ncolors;         /* number of colors in the color table */
    Color  *colors;         /* colortable for pixmaps */
    short  curr_pic, npics; /* current & total file number of images */
    unsigned alpha_flag: 1; /* alpha channel flag - "other" is used */
    unsigned grey_flag : 1; /* is pixmap grey scaled */
    unsigned packed_flag:1; /* is data packed in one chunk of memory */
    unsigned runlen_flag:1; /* is data compressed flag (not used) */
    unsigned : 0;           /* future flags space and word alignment */
    char   *author;         /* author credit, copyright, etc */
    char   *date;           /* date image was made, grabbed, etc. */
    char   *program;        /* program used to make this */
    char   *name;           /* name of the image - usually filename */
    short  ncomments;       /* number of comments strings */
    char   **comments;      /* pointers to null terminated comments */
    char   *offset;         /* original offset in memory when packed */
    float  gamma;           /* image storage gamma */
    float  chroma_red[2];   /* x, y image chromacity coords */
    float  chroma_grn[2];
    float  chroma_blu[2];
    float  chroma_wht[2];
} XimImage;


/* Future external ascii variable length header - under review
*/
#if (VERSION == 4)
typedef struct XimAsciiHeader {
    char file_version[4];   /* header version */
    char header_size[8];    /* Size of file header (fixed part only) */
    char image_height[8];   /* Height of the raster imgage in pixels */
    char image_width[8];    /* Width of the raster image in pixels */
    char bytes_line[8];     /* Actual # of bytes separating scanlines */
    char bits_channel[4];   /* Bits per channel (usually 1 or 8) */
    char num_channels[4];   /* 1 = pixmap, 3 = RG&B buffers */
    char alpha_channel[2];  /* Alpha channel flag */
    char num_colors[4];     /* Number of entries in c_map (if any) */
    char num_pictures[4];   /* Number of images in file */
    char compress_flag[2];  /* Runlength encoded flag */
    char greyscale_flag[2]; /* Greyscale flag */
    char future_flags[4];
    char author[48];        /* Name of who made it, from passwd entry */
    char date[32];          /* Unix format date */
    char program[32];       /* Program that created this */
    char gamma[12];         /* image storage gamma */
    char chroma_red[24];    /* image red primary chromaticity coords. */
    char chroma_grn[24];    /*   "   green "          "         "     */
    char chroma_blu[24];    /*   "   blue  "          "         "     */
    char chroma_wht[24];    /*   "   white point      "         "     */
    char comment_length[8]  /* Total length of all comments */
    /* char *comment;           Null separated comments  */
    /* byte c_map[];   RGB Colortable, (ncolors * 3 bytes) */
} XimAsciiHeader;
#endif VERSION 4

#ifndef rnd
#define rnd(x)  ((int)((float)(x) + 0.5)) /* round a float to an int */
#endif

/* Note:
* - All external data is in char's in order to maintain easily
*   portability across machines and some human readibility.
* - Images may be stored as bitmaps (8 pixels/byte), pixmaps
*   (1 byte /pixel) or as seperate red, green, blue channel data
*   (3 bytes/pixel).
* - An alpha channel is optional and is found after every num_channels
*   of data.
* - Pixmaps or RG&B (and alpha) channel data are stored respectively
*   after the header.
* - If num_channels == 1 a pixmap is assumed and the colormap in the
*   header is used unless its greyscale.
* - Datasize is for one channel.
*/

/*** end ImageHeader.h ***/

#define XDECR   1
#define YDECR   2
#define YMAJOR  4
#define RED     0
#define GRN     1
#define BLU     2
#define EXP     3
#define MAXLINE 512

#define setcolor(col,r,g,b) (col[RED]=(r), col[GRN]=(g), col[BLU]=(b))
#define  copycolr(c1,c2)    (c1[0]=c2[0],c1[1]=c2[1], \
    	c1[2]=c2[2],c1[3]=c2[3])
#define  copycolor(c1,c2)   ((c1)[0]=(c2)[0],(c1)[1]=(c2)[1],\
		(c1)[2]=(c2)[2])
#define COLXS      128      /* excess used for exponent */
typedef float COLOR[3];     /* red, green, blue */
typedef byte  COLR[4];      /* red, green, blue, exponent */

#define	FREE(a) if ((a) != NULL) free((char*)(a)), (a) = NULL

char *malloc(), *calloc(), *AllocAndCopy();
extern debug_flag;
static byte RadiancePixel();


XimReadRadiance(ifp, xim, gamma)
FILE *ifp;
XimImage *xim;
double gamma;
{
    register int x, y, i;
    int width, height;
    COLOR *scanline;		/* RADIANCE variable */
    struct timeval  time_val;
    struct timezone  time_zone;
	struct passwd  *getpwnam(), *getpwuid(), *pwd = NULL;
	char  *login_ptr = NULL, *ctime(), *getlogin();
	byte gmap[256];

	/* get width, height from radiance file */
    bzero(xim, sizeof(XimImage));
    if (!RadianceGetSize(ifp, &width, &height))
		return 0;
	xim->width = xim->bytes_line = width;
	xim->height = height;
	xim->nchannels = 3;
	xim->bits_channel = 8;
	xim->datasize = xim->height * xim->bytes_line;
	xim->npics = 1;
    xim->data = (byte *)malloc(xim->datasize);
    xim->grn_data = (byte *)malloc(xim->datasize);
    xim->blu_data = (byte *)malloc(xim->datasize);
	if (!xim->data || !xim->grn_data || !xim->blu_data) {
		FREE(xim->data);
		FREE(xim->grn_data);
		FREE(xim->blu_data);
        XimWarn("XimReadRadiance: can't malloc xim buffers");
		return 0;
	}
    if (!(scanline = (COLOR*)malloc(width * sizeof(COLOR)))) {
        XimWarn("XimReadRadiance: can't malloc scanline");
        return(0);
    }
	for (i=0; i < 256; i++) {
		gmap[i] = (byte)(0.5 + 255.0 * pow(i / 255.0, 1.0/gamma));
		if (debug_flag)
			fprintf(stderr, "%2d: %u\n", i, gmap[i]);
	}
    for (y=0, i=0; y < height; y++) {
    	if (!freadscan(scanline, width, ifp)) {
        	XimWarn("freadscan: read error");
			free((char*)scanline);
        	return 0;
    	}
        for (x=0; x < width; x++, i++) {
        	xim->data[i] = gmap[RadiancePixel(scanline[x], RED)];
            xim->grn_data[i] = gmap[RadiancePixel(scanline[x], GRN)];
            xim->blu_data[i] = gmap[RadiancePixel(scanline[x], BLU)];
        }
    }
	free((char*)scanline);
	xim->comments = (char**)calloc(2, sizeof(char*));
    xim->comments[0] =AllocAndCopy("converted from RADIANCE format",40);
	xim->ncomments = 1;
	(void)gettimeofday(&time_val, &time_zone);
	xim->date = ctime(&time_val.tv_sec);
	xim->date[strlen(xim->date)-1] = '\0';
	/* date points to static memory, so copy it to make freeable */
	xim->date = AllocAndCopy(xim->date, strlen(xim->date)+1);
    if ((login_ptr = getlogin()) != NULL)
        pwd = getpwnam(login_ptr);
    else
        pwd = getpwuid(getuid());
    if (pwd && !(xim->author = AllocAndCopy(pwd->pw_gecos,
            strlen(pwd->pw_gecos)+1))) {
        XimWarn("XimReadXwdFile: Can't AllocAndCopy author's name");
        return 0;
    } else
        xim->author = NULL;
	return 1;
}


XimWriteRadiance(ofp, xim, gamma)
FILE *ofp;
XimImage *xim;
double gamma;
{
    COLOR *scanline;
    int width, height;
    register int x, y;
	register byte *red, *grn, *blu;
	float gmap[256];

	/* get width, height from xim */
    width = xim->width;
    height = xim->height;

	/* allocate scan line space */
    if (!(scanline = (COLOR *)malloc(width * sizeof(COLOR)))) {
        XimWarn("XimWriteRadiance: cant' alloc scanline");
        return(0);
    }
	/* write out the RADIANCE header */
    radiance_writeheader(ofp, width, height);
	red = xim->data;
	grn = xim->grn_data;
	blu = xim->blu_data;
	for (x=0; x < 256; x++) {
		gmap[x] = (float)pow((x / 255.0), gamma);
		if (debug_flag)
			fprintf(stderr, "%2d: %f\n", x, gmap[x]);
	}
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++)
            setcolor(scanline[x], gmap[red[x]], gmap[grn[x]], gmap[blu[x]]);
        radiance_writescanline(ofp, scanline, width);
		red += xim->bytes_line;
		grn += xim->bytes_line;
		blu += xim->bytes_line;
    }
	return 1;
}

static RadianceGetSize(fp, w, h)
FILE *fp;
int *w, *h;
{
    if (!GetHeader(fp, NULL))
		return 0;
    if (fgetresolu(w, h, fp) != (YMAJOR | YDECR)) {
        fprintf(stderr, "bad RADIANCE format\n");
        return 0;
    }
    return 1;
}

static byte RadiancePixel(pixel, chan)
COLOR pixel;
int chan;
{
    float value;

    value = pixel[chan];
    if (value <= 0.0)
        return (byte)0;
    else if (value >= 1.0)
        return (byte)255;
    return (byte)((int)((value * 256.0)));
}

static radiance_writeheader(fp, x, y)
FILE *fp;
int x;
int y;
{
    fputc('\n', fp);
    fputresolu(YMAJOR | YDECR, x, y, fp);
    fflush(fp);
}


radiance_writescanline(fp, buf, x)
FILE *fp;
COLOR *buf;
int x;
{
    if (fwritescan(buf, x, fp) < 0) {
        fprintf(stderr, "write error?\n");
        perror("fwrite");
        exit(1);
    }
}


freadscan(scanline, len, fp)           /* read in a scanline */
register COLOR *scanline;
int len;
register FILE *fp;
{
    COLR thiscolr;
    int rshift;
    register int i;

    rshift = 0;
    while (len > 0) {
        thiscolr[RED] = getc(fp);
        thiscolr[GRN] = getc(fp);
        thiscolr[BLU] = getc(fp);
        thiscolr[EXP] = getc(fp);
        if (feof(fp) || ferror(fp))
            return 0;
        if (thiscolr[RED] == 1 &&
            thiscolr[GRN] == 1 &&
            thiscolr[BLU] == 1) {
            for (i = thiscolr[EXP] << rshift; i > 0; i--) {
                copycolor(scanline[0], scanline[-1]);
                scanline++;
                len--;
            }
            rshift += 8;
        } else {
            colr_color(scanline[0], thiscolr);
            scanline++;
            len--;
            rshift = 0;
        }
    }
    return 1;
}


colr_color(col, clr)               /* convert short to float color */
register COLOR col;
register COLR clr;
{
    double f;

    if (clr[EXP] == 0)
        col[RED] = col[GRN] = col[BLU] = 0.0;
    else {
        f = ldexp(1.0, (int) clr[EXP] - (COLXS + 8));
        col[RED] = (clr[RED] + 0.5) * f;
        col[GRN] = (clr[GRN] + 0.5) * f;
        col[BLU] = (clr[BLU] + 0.5) * f;
    }
}

static GetHeader(fp, f)               /* get header from file */
FILE *fp;
int (*f)();
{
    char buf[MAXLINE];

    for (;;) {
        buf[MAXLINE - 2] = '\n';
        if (fgets(buf, sizeof(buf), fp) == NULL)
            return (-1);
        if (buf[0] == '\n')
            return 1;
        if (buf[MAXLINE - 2] != '\n') {
            ungetc(buf[MAXLINE - 2], fp);   /* prevent false end */
            buf[MAXLINE - 2] = '\0';
        }
        if (f != NULL)
            (*f)(buf);
    }
}


static fgetresolu(xrp, yrp, fp)           /* get x and y resolution */
int *xrp, *yrp;
FILE *fp;
{
    char buf[64], *xndx, *yndx;
    register char *cp;
    register int ord;

    if (fgets(buf, sizeof(buf), fp) == NULL)
        return (-1);
    xndx = yndx = NULL;
    for (cp = buf + 1; *cp; cp++)
        if (*cp == 'X')
            xndx = cp;
        else if (*cp == 'Y')
            yndx = cp;
    if (xndx == NULL || yndx == NULL)
        return (-1);
    ord = 0;
    if (xndx > yndx)
        ord |= YMAJOR;
    if (xndx[-1] == '-')
        ord |= XDECR;
    if (yndx[-1] == '-')
        ord |= YDECR;
    if ((*xrp = atoi(xndx + 1)) <= 0)
        return (-1);
    if ((*yrp = atoi(yndx + 1)) <= 0)
        return (-1);
    return(ord);
}


fputresolu(ord, xres, yres, fp)        /* put x and y resolution */
register int ord;
int xres, yres;
FILE *fp;
{
    if (ord & YMAJOR)
        fprintf(fp, "%cY %d %cX %d\n", ord & YDECR ? '-' : '+', yres,
            ord & XDECR ? '-' : '+', xres);
    else
        fprintf(fp, "%cX %d %cY %d\n", ord & XDECR ? '-' : '+', xres,
            ord & YDECR ? '-' : '+', yres);
}


fwritescan(scanline, len, fp)          /* write out a scanline */
register COLOR *scanline;
int len;
register FILE *fp;
{
    int rept;
    COLR lastcolr, thiscolr;

    lastcolr[RED] = lastcolr[GRN] = lastcolr[BLU] = 1;
    lastcolr[EXP] = 0;
    rept = 0;
    while (len > 0) {
        setcolr(thiscolr, scanline[0][RED],
            scanline[0][GRN],
            scanline[0][BLU]);
        if (thiscolr[EXP] == lastcolr[EXP] &&
            thiscolr[RED] == lastcolr[RED] &&
            thiscolr[GRN] == lastcolr[GRN] &&
            thiscolr[BLU] == lastcolr[BLU])
            rept++;
        else {
            while (rept) {  /* write out count */
                putc(1, fp);
                putc(1, fp);
                putc(1, fp);
                putc(rept & 255, fp);
                rept >>= 8;
            }
            putc(thiscolr[RED], fp);    /* new color */
            putc(thiscolr[GRN], fp);
            putc(thiscolr[BLU], fp);
            putc(thiscolr[EXP], fp);
            copycolr(lastcolr, thiscolr);
            rept = 0;
        }
        scanline++;
        len--;
    }
    while (rept) {  /* write out count */
        putc(1, fp);
        putc(1, fp);
        putc(1, fp);
        putc(rept & 255, fp);
        rept >>= 8;
    }
    return (ferror(fp) ? -1 : 0);
}

static setcolr(clr, r, g, b)          /* assign a short color value */
register COLR clr;
double r, g, b;
{
    double d, frexp();
    int e;

    d = r > g ? r : g;
    if (b > d)
        d = b;
    if (d <= 1e-32) {
        clr[RED] = clr[GRN] = clr[BLU] = 0;
        clr[EXP] = 0;
        return;
    }
    d = frexp(d, &e) * 256.0 / d;

    clr[RED] = (byte)(r * d);
    clr[GRN] = (byte)(g * d);
    clr[BLU] = (byte)(b * d);
    clr[EXP] = (byte)(e + COLXS);
}

/*** radiance_io.c ***/
