/* xtoxim.c
*  Author: Philip Thompson (phils@athena.mit.edu)
*   - Converts XWDFiles to "xim" 8 bit format.
*   - Also translates parallax video to 24 bit format.
*  Copyright (c) 1989, Massachusetts Institute of Technology
*                Philip R. Thompson
*                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 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.
*/
#ifndef lint
static char  xtoxim_rcsid[] =
    "$Header: xtoxim.c,v 1.5 89/02/20 00:47:12 phils Locked $";
#endif

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XWDFile.h>
#include <sys/time.h>
#include <pwd.h>
#include <sys/types.h>
#include "ImageHeader.h"

typedef unsigned char  byte;
struct conv_table {
    long ry, gy, by;
};

char  *progName;
char *calloc(), *malloc(), *strncpy();
int ncolors, nchannels = 1;
Bool debug_flag = False;

main(argc, argv)
int argc;
char **argv;
{
    register int  i, j;
    register byte  *inbuf = NULL, *grn_buf = NULL, *blu_buf = NULL;
    unsigned  bufsize;
    u_long  swaptest = 1;
    int width, height, name_size;
    char *win_name;
    Bool plx_flag = False, grey_flag = False;
    XWDFileHeader header;
    ImageHeader  p_head;
    FILE *in_file = stdin, *out_file = stdout, *fopen();
    char  *time_ptr, *login_ptr, *ctime(), *getlogin();
    struct passwd  *getpwnam(), *getpwuid(), *pwd;
    struct timeval  time_val;
    struct timezone  time_zone;

    progName = argv[0];
    for (i=1; i < argc; i++) {
        if (strncmp(argv[i], "-i", 2) == 0) {
            if ((in_file = fopen(argv[++i], "r")) == NULL)
                error("Can't read input file: %s.", argv[i]);
            continue;
        }
        if (strncmp(argv[i], "-o", 2) == 0) {
            if ((out_file = fopen(argv[++i], "w")) == NULL)
                error("Can't open %s\n", argv[i]);
            continue;
        }
        if (strncmp(argv[i], "-p", 2) == 0) {
            plx_flag = True;
            continue;
        }
        if (strncmp(argv[i], "-d", 2) == 0) {
            debug_flag = True;
            continue;
        }
        if (strncmp(argv[i], "-g", 2) == 0) {
            grey_flag = True;
            continue;
        }
        error("Usage: %s [-in file] [-out file] [-plx]", progName);
    }

    /* Read in header information. */
    if (fread((char *)&header, sizeof(header), 1, in_file) != 1)
      error("Unable to read dump file header.");
    if (*(char *)&swaptest)
        swaplong((char *) &header, sizeof(header));

    /* check to see if the dump file is in the proper format.  */
    if (header.file_version != XWD_FILE_VERSION)
        error("XWD file format version mismatch.");
    if (header.header_size < sizeof(header))
        error("XWD header size is too small.");

    /* Calloc and read window name.  */
    name_size = ((int)header.header_size - sizeof(header));
    if ((win_name = calloc((unsigned)name_size, sizeof(char))) == NULL)
        error("Can't calloc window name storage.");
    if (fread(win_name, sizeof(char), name_size, in_file) != name_size)
        error("Unable to read window name from dump file.");

    width = (int)header.pixmap_width;
    height = (int)header.pixmap_height;
    if (header.pixmap_format != ZPixmap)
        error("Can only do ZPixmap formats");
    if (header.pixmap_depth > 8)
        error("Image is too deep");
    bzero((char *)&p_head, sizeof(ImageHeader));

    bufsize = width * height;
    if (ncolors = (int)header.ncolors) {
        XColor  *colors;
        colors = (XColor *)calloc((unsigned)ncolors, sizeof(XColor));
        if (colors == NULL)
            error("Can't calloc() colors.");
        if (fread((char *)colors, sizeof(XColor), ncolors, in_file)
                != ncolors)
            error("Unable to read color map from dump file.");
        if (debug_flag)
            fprintf(stderr,"Read in %d colors\n", ncolors);
        for (i=0; i < ncolors; i++) {
            if (*(char *)&swaptest) {
                swaplong((char *)&colors[i].pixel, sizeof(long));
                swapshort((char *)&colors[i].red, 3*sizeof(short));
            }
            j = (int)colors[i].pixel;
            p_head.c_map[j][0] = (byte)(colors[i].red / 257);
            p_head.c_map[j][1] = (byte)(colors[i].green / 257);
            p_head.c_map[j][2] = (byte)(colors[i].blue / 257);
            if (debug_flag)
                fprintf(stderr,"pixel[%u]: r %5u   g %5u   b %5u\n",
                    colors[i].pixel, colors[i].red, colors[i].green,
                    colors[i].blue);
        }
        free((char *)colors);
    }
    /* Read in the pixmap buffer.  */
    if ((inbuf = (byte *)calloc(bufsize, sizeof(byte))) == NULL)
        error("Can't calloc data buffer\n");
    if (fread((char *)inbuf, 1, (int)bufsize, in_file) != bufsize)
            error("Unable to read pixmap from dump file.");
    (void)fclose(in_file);

    if (plx_flag) {
        inbuf += 44 * width;     /* hack for removing to top buttons */
        height -= 44;    /* assuming height of 44 for box */
        bufsize = width * height; /* ONLY on Athena's xvideo widget */
        if (!grey_flag) {
            grn_buf = (byte *)malloc(bufsize);
            blu_buf = (byte *)malloc(bufsize);
            if (grn_buf == NULL || blu_buf == NULL)
                error("Can't malloc() color buffers\n");
        }
        yuv_decode(inbuf, grn_buf, blu_buf, bufsize, grey_flag);
        ncolors = 256;
        if (grey_flag)
            nchannels = 1;
        else
            nchannels = 3;
        for (i=0; i < ncolors; i++)
            p_head.c_map[i][0] = p_head.c_map[i][1] =
                p_head.c_map[i][2] = (byte)i;
    }

    (void)gettimeofday(&time_val, &time_zone);
    time_ptr = ctime(&time_val.tv_sec);
    time_ptr[strlen(time_ptr)-1] = '\0';
    login_ptr = getlogin();
    if (*login_ptr)
        pwd = getpwnam(login_ptr);
    else
        pwd = getpwuid((int)getuid());

    /* Fill the xim header */
    (void)sprintf(p_head.header_size,"%d", sizeof(ImageHeader));
    (void)sprintf(p_head.file_version,"%d", IMAGE_VERSION);
    (void)sprintf(p_head.image_width,"%d", width);
    (void)sprintf(p_head.image_height,"%d", height);
    (void)sprintf(p_head.num_colors,"%d", ncolors);
    (void)sprintf(p_head.num_channels,"%d", nchannels);
    (void)sprintf(p_head.date,"%s", time_ptr);
    (void)strncpy(p_head.author, pwd->pw_gecos, sizeof(p_head.author));
    (void)strncpy(p_head.program, win_name, sizeof(p_head.program));
    (void)fprintf(stderr,"Author: %s\n", p_head.author);
    (void)fprintf(stderr,"Date: %s\n", p_head.date);
    (void)fprintf(stderr,"Program: %s\n", p_head.program);
    (void)fprintf(stderr,"Width: %d  Height: %d\n", width, height);


    (void)fwrite((char *)&p_head, sizeof(ImageHeader), 1, out_file);
    if (nchannels == 3) {
        (void)fwrite((char *)inbuf, 1, (int)bufsize, out_file);
        (void)fwrite((char *)grn_buf, 1, (int)bufsize, out_file);
        (void)fwrite((char *)blu_buf, 1, (int)bufsize, out_file);
    } else 
        (void)fwrite((char *)inbuf, 1, (int)bufsize, out_file);
    (void)fclose(out_file);
    exit(0);
}


yuv_decode(red_buf, grn_buf, blu_buf, bufsize, grey_flag)
byte *red_buf, *grn_buf, *blu_buf;
unsigned  bufsize;
Bool grey_flag;
{
    register int  i, j, k;
    register int  red, green, blue, gray, idx;
    struct conv_table  *p, ctable[256];
    byte minmax_table[768], *normalize;

    setup_tables(ctable, minmax_table);
    normalize = &minmax_table[256];

    for (j=0, i=0; i < bufsize; i+=4) {
        idx = ((red_buf[i] & 0xc0)>>6) | ((red_buf[i+1] & 0xc0)>>4) |
                ((red_buf[i+2] & 0xc0)>>2) | ((red_buf[i+3] & 0xc0));
        p = &ctable[idx];
        for (k=0; k < 4; k++, j++) {
            gray = (red_buf[j] & 0x3e) << 2;
            red = normalize[gray + p->ry];
            green = normalize[gray + p->gy];
            blue = normalize[gray + p->by];
            if (grey_flag)
                red_buf[j] = (byte)((int)((float)red*0.299 +
                    (float)green*0.587 + (float)blue*0.114 + 0.5));
            else {
                red_buf[j] = (byte)red;
                grn_buf[j] = (byte)green;
                blu_buf[j] = (byte)blue;
            }
        }
    }
}
            

/*  Copyright (c) 1987 by
*   PARALLAX GRAPHICS, INCORPORATED, Santa Clara, California.
*   All rights reserved
*   Author:   Martin J. Levy.
*             Parallax Graphics
*   Date:     October 1987
*   Modified: P. Thompson, Nov. 1988.  Simplified code.
*/
#define max(a,b)    ((a) < (b) ? (b) : (a))
#define min(a,b)    ((a) > (b) ? (b) : (a))

setup_tables(ctbl, minmax_table)
struct conv_table  ctbl[256];
byte  minmax_table[768];
{
    register int i;
    register byte *minmax = &minmax_table[256];

    for (i=0; i < 256; i++) {
        ctbl[i].ry = (((((i & 0x03)<<2) | ((i & 0x0c)>>2)) & 0xf) - 8)
            << 13;
        ctbl[i].ry *= 179;

        ctbl[i].by = (((((i & 0x30)>>2) | ((i & 0xc0)>>6)) & 0xf) - 8)
            << 13;
        ctbl[i].by *= 228;

        ctbl[i].gy = -((ctbl[i].ry>>8) * 131) - ((ctbl[i].by>>8) * 49);

        ctbl[i].ry >>= 16;
        ctbl[i].gy >>= 16;
        ctbl[i].by >>= 16;

        if (debug_flag)
            fprintf(stderr,"pixel[%u]: r %4ld   g %4ld   b %4ld\n", i,
                ctbl[i].ry, ctbl[i].gy, ctbl[i].by);
    }
    /* pre computed min and max of all pixel values, normalizes 0-255 */
    for (i= -256; i < 512; i++)
        minmax[i] = min(255, max(0, i));
}


/* VARARGS1 */
error(s1, s2)
char *s1, *s2;  /* Error description string. */
{
    extern int errno;

    fprintf(stderr, s1, s2);
    if (errno != 0) {
        perror(progName);
        fprintf(stderr, "\n");
    }
    exit(1);
}

swapshort (bp, n)
register char *bp;
register unsigned n;
{
    register char c;
    register char *ep = bp + n;

    while (bp < ep) {
        c = *bp;
        *bp = *(bp + 1);
        bp++;
        *bp++ = c;
    }
}

swaplong (bp, n)
register char *bp;
register unsigned n;
{
    register char *sp, c;
    register char *ep = bp + n;

    while (bp < ep) {
        sp = bp + 3;
        c = *sp;
        *sp = *bp;
        *bp++ = c;
        sp = bp + 1;
        c = *sp;
        *sp = *bp;
        *bp++ = c;
        bp += 2;
    }
}

/* End of xtoxim.c */
