/**************************************************************************
 *{@C
 *	Copyright:	1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *	Module:		FileFormats
 *	Filename:	FF_ImageSgi.c
 *
 *	Author:		Paul Obermeier
 *
 *	Description:	The file format dependent layer of the image file
 *			format library: Functions to read and write
 *			files in Silicon Graphics' RGB pixel file format.
 *			The following SGI image types are supported:
 *
 *			    Uncompressed RGB image
 *			    Run-length encoded RGB image
 *
 *			Both types may have a transparency channel.
 *
 *	Additional documentation:
 *			None.
 *
 *	Exported functions:
 *			FF_ImgOpenWriteSgi
 *			FF_ImgOpenReadSgi
 *
 **************************************************************************/

#include <stdio.h>
#include <string.h>

#include "UT_Compat.h"
#include "UT_Const.h"
#include "UT_Macros.h"
#include "UT_Error.h"
#include "UT_Memory.h"
#include "UT_Portable.h"

#include "FF_Image.h"
#include "FF_ImagePrivate.h"
#include "FF_ImageSgi.h"

static UInt16 
	*chnbuf = NULL;		/* Buffer for one channel of a scanline. */


/* Close a pixel file previously opened with
   FF_ImgOpenWriteSgi or FF_ImgOpenReadSgi. */

static void sgi_close (FF_ImgFileDesc *px_f)
{
    sgifile *sgif;
    sgif = ((pixfile *)px_f->pixprivate)->fmtprivate;
    sgiClose (sgif->image);
    if (chnbuf)
	UT_MemFree (chnbuf);
    if (sgif)
	UT_MemFree (sgif);
    return;
}

/* Try to read information stored in the "name" field of the IMAGE header. */

static UT_Bool read_id_data (const char *buf, FF_ImgFileDesc *px_f)
{
    Int16 magic;

    if ((magic = UT_PortByteToInt16 ((const Int8 *)(buf + 0))) != SGI_int_magic)
	return UT_False;
    (void)magic;
    px_f->desc.gamma	= UT_PortByteToFloat32 ((const Int8 *)(buf +  2));
    px_f->desc.aspect	= UT_PortByteToFloat32 ((const Int8 *)(buf +  6));
    px_f->desc.red.x	= UT_PortByteToFloat32 ((const Int8 *)(buf + 10));
    px_f->desc.red.y	= UT_PortByteToFloat32 ((const Int8 *)(buf + 14));
    px_f->desc.green.x	= UT_PortByteToFloat32 ((const Int8 *)(buf + 18));
    px_f->desc.green.y	= UT_PortByteToFloat32 ((const Int8 *)(buf + 22));
    px_f->desc.blue.x	= UT_PortByteToFloat32 ((const Int8 *)(buf + 26));
    px_f->desc.blue.y	= UT_PortByteToFloat32 ((const Int8 *)(buf + 30));
    px_f->desc.white.x	= UT_PortByteToFloat32 ((const Int8 *)(buf + 34));
    px_f->desc.white.y	= UT_PortByteToFloat32 ((const Int8 *)(buf + 38));
    return UT_True;
}

/* Write the information into the name field of the IMAGE header. */

static void write_id_data (char *buf, FF_ImgFileDesc *px_f)
{
    UT_PortInt16ToByte (SGI_int_magic, 		(Int8 *)(buf +  0));
    UT_PortFloat32ToByte (px_f->desc.gamma,	(Int8 *)(buf +  2));
    UT_PortFloat32ToByte (px_f->desc.aspect,	(Int8 *)(buf +  6));
    UT_PortFloat32ToByte (px_f->desc.red.x,	(Int8 *)(buf + 10));
    UT_PortFloat32ToByte (px_f->desc.red.y,	(Int8 *)(buf + 14));
    UT_PortFloat32ToByte (px_f->desc.green.x,	(Int8 *)(buf + 18));
    UT_PortFloat32ToByte (px_f->desc.green.y,	(Int8 *)(buf + 22));
    UT_PortFloat32ToByte (px_f->desc.blue.x,	(Int8 *)(buf + 26));
    UT_PortFloat32ToByte (px_f->desc.blue.y,	(Int8 *)(buf + 30));
    UT_PortFloat32ToByte (px_f->desc.white.x,	(Int8 *)(buf + 34));
    UT_PortFloat32ToByte (px_f->desc.white.y,	(Int8 *)(buf + 38));
    return;
}

static UT_Bool read_header (sgifile *sgif, FF_ImgFileDesc *px_f)
{
    Int32 chn;
    FF_ImgFmtType   chanType;
    FF_ImgComprType comprType;

    if (read_id_data (sgif->image->name, px_f)) {
	strcpy (px_f->version, str_int_version);
    } else {
	px_f->desc.red.x = 0.670f;
	px_f->desc.red.y = 0.330f;
	px_f->desc.green.x = 0.210f;
	px_f->desc.green.y = 0.710f;
	px_f->desc.blue.x = 0.140f;
	px_f->desc.blue.y = 0.080f;
	px_f->desc.white.x = 0.313f;
	px_f->desc.white.y = 0.329f;
	px_f->desc.aspect = (Float32)sgif->image->xsize /
			    (Float32)sgif->image->ysize;
	px_f->desc.gamma  = 2.0;
	strcpy (px_f->version, str_ext_version);
    }

    px_f->scanorder = FF_ImgOrderTypeBottomUp;
    px_f->desc.width = sgif->image->xsize;
    px_f->desc.height = sgif->image->ysize;

    for (chn=0; chn<FF_NumImgChanTypes; chn++) {
        px_f->channel[chn] = FF_ImgFmtTypeNone;
        px_f->compression[chn] = FF_ImgComprTypeNone;
    }

    chanType  = BPP (sgif->image->type) == 1?
                     FF_ImgFmtTypeUByte: FF_ImgFmtTypeFloat;
    comprType = ISRLE (sgif->image->type)? 
                       FF_ImgComprTypeRle: FF_ImgComprTypeNone;

    switch (sgif->image->zsize) {
	case 1: {
            px_f->channel[FF_ImgChanTypeGray]     = chanType;
            px_f->compression[FF_ImgChanTypeGray] = comprType;
	    break;
        }
	case 2: {
            px_f->channel[FF_ImgChanTypeGray]      = chanType;
            px_f->compression[FF_ImgChanTypeGray]  = comprType;
            px_f->channel[FF_ImgChanTypeMatte]     = chanType;
            px_f->compression[FF_ImgChanTypeMatte] = comprType;
	    break;
        }
	case 3: {
            px_f->channel[FF_ImgChanTypeRed]       = chanType;
            px_f->compression[FF_ImgChanTypeRed]   = comprType;
            px_f->channel[FF_ImgChanTypeGreen]     = chanType;
            px_f->compression[FF_ImgChanTypeGreen] = comprType;
            px_f->channel[FF_ImgChanTypeBlue]      = chanType;
            px_f->compression[FF_ImgChanTypeBlue]  = comprType;
	    break;
        }
	case 4: {
            px_f->channel[FF_ImgChanTypeRed]       = chanType;
            px_f->compression[FF_ImgChanTypeRed]   = comprType;
            px_f->channel[FF_ImgChanTypeGreen]     = chanType;
            px_f->compression[FF_ImgChanTypeGreen] = comprType;
            px_f->channel[FF_ImgChanTypeBlue]      = chanType;
            px_f->compression[FF_ImgChanTypeBlue]  = comprType;
            px_f->channel[FF_ImgChanTypeMatte]     = chanType;
            px_f->compression[FF_ImgChanTypeMatte] = comprType;
	    break;
        }
	default: {
	    UT_ErrFatal ("sgi_read_scan", "Illegal number of channels");
	    return UT_False;
        }
    }
    return UT_True;
}

static UT_Bool write_channel (const FF_ImgFileDesc *px_f, FF_ImgChanType chantype, 
			   Int32 sgichn, Int32 y, Int32 n)
{
    UInt16 *dest = chnbuf;
    pixfile *pf;
    sgifile *sgif;

    pf = px_f->pixprivate;
    sgif = pf->fmtprivate;

    switch (px_f->channel[chantype]) {
	case FF_ImgFmtTypeNone: {
	    break;
	}
	case FF_ImgFmtTypeUByte: {
	    UInt8 *src = pf->buf[chantype].ubytes;
	    UInt8 *stop = src + n;
	    while (src < stop)
		*(dest++) = *(src++);
	    break;
	}
	case FF_ImgFmtTypeFloat: {
	    Float32 *src = pf->buf[chantype].floats;
	    Float32 *stop = src + n;
	    while (src < stop) {
		*dest = (UInt16) UT_MIN (MaxUInt16, UT_MAX (0, (*src * MaxUInt16 + 0.5)));
		src++;
		dest++;
	    }
	    break;
	}
    }
    if (-1 == sgiPutrow (sgif->image, chnbuf, y, sgichn)) {
	UT_ErrSetNum (UT_ErrFromOs (), "write");
	return UT_False;
    }
    return UT_True;
}

/* All channels of scan line number y for pixel file "file" are stored
   in the file. Note that the TARGA file format doesn't support the
   FF_ImgOrderTypeRandom scanline order. 
   So we write TARGA files in FF_ImgOrderTypeBottomUp order. */

static UT_Bool sgi_write_scan (FF_ImgFileDesc *px_f, Int32 y)
{
    pixfile *pf;
    sgifile *sgif;

    pf = px_f->pixprivate;
    sgif = pf->fmtprivate;

    /* Check if the parameters supplied are reasonable. */
    if (pf->readfile)
	UT_ErrFatal ("sgi_write_scan", "pixel file is read-only");

    /* Copy the data to their final destination. */
    if (!write_channel (px_f, FF_ImgChanTypeRed,   0, y, px_f->desc.width) ||
    	!write_channel (px_f, FF_ImgChanTypeGreen, 1, y, px_f->desc.width) ||
    	!write_channel (px_f, FF_ImgChanTypeBlue,  2, y, px_f->desc.width))
	return UT_False;

    if (sgif->image->zsize > 3)
	if (!write_channel (px_f, FF_ImgChanTypeMatte, 3, y, px_f->desc.width))
	    return UT_False;

    return UT_True;
}

static UT_Bool read_channel (const FF_ImgFileDesc *px_f, FF_ImgChanType chantype, 
			  Int32 sgichn, Int32 y, Int32 n)
{
    UInt16 *src = chnbuf;
    UInt16 *stop = src + n;
    pixfile *pf;
    sgifile *sgif;

    pf = px_f->pixprivate;
    sgif = pf->fmtprivate;

    if (-1 == sgiGetrow (sgif->image, chnbuf, y, sgichn))
    {
	UT_ErrSetNum (UT_ErrFromOs (), "read");
	return UT_False;
    }

    switch (px_f->channel[chantype])
    {
	case FF_ImgFmtTypeNone: {
	    break;
	}
	case FF_ImgFmtTypeUByte:
	{
	    UInt8 *dest = pf->buf[chantype].ubytes;
	    while (src < stop)
		*(dest++) = *(src++);
	    break;
	}
	case FF_ImgFmtTypeFloat:
	{
	    Float32 *dest = pf->buf[chantype].floats;
	    while (src < stop)
		*(dest++) = (Float32)(UInt32)*(src++) * (1.0 / MaxUInt16);
	    break;
	}
    }
    return UT_True;
}

/* The channels of scan line number "y" of the pixel file "px_f" are read.
   Note that the SGI file format only supports FF_ImgOrderTypeBottomUp 
   scanline order. */

static UT_Bool sgi_read_scan (FF_ImgFileDesc *px_f, Int32 y)
{
    pixfile *pf;
    sgifile *sgif;
    Int32 nchan;

    pf = px_f->pixprivate;
    sgif = pf->fmtprivate;
    nchan = sgif->image->zsize;

    /* Check if the parameters supplied are reasonable. */
    if (!pf->readfile)
	UT_ErrFatal ("sgi_read_scan", "pixel file is write-only");

    /* Copy the data to their final destination. */
    switch (nchan) {
	case 1:
	    if (!read_channel (px_f, FF_ImgChanTypeGray, 0, y, px_f->desc.width))
		return UT_False;
	    break;
	case 2:
	    if (!read_channel (px_f, FF_ImgChanTypeGray, 0, y, px_f->desc.width) ||
		!read_channel (px_f, FF_ImgChanTypeMatte,  1, y, px_f->desc.width))
		return UT_False;
	    break;
	case 3:
	    if (!read_channel (px_f, FF_ImgChanTypeRed,   0, y, px_f->desc.width) ||
		!read_channel (px_f, FF_ImgChanTypeGreen, 1, y, px_f->desc.width) ||
		!read_channel (px_f, FF_ImgChanTypeBlue,  2, y, px_f->desc.width))
		return UT_False;
	    break;
	case 4:
	    if (!read_channel (px_f, FF_ImgChanTypeRed,   0, y, px_f->desc.width) ||
		!read_channel (px_f, FF_ImgChanTypeGreen, 1, y, px_f->desc.width) ||
		!read_channel (px_f, FF_ImgChanTypeBlue,  2, y, px_f->desc.width) ||
		!read_channel (px_f, FF_ImgChanTypeMatte, 3, y, px_f->desc.width))
		return UT_False;
	    break;
	default:
	    UT_ErrFatal ("sgi_read_scan", "Illegal number of channels");
	    return UT_False;
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *	Name:		FF_ImgOpenWriteSgi
 *
 *	Usage:		Open a SGI pixel file for writing.
 *
 *	Synopsis:	UT_Bool FF_ImgOpenWriteSgi
 *				(const char name[], FF_ImgFileDesc *px_f)
 *
 *	Description:	SGI pixel file "name" is opened for writing.
 *
 *			Only FF_ImgChanTypeRed, FF_ImgChanTypeGreen,
 *			FF_ImgChanTypeBlue and FF_ImgChanTypeMatte channel
 *			types are supported.
 *			The only data compression method supported is
 *			run-length encoding.
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also:	FF_ImgOpenReadSgi
 *
 ***************************************************************************/

UT_Bool FF_ImgOpenWriteSgi (const char name[], FF_ImgFileDesc *px_f)
{
    pixfile *pf;
    sgifile *sgif;
    UT_MemState memstate;
    UT_Bool matte, nocmp;
    UInt32 bpp = 1;	/* Bytes per pixel */

    memstate = UT_MemRemember ();
    /* Allocate memory for a "sgifile". */
    if (!(sgif = UT_MemTemp (sgifile)) ||
    	!(chnbuf = UT_MemTempArray (px_f->desc.width, UInt16)))
    {
	UT_MemRestore (memstate);
	return UT_False;
    }

    pf = px_f->pixprivate;
    pf->fmtprivate = sgif;
    /* Open the file for writing. */

    if (px_f->channel[FF_ImgChanTypeMatte] != FF_ImgFmtTypeNone) {
	matte = UT_True;
    } else {
	matte = UT_False;
    }

    if ((px_f->compression[FF_ImgChanTypeRed] == FF_ImgComprTypeNone) ||
        (px_f->compression[FF_ImgChanTypeGreen] == FF_ImgComprTypeNone) ||
        (px_f->compression[FF_ImgChanTypeBlue] == FF_ImgComprTypeNone)) {
	nocmp = UT_True;
    } else {
	nocmp = UT_False;
    }

    if ((px_f->channel[FF_ImgChanTypeRed]   == FF_ImgFmtTypeFloat) ||
        (px_f->channel[FF_ImgChanTypeGreen] == FF_ImgFmtTypeFloat) ||
        (px_f->channel[FF_ImgChanTypeBlue]  == FF_ImgFmtTypeFloat) || 
        (matte && px_f->channel[FF_ImgChanTypeMatte] == FF_ImgFmtTypeFloat)) {
	bpp = 2;
    }
    if (!(sgif->image = sgiOpen (name, "wb",
    			         nocmp? VERBATIM(bpp): RLE(bpp), 
			         matte? 4: 3, 
			         px_f->desc.width, px_f->desc.height, 
			         matte? 4: 3)))
    {
	UT_ErrSetNum (UT_ErrFromOs(), str_open_write, name);
	return UT_False;
    }
    if (!strcmp (px_f->version, str_int_version))
	write_id_data (sgif->image->name, px_f);
    pf->writescan = sgi_write_scan;
    pf->close = sgi_close;
    px_f->scanorder = FF_ImgOrderTypeBottomUp;
    return UT_True;
}

/***************************************************************************
 *[@e
 *	Name:		FF_ImgOpenReadSgi
 *
 *	Usage:		Open a SGI pixel file for reading.
 *
 *	Synopsis:	UT_Bool FF_ImgOpenReadSgi
 *				(const char *name,
 *				FF_ImgFileDesc *px_f)
 *
 *	Description:	SGI pixel file "name" is opened for reading.
 *
 *			Only FF_ImgChanTypeRed, FF_ImgChanTypeGreen,
 *			FF_ImgChanTypeBlue and FF_ImgChanTypeMatte channel
 *			types are supported.
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also:	FF_ImgOpenWriteSgi
 *
 ***************************************************************************/

UT_Bool FF_ImgOpenReadSgi (const char *name, FF_ImgFileDesc *px_f)
{
    pixfile *pf;
    sgifile *sgif;
    UT_MemState memstate;

    memstate = UT_MemRemember ();
    pf = px_f->pixprivate;
    if (!(sgif = UT_MemTemp (sgifile))) {
	UT_MemRestore (memstate);
	return UT_False;
    }

    /* Open the file for reading. */
    if (!(sgif->image = sgiOpen (name, "rb", 0, 0, 0, 0, 0))) {
	UT_MemRestore (memstate);
        UT_ErrSetNum (FF_ImgErrFormat, str_open_read, name);
	return UT_False;
    }

    if (!read_header (sgif, px_f)) {
	UT_MemRestore (memstate);
	sgiClose (sgif->image);
	return UT_False;
    }

    if (!(chnbuf = UT_MemTempArray (px_f->desc.width, UInt16))) {
	UT_MemRestore (memstate);
	sgiClose (sgif->image);
	return UT_False;
    }

    /* Fill in the "pixfile". */
    pf->fmtprivate = sgif;
    pf->readscan = sgi_read_scan;
    pf->close = sgi_close;

    UT_MemSave (memstate);
    return UT_True;
}
