/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/*
 * tclimg.c --
 *
 *	Commands for image files.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/tclimg.c,v 1.12 2005/08/01 20:29:24 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * Local functions defined in this file
 */

static int ChanGetc(Tcl_Channel chan);
static int JpegNextMarker(Tcl_Channel chan);
static int JpegSize(Tcl_Channel chan, int *wPtr, int *hPtr);
static int JpegRead2Bytes(Tcl_Channel chan);
static int AppendObjDims(Tcl_Interp *interp, int w, int h);


/*
 *----------------------------------------------------------------------
 *
 * NsTclGifSizeObjCmd --
 *
 *	Implements ns_gifsize, returning a list of width and height.
 *
 * Results:
 *	Tcl result. 
 *
 * Side effects:
 *	See docs. 
 *
 *----------------------------------------------------------------------
 */

int
NsTclGifSizeObjCmd(ClientData arg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    int fd;
    unsigned char  buf[0x300];
    int depth, colormap, dx, dy, status;
    char *file;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "gif");
        return TCL_ERROR;
    }
    file = Tcl_GetString(objv[1]);
    fd = open(file, O_RDONLY|O_BINARY);
    if (fd == -1) {
        Tcl_AppendResult(interp, "could not open \"", file, "\": ",
			 Tcl_PosixError(interp), NULL);
        return TCL_ERROR;
    }
    status = TCL_ERROR;

    /*
     * Read the GIF version number
     */
    
    if (read(fd, buf, 6) != 6) {
readfail:
        Tcl_AppendResult(interp, "could not read \"", file, "\": ",
			 Tcl_PosixError(interp), NULL);
	goto done;
    }

    if (strncmp((char *) buf, "GIF87a", 6) && 
	strncmp((char *) buf, "GIF89a", 6)) {
badfile:
        Tcl_AppendResult(interp, "invalid gif file: ", file, NULL);
        goto done;
    }

    if (read(fd, buf, 7) != 7) {
	goto readfail;
    }

    depth = 1 << ((buf[4] & 0x7) + 1);
    colormap = (buf[4] & 0x80 ? 1 : 0);

    if (colormap) {
        if (read(fd, buf, (size_t)(3*depth)) != (3*depth)) {
            goto readfail;
        }
    }

  outerloop:
    if (read(fd, buf, 1) != 1) {
        goto readfail;
    }

    if (buf[0] == '!') {
        unsigned char count;
	
        if (read(fd, buf, 1) != 1) {
            goto readfail;
        }
      innerloop:
        if (read(fd, (char *) &count, 1) != 1) {
            goto readfail;
        }
        if (count == 0) {
            goto outerloop;
        }
        if (read(fd, buf, count) != count) {
            goto readfail;
        }
        goto innerloop;
    } else if (buf[0] != ',') {
        goto badfile;
    }

    if (read(fd,buf,9) != 9) {
        goto readfail;
    }

    dx = 0x100 * buf[5] + buf[4];
    dy = 0x100 * buf[7] + buf[6];
    if(AppendObjDims(interp, dx, dy) != TCL_OK) {
		return TCL_ERROR;
	};
    status = TCL_OK;

done:
    close(fd);
    return status;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclJpegSizeObjCmd --
 *
 *	Implements ns_jpegsize as obj command. 
 *
 * Results:
 *	Tcl result. 
 *
 * Side effects:
 *	See docs. 
 *
 *----------------------------------------------------------------------
 */

int
NsTclJpegSizeObjCmd(ClientData arg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    int   code, w = 0, h = 0;
    Tcl_Channel chan;
    char *file;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "file");
	return TCL_ERROR;
    }
    file = Tcl_GetString(objv[1]);
    chan = Tcl_OpenFileChannel(interp, file, "r", 0);
    if (chan == NULL) {
        /* Tcl function will leave error message in interp's result */
        return TCL_ERROR;
    }
    if (Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) {
        /* Tcl function will leave error message in interp's result */
        return TCL_ERROR;
    }
    code = JpegSize(chan, &w, &h);
    Tcl_Close(interp, chan);
    if (code != TCL_OK) {
	Tcl_AppendResult(interp, "invalid jpeg file: ", file, NULL);
	return TCL_ERROR;
    }
    if(AppendObjDims(interp, w, h) != TCL_OK) {
	return TCL_ERROR;
    };
    return TCL_OK;
}

#define M_SOI   0xD8		/* Start Of Image (beginning of datastream) */
#define M_EOI   0xD9		/* End Of Image (end of datastream) */
#define M_SOS   0xDA		/* Start Of Scan (begins compressed data) */

static int
JpegSize(Tcl_Channel chan, int *wPtr, int *hPtr)
{
    unsigned int i, w, h;
    Tcl_WideInt  numbytes;

    if (ChanGetc(chan) == 0xFF && ChanGetc(chan) == M_SOI) {
	while (1) {
	    i = JpegNextMarker(chan);
	    if (i == EOF || i == M_SOS || i == M_EOI) {
	    	break;
	    }
            if (0xC0 <= i && i <= 0xC3) {
		if (JpegRead2Bytes(chan) != EOF && ChanGetc(chan) != EOF
		    && (h = JpegRead2Bytes(chan)) != EOF
		    && (w = JpegRead2Bytes(chan)) != EOF) {
		    *wPtr = w;
		    *hPtr = h;
		    return TCL_OK;
		}
		break;
	    }
	    numbytes = JpegRead2Bytes(chan);
	    if (numbytes < 2 || Tcl_Seek(chan, numbytes - 2, SEEK_CUR) == -1) {
	    	break;
	    }
	}
    }
    return TCL_ERROR;
}


/*
 *----------------------------------------------------------------------
 *
 * JpegRead2Bytes --
 *
 *	Read 2 bytes, convert to unsigned int. All 2-byte quantities 
 *	in JPEG markers are MSB first. 
 *
 * Results:
 *	The two byte value, or -1 on error. 
 *
 * Side effects:
 *	Advances file pointer. 
 *
 *----------------------------------------------------------------------
 */

static int
JpegRead2Bytes(Tcl_Channel chan)
{
    int c1, c2;
    
    c1 = ChanGetc(chan);
    c2 = ChanGetc(chan);
    if (c1 == EOF || c2 == EOF) {
	return -1;
    }
    return (int)(((unsigned int) c1) << 8) + ((unsigned int) c2);
}


/*
 *----------------------------------------------------------------------
 *
 * JpegNextMarker --
 *
 *	Find the next JPEG marker and return its marker code. We 
 *	expect at least one FF byte, possibly more if the compressor 
 *	used FFs to pad the file. There could also be non-FF garbage 
 *	between markers. The treatment of such garbage is 
 *	unspecified; we choose to skip over it but emit a warning 
 *	msg. This routine must not be used after seeing SOS marker, 
 *	since it will not deal correctly with FF/00 sequences in the 
 *	compressed image data... 
 *
 * Results:
 *	The next marker code.
 *
 * Side effects:
 *	Will eat up any duplicate FF bytes.
 *
 *----------------------------------------------------------------------
 */

static int
JpegNextMarker(Tcl_Channel chan)
{
    int c;

    /*
     * Find 0xFF byte; count and skip any non-FFs.
     */
    
    c = ChanGetc(chan);
    while (c != EOF && c != 0xFF) {
	c = ChanGetc(chan);
    }
    if (c != EOF) {
	/*
	 * Get marker code byte, swallowing any duplicate FF bytes.
	 */
	
	do {
	    c = ChanGetc(chan);
	} while (c == 0xFF);
    }

    return c;
}


/*
 *----------------------------------------------------------------------
 *
 * ChanGetc --
 *
 *	Read a single unsigned char from a channel.
 *
 * Results:
 *	Character or EOF. 
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ChanGetc(Tcl_Channel chan)
{
    unsigned char buf[1];

    if (Tcl_Read(chan, (char *) buf, 1) != 1) {
	return EOF;
    }
    return (int) buf[0];
}


/*
 *----------------------------------------------------------------------
 *
 * AppendObjDims --
 *
 *	Format and append width and height dimensions.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	List elements appended to interp result.
 *
 *----------------------------------------------------------------------
 */

static int
AppendObjDims(Tcl_Interp *interp, int w, int h)
{
    char buf[20];
    Tcl_Obj *result = Tcl_NewObj();

    sprintf(buf, "%d", w);
    if (Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(buf, -1))) {
        return TCL_ERROR;
    }
    sprintf(buf, "%d", h);
    if (Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(buf, -1))) {
        return TCL_ERROR;
    }

    Tcl_SetObjResult(interp, result);

    return TCL_OK;
}
