/*  Rendition.c           by Martin Boyd 25-Apr-93.
 *
 *  An example file saver using the OpalVision LSI interface.
 *
 *  The rendition file format simply consists of a 1024 byte
 * header (Described below) followed by 32 bit (RGBA) image data
 * compressed using run-length encoding. Each run is encoded as
 * 6 bytes:
 *
 *	Byte 0 - Red
 *	Byte 1 - Green
 *	Byte 2 - Blue
 *	Byte 3 - Alpha
 *	Byte 4 - Run length low byte
 *	Byte 5 - Run length high byte
 *
 * NOTE: This saver only saves the 6RN format as described
 *      above.
 */

#include <proto/exec.h>
#include <proto/dos.h>
#include <opal/opallib.h>
#include <opal/loadsave.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <exec/memory.h>
#include <exec/types.h>
#include <exec/ports.h>
#include <exec/nodes.h>
#include <stdlib.h>
#include <string.h>


char Version[] = "\0$VER: Rendition.Saver 1.0 (25.04.93)";

	/* Rendition format information */

struct Tile
	{ long Ptr;
	  long Length;
	};

struct RendHeader		/* 1024 byte header		  */
	{ long	Magic;		/* H_MAGIC			  */ 
	  SHORT	Version;	/* 2				  */
	  char	Label[128];	/* ASCII Desciption		  */
	  char	Date[26];	/* Date of image creation	  */
	  SHORT	Height;		/* Pixel height of image	  */
	  SHORT	Width;		/* Pixel width of image		  */
	  SHORT	TileHeight;	/* Pixel height of tile		  */
	  SHORT	TileWidth;	/* Pixel width of tile		  */
	  SHORT	Format;		/* See below			  */
	  SHORT	Encoding;	/* See below			  */
	  long	NumRuns;	/* Number of runs		  */
	  SHORT	BlockSize;	/* 1024				  */
	  SHORT XOffset;	/* Horizontal offset		  */
	  SHORT YOffset;	/* Vertical offset		  */
	  long CMapLen;		/* Number of colour table entries */
	  long CMapPtr;		/* Pointer to ColourMap		  */
	  char CMapName[64];	/* Colour Map filename		  */
	  struct Tile Tile[64];	/* Tile pointer table		  */
	  char UnSpec[258];	/* Unspecified (reserved)	  */
	};


#define H_MAGIC		0xE880
#define H_BLOCKSIZE	1024
#define H_HEADERSIZE	1024

	/* Format types */
#define H_CRGBAPIX	2	/* 4 bytes per pixel	*/
#define H_6RUNLH	11	/* 6 bytes per run	*/
#define H_2RUN		12	/* 2 bytes per run; colour map in seperate file */
#define H_2RUNMAP	13	/* 2 bytes per run; colour map in image file */
#define H_COLOURMAP	20	/* 4 bytes per entry	*/
#define H_MIPMAP	22	/* Mip map: header, map table and 4-byte pixels */
#define H_ONEDRGBA8	23	/* 1D table of 4-byte pixels */
#define H_ONEDBYTE	24	/* 1D table of bytes */


extern struct DosLibrary *DOSBase;
struct OpalBase *OpalBase;
struct MsgPort *SavePort;
int Width,Height;
char *FileName;
ULONG SaveFlags;
struct RendHeader RendHeader;


BOOL Do_Save (void);

char SaverName[] = "Rendition";
char PortName[] = "Rendition_Saver";

char Comment[] = "Written by OpalVision Rendition Saver v1.0";


void main (void)
{
   struct LSIMessage *LSIMesg;

	SavePort = CreatePort (PortName,0);
	if (SavePort==NULL)
		exit (10);
	OpalBase = (struct OpalBase *)OpenLibrary ("opal.library",0);
	if (OpalBase==NULL)
		{ DeletePort (SavePort);
		  exit (10);
		}


	if (!AddOVSaver (SaverName,SavePort,
			OVSF_ALLOWIMAGE|OVSF_ALLOWALPHA|OVSF_NEEDFILENAME))
		{ DeletePort (SavePort);
		  CloseLibrary ((struct Library *)OpalBase);
		  exit (10);
		}
	CloseLibrary ((struct Library *)OpalBase);	/* library must be closed */
	OpalBase = NULL;

	while (1)
		{ WaitPort (SavePort);
		  while (LSIMesg = (struct LSIMessage *)GetMsg (SavePort))
			{ switch (LSIMesg->lsi_Type)
				{ case OVCMD_SAVEIMAGE:
					Width = LSIMesg->lsi_Width;
					Height = LSIMesg->lsi_Height;
					FileName = LSIMesg->lsi_FileName;
					SaveFlags = LSIMesg->lsi_Flags;
					ReplyMsg ((struct Message *)LSIMesg);
					Do_Save();
					break;
				  case OVCMD_EXPUNGE:
					if (!OpalBase)
						OpalBase = (struct OpalBase *)OpenLibrary ("opal.library",0);
					RemOVSaver (SaverName);
					if (OpalBase)
						CloseLibrary ((struct Library *)OpalBase);
					DeletePort (SavePort);
					ReplyMsg ((struct Message *)LSIMesg);
					exit (0);
					break;
				  case OVCMD_PARAMETERS:
					/* No parameters */
					break;
				}
			}
		}
}


/* I'm not sure if the format allows run length compression
 * beyond the end of a line, so to be safe I end the compression
 * at the end of each scan line.
 */


BOOL Do_Save (void)
{
   int x,y;
   struct MsgPort *MasterPort;
   struct LSIMessage LSIMesg;
   BPTR File;
   UBYTE *SrcBuff,*DestBuff;
   register UBYTE *D,*R,*G,*B,*A,RData,GData,BData,AData;
   long SBuffSize,DBuffSize,BuffRows,RowsLeft,Size,RunLength;
   struct DateTime DT;


		/* Get a handle on the Host port */
	MasterPort = FindPort (OVSAVERPORT);
	if (MasterPort==NULL) return (FALSE);

		/* Allocate buffers for compression */
	BuffRows = 20;
	SrcBuff = NULL;
	DestBuff = NULL;
	do
		{ SBuffSize = BuffRows * Width*4;
		  DBuffSize = BuffRows * Width*6;
		  SrcBuff = AllocMem (SBuffSize,MEMF_CLEAR);
		  if (SrcBuff!=NULL)
			{ DestBuff = AllocMem (DBuffSize,MEMF_CLEAR);
			  if (DestBuff==NULL)
				{ FreeMem (SrcBuff,SBuffSize);
				  SrcBuff=NULL;
				}
			}
		  if (SrcBuff==NULL) BuffRows--;
		} while ((SrcBuff==NULL) && (DestBuff==NULL) && (BuffRows>0));

	if (BuffRows==0)
		{ LSIMesg.lsi_Type = OVCMD_ERROR;
		  LSIMesg.lsi_Result = LSI_ERR_OUTOFMEM;
		  LSICmd (MasterPort,SavePort,&LSIMesg);
		  return (FALSE);
		}

	memset (&LSIMesg,0,sizeof(struct LSIMessage));
	LSIMesg.lsi_Node.mn_Node.ln_Type = NT_MESSAGE;
	LSIMesg.lsi_Node.mn_ReplyPort = SavePort;
	LSIMesg.lsi_Node.mn_Length = sizeof (struct LSIMessage);

	File = Open (FileName,MODE_NEWFILE);
	if (File==NULL)
		{ FreeMem (SrcBuff,SBuffSize);
		  FreeMem (DestBuff,DBuffSize);
		  LSIMesg.lsi_Type = OVCMD_ERROR;
		  LSIMesg.lsi_Result = LSI_ERR_OPENFILE;
		  LSICmd (MasterPort,SavePort,&LSIMesg);
		  return (FALSE);
		}

		/* Initialise File Header */
	memset (&RendHeader,0,sizeof(struct RendHeader));
	RendHeader.Magic = H_MAGIC;
	RendHeader.Version = 2;
	strcpy (RendHeader.Label,Comment);
	if (DOSBase->dl_lib.lib_Version>=36)
		{ DateStamp (&DT.dat_Stamp);
		  DT.dat_Format = FORMAT_DOS;
		  DT.dat_Flags = 0;
		  DT.dat_StrDay = NULL;
		  DT.dat_StrDate = RendHeader.Date;
		  DT.dat_StrTime = &RendHeader.Date[10];
		  DateToStr (&DT);
		  RendHeader.Date[9] = ' ';
		}
	RendHeader.Width = Width;
	RendHeader.Height = Height;
	RendHeader.Format = H_6RUNLH;
	Size = sizeof (struct RendHeader);

		/* Write the header */
	if (Write (File,&RendHeader,Size)!=Size)
		{ FreeMem (SrcBuff,SBuffSize);
		  FreeMem (DestBuff,DBuffSize);
		  Close (File);
		  LSIMesg.lsi_Type = OVCMD_ERROR;
		  LSIMesg.lsi_Result = LSI_ERR_FILEWRITE;
		  LSICmd (MasterPort,SavePort,&LSIMesg);
		  return (FALSE);
		}

		/* Compress and Write the body */
	RowsLeft=0;
	D = DestBuff;

	for (y=0; y<Height; y++)
		{ if (RowsLeft==0)
			{ if (y>0)
				{ Size = D-DestBuff;
				  if (Write (File,DestBuff,Size)!=Size)
					{ FreeMem (SrcBuff,SBuffSize);
					  FreeMem (DestBuff,DBuffSize);
					  Close (File);
					  LSIMesg.lsi_Type = OVCMD_ERROR;
					  LSIMesg.lsi_Result = LSI_ERR_FILEWRITE;
					  LSICmd (MasterPort,SavePort,&LSIMesg);
					  return (FALSE);
					}
				}
				/* Get some image data (in RGB) */
			  D = DestBuff;
			  R = SrcBuff;
			  G = SrcBuff + BuffRows*Width;
			  B = SrcBuff + (2*BuffRows*Width);
			  A = SrcBuff + (3*BuffRows*Width);
			  LSIMesg.lsi_Width = Width;
			  LSIMesg.lsi_Height = BuffRows;
			  LSIMesg.lsi_X = 0;
			  LSIMesg.lsi_Y = y;
			  LSIMesg.lsi_Type = OVCMD_GETDATA;
			  LSIMesg.lsi_SubType = OVDF_RGB;
			  LSIMesg.lsi_Planes[0] = R;
			  LSIMesg.lsi_Planes[1] = G;
			  LSIMesg.lsi_Planes[2] = B;
			  LSICmd (MasterPort,SavePort,&LSIMesg);

				/* If image has an Alpha chanel, get alpha
				 * data, else set Alpha to 0xFF
				 */

			  if (SaveFlags & LSIF_ALPHA)
				{ LSIMesg.lsi_SubType = OVDF_ALPHA;
				  LSIMesg.lsi_Planes[0] = A;
				  LSICmd (MasterPort,SavePort,&LSIMesg);
				}
			  else
				{ for (x=BuffRows*Width; x>0; x--)
					*A++ = 0xFF;
				  A = SrcBuff + (3*BuffRows*Width);
				}
			  RowsLeft = BuffRows;
			}
		  RData = *R++;
		  GData = *G++;
		  BData = *B++;
		  AData = *A++;
		  RunLength = 1;
		  for (x=1; x<Width; x++)
			{ if ((*R==RData) && (*G==GData) && (*B==BData) &&
				(*A==AData))
				{ RunLength++;
				  R++; G++; B++; A++;
				}
			  else
				{ *D++ = RData;
				  *D++ = GData;
				  *D++ = BData;
				  *D++ = AData;
				  *D++ = RunLength & 0xFF;
				  *D++ = (RunLength>>8) & 0xFF;
				  RData = *R++;
				  GData = *G++;
				  BData = *B++;
				  AData = *A++;
				  RunLength = 1;
				}
			}
		  *D++ = RData;
		  *D++ = GData;
		  *D++ = BData;
		  *D++ = AData;
		  *D++ = RunLength & 0xFF;
		  *D++ = (RunLength>>8) & 0xFF;

		  if ((y & 0xF)==0)
			{ LSIMesg.lsi_Type = OVCMD_PERCENTAGE;
			  LSIMesg.lsi_Address = "Rend ";
			  LSIMesg.lsi_Result = (y*100)/Height;
			  LSICmd (MasterPort,SavePort,&LSIMesg);
			}
		  RowsLeft--;
		}

		/* Write out whatever's left */
	Size = D-DestBuff;
	if (Size>0)
		Write (File,DestBuff,Size);

	LSIMesg.lsi_Type = OVCMD_DONE;
	LSICmd (MasterPort,SavePort,&LSIMesg);
	Close (File);
	FreeMem (SrcBuff,SBuffSize);
	FreeMem (DestBuff,DBuffSize);
	return (TRUE);
}



