/****************************************************************************
* $Id: decoder.c,v 1.4 2006-11-22 13:51:30+01 rolf Exp rolf $
*****************************************************************************/
/*
 * Decoder.c --
 *
 * Rosetta-Lander ROLIS Image decoder
 *
 * 04. October 2000: Rolf.Schroedter@dlr.de
 */
/////////////////////////////////////////////////////////////////////////////////
// Decoder.c --
//
// Rosetta-Lander ROLIS Decoder for (16-bit) gray images.
// Compatible to the ROLIS onboard CODER software
//
//	- Decoding of wavelet compressed images
//	- Image size = 1024 x 1024
//	- Tile size = 64 x 64
// Created September 27, 2000: Rolf.Schroedter@dlr.de
//
// Changes:
//	Jan 12, 2001: adjust_negative fine-tuned
//		- pixel=-100..-1 :	underflow --> pixel=0
//		- pixel=-32768..-100 :	overflow  --> pixel=32767
//
//      Jun 10, 2004: Added support for RawImages with mask (selected pixels)
//              - decoder::unpack mask data
/////////////////////////////////////////////////////////////////////////////////

#include <string.h>
#include <stdlib.h>

#define USE_TCL_STUBS

#include <tcl.h>
#include "decoder.h"

#ifdef __WIN32__
#   define WIN32_LEAN_AND_MEAN
#   include <windows.h>
#   undef WIN32_LEAN_AND_MEAN

/*
 * VC++ has an alternate entry point called DllMain, so we need to rename
 * our entry point.
 */

#   if defined(_MSC_VER)
#       define EXPORT(a,b) __declspec(dllexport) a b
#       define DllEntryPoint DllMain
#   else
#       if defined(__BORLANDC__)
#           define EXPORT(a,b) a _export b
#       else
#           define EXPORT(a,b) a b
#       endif
#   endif
#else
#   define EXPORT(a,b) a b
#endif

#include "defines.h"
#include "bitstm.h"
#include "wave_linear.h"
#include "zerotree.h"

#define MY_TCL_VERSION ( 100*TCL_MAJOR_VERSION + 10*TCL_MINOR_VERSION + 1*TCL_RELEASE_SERIAL )

#if MY_TCL_VERSION < 820
#	error "Need Tcl version >= 8.2.0"
#endif

// Some TCL defines making life easier
#define NoClient	(ClientData)NULL
#define NoDelete	(Tcl_CmdDeleteProc *)NULL

static int initialized = 0;

/////////////////////////////////////////////////////////////////////////////////
// Little helpers on 2^N

int ilog2( unsigned x )		/* log2 of a number */
{
    int n = -1;
	while( x > 0 ) {
		x /= 2, n++;
	}
    return n;
}
int check2N( int x )	    /* check whether x = 2^N */
{
    return ( (x-1) & x ) == 0;
}

/////////////////////////////////////////////////////////////////////////////////
// Global DATA
// Holds image during decompression

struct {
	size_t	SizeX, SizeY;		// Image size in pixels X x Y
	size_t  ImgSize;			// Size of whole image in bytes
	size_t	TileSize;			// Tile size square
	sWord	*Img;				// Pointer to whole image
} DC
= { 0, 0, 0, 0, NULL };

/////////////////////////////////////////////////////////////////////////////////
// FreeImg(), AllocImg ()

static void freeImg( void )
{
	if( DC.Img != NULL ) {
		free(DC.Img);
		DC.Img = NULL;
	}
}
static int allocImg( size_t xSize, size_t ySize, size_t tileSize )
{
	freeImg();
	DC.SizeX = xSize;
	DC.SizeY = ySize;
	DC.TileSize = tileSize;
	DC.ImgSize = xSize * ySize * sizeof(DC.Img[0]);
	DC.Img = malloc(DC.ImgSize);

	memset( DC.Img, 0, DC.ImgSize );

	return (DC.Img != NULL);
}

/////////////////////////////////////////////////////////////////////////////////
// Adjust negative values to zero
// - Needed after reverse wavelet transform to avoid negative pixel values
// - pixel<0 may be
//		a) underflow: original pixel had a small value
//		b) overflow:  original pixel was near to saturation
static void adjust_negative( sWord *img, size_t size )
{
	while( size-- > 0 ) {
		if( *img < 0 ) {
			if( *img > -100 ) {	// underflow
//				*img = 0;
			} else {			// overflow
//				*img = 32767;
			}
		}
		img++;
	}
}
/////////////////////////////////////////////////////////////////////////////////
// Get/Set pixel values in image
// - with checking the position parameters
/////////////////////////////////////////////////////////////////////////////////
static int getPixel( int xPos, int yPos )
{
	// Check if position inside image
	if( (xPos < 0) || (yPos < 0) || (xPos >= DC.SizeX) || (yPos >= DC.SizeY) ) {
		return 0;
	} else {
		return DC.Img[yPos * DC.SizeX + xPos];
	}
}
static void setPixel( int xPos, int yPos, sWord val )
{
	// Check if position inside image
	if( (xPos < 0) || (yPos < 0) || (xPos >= DC.SizeX) || (yPos >= DC.SizeY) ) {
		return;
	} else {
		DC.Img[yPos * DC.SizeX + xPos] = val;
	}
}

/////////////////////////////////////////////////////////////////////////////////
// Swap data hi<->lo
// decoder::swap var_name
//	   var_name - name of a binary data variable to be swaped
/////////////////////////////////////////////////////////////////////////////////
static int swapProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	Tcl_Obj	*varPtr;		// variable object to be modified
	uWord	*p, x, *data;	// data to be swaped
	int		i, dataLen;		// strlen(data)

	// Check for the number of parameters
	if( objc != 2 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "data" );
		return TCL_ERROR;
	}
	// Get command parameter
	varPtr = Tcl_ObjGetVar2(interp, objv[1], (Tcl_Obj*)NULL, (TCL_LEAVE_ERR_MSG | TCL_PARSE_PART1));
	if (varPtr == NULL) {
		return TCL_ERROR;
	}
	data = (unsigned short*) Tcl_GetByteArrayFromObj( varPtr, &dataLen );

	// swap data, change string representation of object
	for( p=data, i=dataLen/2; i>0; p++, i-- ) {
		x = *p;
		*p = ((x & 0x00FF) << 8) | (x >> 8);
	}

	Tcl_SetByteArrayObj( varPtr, (unsigned char *)data, dataLen );

	// Must call Tcl_ObjSetVar2 to support variable tracing
//	varPtr = Tcl_ObjSetVar2(interp, objv[1], (Tcl_Obj*)NULL, varPtr, (TCL_LEAVE_ERR_MSG | TCL_PARSE_PART1));
//	if (varPtr == NULL) {
//		return TCL_ERROR;
//	}
	Tcl_SetObjResult(interp, varPtr);

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
// Create an empty new image
// decoder::new_img xSize ySize tileSize
//      xSize    - X-Size of the image in pixel, must be = A*tileSize
//	ySize    - Y-Size of the image in pixel, must be = B*tileSize
//	tileSize - Size of the square tiles in pixel, must be = 2^N
// For ROLIS compressed frames type=8: xSize=ySize=1024, tileSize=64
/////////////////////////////////////////////////////////////////////////////////
static int newImgProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	int xSize, ySize, tileSize;
	int okay;
	Tcl_Obj	*resultPtr;

	if( objc != 4 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "xSize ySize tileSize" );
		return TCL_ERROR;
	}

        // The result should be written here
        resultPtr = Tcl_GetObjResult(interp);

	// Get command parameter
        if( Tcl_GetIntFromObj(interp, objv[1], &xSize) != TCL_OK ) {
		return TCL_ERROR;
	}
        if( Tcl_GetIntFromObj(interp, objv[2], &ySize) != TCL_OK ) {
		return TCL_ERROR;
	}
        if( Tcl_GetIntFromObj(interp, objv[3], &tileSize) != TCL_OK ) {
		return TCL_ERROR;
	}

	// Verify command parameters
	if( (xSize < 0) || (ySize < 0) || (tileSize < 0) ) {
		Tcl_AppendStringsToObj(resultPtr, "Image sizes must be > 0", (char *)NULL);
        return TCL_ERROR;
	}
	if( check2N(tileSize) == 0 ) {
		Tcl_AppendStringsToObj(resultPtr, "tileSizes must be = 2^N", (char *)NULL);
        return TCL_ERROR;
	}
	if( (xSize % tileSize != 0) || (ySize % tileSize != 0) ) {
		Tcl_AppendStringsToObj(resultPtr, "xSize,ySize must be multiples of tileSizes", (char *)NULL);
        return TCL_ERROR;
	}

	okay = allocImg( xSize, ySize, tileSize );
	Tcl_SetObjResult(interp, Tcl_NewIntObj(okay) );

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
// Return current image data
// decoder::get_image
/////////////////////////////////////////////////////////////////////////////////
static int getImgProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	Tcl_Obj *data;

	// Check for the number of parameters
	if( objc != 1 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "" );
		return TCL_ERROR;
	}

	if( DC.Img == NULL ) {
		data = Tcl_NewByteArrayObj( "", 0 );
	} else {
		data = Tcl_NewByteArrayObj( (void *)DC.Img, DC.ImgSize );
	}

	Tcl_SetObjResult(interp, data);

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
// Put a tile into the image
// decoder::decode_tile code
//     code   - binary data containing coded tile
//	        starts with: numTile(8 bits), image(3 bits), MSB(5 bits), code
/////////////////////////////////////////////////////////////////////////////////
static int decodeTileProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	void		*code;			// raw binary code
	int		codeLen;		// strlen(code)
	int		codeStep;		// Number of decoding steps left, result of ztree_decode()
	ztreeNodePtr	zt;			// Zero tree
	size_t		tileOfs;		// Tile position within image
	unsigned	tilesPerLine;		// Number of tiles in one image line
	bsWord		numTile, numImg;	// Tile header 8+3=11 bits
										// MSB will be read by zt_decode()
	Tcl_Obj	*resultPtr;

        // The result should be written here
        resultPtr = Tcl_GetObjResult(interp);

	// Check for the number of parameters
	if( objc != 2 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "code" );
		return TCL_ERROR;
	}
	// Get command parameter
	code = (unsigned short*) Tcl_GetByteArrayFromObj( objv[1], &codeLen );
	if( codeLen <= 2 ) {
		Tcl_AppendStringsToObj(resultPtr, "Code size must at least be > 2 bytes", (char *)NULL);
                return TCL_ERROR;
	}

	bsInit( code, codeLen );
	bsGetWord( &numTile, 0xFF );	// 8 Bits: Number of tile
	bsGetWord( &numImg,  0x07 );	// 3 Bits: Number of image

	tilesPerLine = DC.SizeX / DC.TileSize;
	tileOfs = DC.TileSize * (numTile / tilesPerLine) * DC.SizeX			// line offset
			+ DC.TileSize * (numTile % tilesPerLine);					// + column offset

	zt = ztree_build( NULL, DC.TileSize, DC.SizeX );
	codeStep = ztree_decode( zt, codeLen*8-11 );					// 11 Bits = numTile(8)+numImg(3)
	ztree_undo( zt, DC.Img+tileOfs, DC.TileSize, DC.SizeX );

	Tcl_SetObjResult(interp, Tcl_NewIntObj(codeStep));

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
// Perform reverse wavelet transformation on the whole tile
// decoder::wavelet_rev
/////////////////////////////////////////////////////////////////////////////////
static int waveletRevProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	// Check for the number of parameters
	if( objc != 1 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "" );
		return TCL_ERROR;
	}

	lin_2d_reverse( DC.Img, DC.SizeX, DC.SizeY, ilog2(DC.TileSize) );
	adjust_negative( DC.Img, DC.SizeX * DC.SizeY );

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
// Correct the Mean value for missing tiles
// decoder::missing_tiles
/////////////////////////////////////////////////////////////////////////////////
static int missingTilesProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	int		x, y;
	sWord	pixel;
	int		sum, n;

	// Check for the number of parameters
	if( objc != 1 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "" );
		return TCL_ERROR;
	}
	// Walk through all Tiles and check the mean values
	for( y = 0; y < DC.SizeY; y += DC.TileSize ) {
		for( x = 0; x < DC.SizeX; x += DC.TileSize ) {
			pixel = getPixel(x, y);
			if( pixel == 0 ) {	// missing tile
				sum = n = 0;
				pixel = getPixel(x-DC.TileSize, y);		// left tile
				sum += pixel; n += (pixel != 0);
				pixel = getPixel(x+DC.TileSize, y);		// right tile
				sum += pixel; n += (pixel != 0);
				pixel = getPixel(x, y-DC.TileSize);		// upper tile
				sum += pixel; n += (pixel != 0);
				pixel = getPixel(x, y+DC.TileSize);		// lower tile
				sum += pixel; n += (pixel != 0);
				if( n > 0 ) {
					setPixel(x, y, (sWord)((sum+n/2)/n));	// mean value, rounded
				} else {
					setPixel(x, y, (sWord)0);	// mean value, rounded
				}
			}
		}
	}

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
// Unpack masked binary data from a bitstream
// decoder::unpack mask bits
//      mask     - 16-bit mask with bits included in the bitstream
//                 e.g. 0x03F0 means that 6 bits per pixel are packed
//      bits     - Input binary data to be unpacked (16-bit little endian)
// Returns
//      binary 16-bit pixel values in little-endian (lo,hi)
/////////////////////////////////////////////////////////////////////////////////
static int unpackProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[] )
{
	int mask, bpp, nPixels, len, i;
        unsigned tmp;
	unsigned char *data, *bits;

	if( objc != 3 ) {
		Tcl_WrongNumArgs( interp, 1, objv, "mask bits" );
		return TCL_ERROR;
	}

	// Get command parameter
        if( Tcl_GetIntFromObj(interp, objv[1], &mask) != TCL_OK ) {
		return TCL_ERROR;
	}
	bits = Tcl_GetByteArrayFromObj( objv[2], &len );

        // Count the number of bits per pixel from mask
        // = Number of one's in mask
        //
        bpp = 0;
        tmp = (unsigned)mask;
        while( tmp ) {
                bpp += (tmp & 0x0001);
                tmp >>= 1;
        }
        // The total number of pixels is derived from binary size and bpp
        // Allocate the memory
        //
        nPixels = len * 8 / bpp;
        data = calloc(nPixels, 2);

        // Main loop: Unpack the data bits
        //
        {
                unsigned wMask = 0;     // Byte read mask
                unsigned word  = 0;     // A single byte from the input bits

                for( i=0; i<nPixels; i++ ) {
                        unsigned short x = 0, xMask = 0x8000;

                        while( xMask ) {
                                if( (xMask & mask) != 0 ) {
                                        // This bit should be unpacked
                                        // ==> Read a single bit from input
                                        //
                                        if( wMask == 0 ) {
					    	// Read little endian
					    	//
                                                word  = bits[0] + (bits[1] << 8);
                                                bits += 2;
                                                wMask = 0x8000;
                                        }
                                        // If bit is set, then add it to x-value
                                        //
                                        if( (word & wMask) != 0 ) {
                                                x |= xMask;
                                        }
                                        // Proceed with next input bit
                                        //
                                        wMask >>= 1;
                                }
                                // Proceed with next output bit
                                //
                                xMask >>= 1;
                        }
                        // Write the pixel word as little endian
                        //
                        data[2*i]   = x & 0x00FF;
                        data[2*i+1] = (x >> 8) & 0x00FF;
                }
        }

        // Return byte array object
        //
        Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((void *)data, nPixels*2) );
        free(data);

	return TCL_OK;
}

/////////////////////////////////////////////////////////////////////////////////
//
// Decoder_Init , Decoder_SafeInit, Decoder_InitStandAlone --
//	Create Decoder commands.
//
// Results:
//	None
//
// Side effects:
//	None
//
/////////////////////////////////////////////////////////////////////////////////

EXPORT(int, Decoder_Init)(Tcl_Interp *interp)
{
	const char *version;
	if ((version = Tcl_InitStubs(interp, "8", 0)) == NULL) {
		return TCL_ERROR;
	}
	if (!initialized) {
		initialized = 1;
		Tcl_CreateObjCommand( interp, "decoder::swap",		swapProc,		NoClient, NoDelete );
		Tcl_CreateObjCommand( interp, "decoder::new_img",	newImgProc,		NoClient, NoDelete );
		Tcl_CreateObjCommand( interp, "decoder::get_img",	getImgProc,		NoClient, NoDelete );
		Tcl_CreateObjCommand( interp, "decoder::decode_tile",	decodeTileProc,		NoClient, NoDelete );
		Tcl_CreateObjCommand( interp, "decoder::missing_tiles",	missingTilesProc,	NoClient, NoDelete );
		Tcl_CreateObjCommand( interp, "decoder::wavelet_rev",	waveletRevProc,		NoClient, NoDelete );
		Tcl_CreateObjCommand( interp, "decoder::unpack",	unpackProc,		NoClient, NoDelete );
	}

	return Tcl_PkgProvide(interp,"Decoder","0.2");
}

EXPORT(int, Decoder_SafeInit)(Tcl_Interp *interp)
{
	return Decoder_Init(interp);
}

