/*
**	termSaveWindow.c
**
**	Support routines for saving IFF-ILBM files
**
**	Copyright © 1990-1993 by Olaf `Olsen' Barthel & MXM
**		All Rights Reserved
*/

#include "termGlobal.h"

	/* Current compression mode. */

#define DUMP		0
#define RUN		1

	/* ByteRun compression data. */

#define MINRUN		3
#define MAXRUN		128
#define MAXDAT		128

	/* Chunk types. */

#define ID_ILBM		MAKE_ID('I','L','B','M')
#define ID_ANNO		MAKE_ID('A','N','N','O')
#define ID_BMHD		MAKE_ID('B','M','H','D')
#define ID_CMAP		MAKE_ID('C','M','A','P')
#define ID_CAMG		MAKE_ID('C','A','M','G')
#define ID_BODY		MAKE_ID('B','O','D','Y')

	/* Masking technique. */

#define mskNone		0

	/* Compression techniques. */

#define cmpNone		0
#define cmpByteRun1	1

	/* A bitmap header. */

typedef struct
{
	UWORD		w,h;			/* raster width & height in pixels */
	WORD		x,y;			/* position for this image */
	UBYTE		nPlanes;		/* # source bitplanes */
	UBYTE		masking;		/* masking technique */
	UBYTE		compression;		/* compression algorithm */
	UBYTE		pad1;			/* UNUSED.  For consistency, put 0 here.*/
	UWORD		transparentColor;	/* transparent "color number" */
	UBYTE		xAspect,yAspect;	/* aspect ratio, a rational number x/y */
	WORD		pageWidth,pageHeight;	/* source "page" size in pixels */
} BitMapHeader;

	/* A single 8-bit colour register. */

typedef struct
{
	UBYTE		red,			/* red component, 0..255 */
			green,			/* green component, 0..255 */
			blue;			/* blue component, 0..255 */
} ColorRegister;

	/* Local packer data. */

STATIC LONG		PackedBytes;
STATIC BYTE		Buffer[MAXDAT + 1];

	/* PutDump(register PLANEPTR Destination,register LONG Count):
	 *
	 *	Output a byte dump.
	 */

STATIC PLANEPTR __regargs
PutDump(register PLANEPTR Destination,register LONG Count)
{
	register PLANEPTR Source = Buffer;

	*Destination++	 = Count - 1;
	 PackedBytes	+= Count + 1;

	while(Count--)
		*Destination++ = *Source++;

	return(Destination);
}

	/* PutRun(register PLANEPTR Destination,LONG Count,WORD Char):
	 *
	 *	Output a byte run.
	 */

STATIC PLANEPTR __regargs
PutRun(register PLANEPTR Destination,LONG Count,WORD Char)
{
	*Destination++	 = -(Count - 1);
	*Destination++	 = Char;
	 PackedBytes	+= 2;

	return(Destination);
}

	/* PackRow(PLANEPTR *SourcePtr,register PLANEPTR Destination,LONG RowSize):
	 *
	 *	Pack a row of bitmap data using ByteRun compression,
	 *	based on the original "EA IFF 85" pack.c example code.
	 */

STATIC LONG __regargs
PackRow(PLANEPTR *SourcePtr,register PLANEPTR Destination,LONG RowSize)
{
	register PLANEPTR Source = *SourcePtr;

	WORD	Buffered	= 1,
		RunStart	= 0;
	BYTE	Mode		= DUMP,
		LastChar,
		Char;

	PackedBytes = 0;

	Buffer[0] = LastChar = Char = *Source++;

	RowSize--;

	while(RowSize--)
	{
		Buffer[Buffered++] = Char = *Source++;

		if(Mode)
		{
			if((Char != LastChar) || (Buffered - RunStart > MAXRUN))
			{
				Destination	= PutRun(Destination,Buffered - 1 - RunStart,LastChar);
				Buffer[0]	= Char;
				Buffered	= 1;
				RunStart	= 0;
				Mode		= DUMP;
			}
		}
		else
		{
			if(Buffered > MAXDAT)
			{
				Destination	= PutDump(Destination,Buffered - 1);
				Buffer[0]	= Char;
				Buffered	= 1;
				RunStart	= 0;
			}
			else
			{
				if(Char == LastChar)
				{
					if(Buffered - RunStart >= MINRUN)
					{
						if(RunStart)
							Destination = PutDump(Destination,RunStart);

						Mode = RUN;
					}
					else
					{
						if(!RunStart)
							Mode = RUN;
					}
				}
				else
					RunStart = Buffered - 1;
			}
		}

		LastChar = Char;
	}

	if(Mode)
		PutRun(Destination,Buffered - RunStart,LastChar);
	else
		PutDump(Destination,Buffered);

	*SourcePtr = Source;

	return(PackedBytes);
}

	/* PutANNO(struct IFFHandle *Handle):
	 *
	 *	Output `ANNO' chunk.
	 */

STATIC BYTE __regargs
PutANNO(struct IFFHandle *Handle)
{
	extern	UBYTE	VersTag[];
		WORD	Len;

	Len = strlen(&VersTag[1]);

		/* Push the `ANNO' chunk on the stack. */

	if(!PushChunk(Handle,0,ID_ANNO,Len))
	{
			/* Write the creator string. */

		if(WriteChunkBytes(Handle,&VersTag[1],Len) == Len)
		{
				/* Pop the `ANNO' chunk. */

			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutBMHD():
	 *
	 *	Output `BMHD' chunk.
	 */

STATIC BYTE __regargs
PutBMHD(struct IFFHandle *Handle,struct Window *Window,UBYTE Compression,LONG Left,LONG Top,LONG Width,LONG Height)
{
	struct DisplayInfo DisplayInfo;

		/* Get the display aspect ratio. */

	if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,GetVPModeID(&Window -> WScreen -> ViewPort)))
	{
		BitMapHeader Header;

			/* Fill in the bitmap header. */

		Header . w			= Width;
		Header . h			= Height;
		Header . pageWidth		= Window -> WScreen -> Width;
		Header . pageHeight		= Window -> WScreen -> Height;
		Header . x			= Left;
		Header . y			= Top;
		Header . nPlanes		= Window -> WScreen -> RastPort . BitMap -> Depth;
		Header . masking		= mskNone;
		Header . compression		= Compression;
		Header . pad1			= 0;
		Header . transparentColor	= 0;
		Header . xAspect		= DisplayInfo . Resolution . x;
		Header . yAspect		= DisplayInfo . Resolution . y;

			/* Push the `BMHD' chunk on the stack. */

		if(!PushChunk(Handle,0,ID_BMHD,sizeof(BitMapHeader)))
		{
				/* Write the bitmap header. */

			if(WriteChunkBytes(Handle,&Header,sizeof(BitMapHeader)) == sizeof(BitMapHeader))
			{
					/* Pop the `BMHD' chunk. */

				if(!PopChunk(Handle))
					return(TRUE);
			}
		}
	}

	return(FALSE);
}

	/* PutCMAP(struct IFFHandle *Handle,struct ViewPort *VPort):
	 *
	 *	Output `CMAP' chunk, only 4-bit colour registers
	 *	are supported so far.
	 */

STATIC BYTE __regargs
PutCMAP(struct IFFHandle *Handle,struct ViewPort *VPort)
{
		/* Push the `CMAP' chunk on the stack. */

	if(!PushChunk(Handle,0,ID_CMAP,3 * VPort -> ColorMap -> Count))
	{
		ColorRegister	Colour;
		LONG		i;
		ULONG		Value,
				R,G,B;

			/* Read and convert all the
			 * ColorMap entries (4 bit colour
			 * components only).
			 */

		for(i = 0 ; i < VPort -> ColorMap -> Count ; i++)
		{
				/* Read colour value. */

			Value = GetRGB4(VPort -> ColorMap,i);

				/* Split the value into components. */

			R = (Value >> 8) & 0xF;
			G = (Value >> 4) & 0xF;
			B = (Value     ) & 0xF;

				/* Store the colour components. */

			Colour . red	= (R << 4) | R;
			Colour . green	= (G << 4) | G;
			Colour . blue	= (B << 4) | B;

				/* Write the colours. */

			if(WriteChunkBytes(Handle,&Colour,3) != 3)
				return(FALSE);
		}

			/* Pop the `CMAP' chunk. */

		if(!PopChunk(Handle))
			return(TRUE);
	}

	return(FALSE);
}

	/* PutCAMG(struct IFFHandle *Handle,struct ViewPort *VPort):
	 *
	 *	Output `CAMG' chunk.
	 */

STATIC BYTE __regargs
PutCAMG(struct IFFHandle *Handle,struct ViewPort *VPort)
{
	if(!PushChunk(Handle,0,ID_CAMG,sizeof(ULONG)))
	{
		ULONG ViewModes = GetVPModeID(VPort);

		if(WriteChunkBytes(Handle,&ViewModes,sizeof(ULONG)) == sizeof(ULONG))
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutBODY(struct IFFHandle *Handle)
	 *
	 *	Output `BODY' chunk.
	 */

STATIC BYTE __regargs
PutBODY(struct IFFHandle *Handle,struct BitMap *BitMap,UBYTE Compression)
{
	BYTE		 Success = FALSE;
	PLANEPTR	*Planes;

		/* Allocate temporary bitplane pointers. */

	if(Planes = (PLANEPTR *)AllocVec(BitMap -> Depth * sizeof(PLANEPTR *),MEMF_ANY | MEMF_CLEAR))
	{
		register LONG i;

			/* Copy the bitplane pointers. */

		for(i = 0 ; i < BitMap -> Depth ; i++)
			Planes[i] = BitMap -> Planes[i];

			/* Are we to compress the data? */

		if(Compression == cmpByteRun1)
		{
			PLANEPTR PackBuffer;

				/* Allocate line compression buffer. */

			if(PackBuffer = (PLANEPTR)AllocVec(BitMap -> BytesPerRow * 2,MEMF_ANY))
			{
					/* Push the `BODY' chunk on the stack. */

				if(!PushChunk(Handle,0,ID_BODY,IFFSIZE_UNKNOWN))
				{
					register LONG	PackedBytes,
							j;

						/* So far, we are quite successful,
						 * any write access to fail will
						 * cause `Success' to drop to FALSE.
						 */

					Success = TRUE;

						/* Compress all the rows. */

					for(i = 0 ; Success && i < BitMap -> Rows ; i++)
					{
							/* Compress all the planes. */

						for(j = 0 ; Success && j < BitMap -> Depth ; j++)
						{
								/* Do the compression. */

							PackedBytes = PackRow(&Planes[j],PackBuffer,BitMap -> BytesPerRow);

								/* Write the compressed data. */

							if(WriteChunkBytes(Handle,PackBuffer,PackedBytes) != PackedBytes)
								Success = FALSE;
						}
					}

						/* Pop the `BODY' chunk. */

					if(PopChunk(Handle))
						Success = FALSE;
				}

					/* Free the line compression buffer. */

				FreeVec(PackBuffer);
			}
		}
		else
		{
				/* Push the `BODY' chunk on the stack. */

			if(!PushChunk(Handle,0,ID_BODY,IFFSIZE_UNKNOWN))
			{
				register LONG j;

					/* So far, we are quite successful,
					 * any write access to fail will
					 * cause `Success' to drop to FALSE.
					 */

				Success = TRUE;

					/* Compress all the rows. */

				for(i = 0 ; Success && i < BitMap -> Rows ; i++)
				{
						/* Compress all the planes. */

					for(j = 0 ; Success && j < BitMap -> Depth ; j++)
					{
							/* Write the row. */

						if(WriteChunkBytes(Handle,Planes[j],BitMap -> BytesPerRow) != BitMap -> BytesPerRow)
							Success = FALSE;
						else
							Planes[j] += BitMap -> BytesPerRow;
					}
				}

					/* Pop the `BODY' chunk. */

				if(PopChunk(Handle))
					Success = FALSE;
			}
		}

			/* Free the temporary bitplane pointers. */

		FreeVec(Planes);
	}

		/* Return the result. */

	return(Success);
}

	/* SaveWindow(STRPTR Name,struct Window *Window):
	 *
	 *	Save the contents of a window to a file.
	 */

BYTE
SaveWindow(STRPTR Name,struct Window *Window)
{
	struct RastPort		*RPort;
	BYTE			 Success	= FALSE,
				 NewFile	= FALSE,
				 Locked		= TRUE;

	LockLayerRom(Window -> RPort -> Layer);

		/* Allocate a dummy rastport, we will need only
		 * to copy the contents of the window into the
		 * bitmap.
		 */

	if(RPort = (struct RastPort *)AllocVec(sizeof(struct RastPort),MEMF_ANY))
	{
		struct BitMap *BitMap;

			/* Initialize the rastport with defaults. */

		InitRastPort(RPort);

			/* Allocate a bitmap. */

		if(BitMap = (struct BitMap *)AllocVec(sizeof(struct BitMap),MEMF_ANY))
		{
			LONG	Width	= Window -> Width	- (Window -> BorderLeft + Window -> BorderRight),
				Height	= Window -> Height	- (Window -> BorderTop + Window -> BorderBottom),
				Left	= Window -> LeftEdge + Window -> BorderLeft,
				Top	= Window -> TopEdge + Window -> BorderTop;
			WORD	i;

				/* Put it into the rastport. */

			RPort -> BitMap = BitMap;

				/* Initialize it with the window dimensions. */

			InitBitMap(BitMap,Window -> WScreen -> RastPort . BitMap -> Depth,Width,Height);

				/* Flag success so any allocation
				 * to fail in the bitplane allocation
				 * loop will indicate failure.
				 */

			Success = TRUE;

				/* Allocate all the bitplanes necessary. */

			for(i = 0 ; Success && i < BitMap -> Depth ; i++)
			{
				if(!(BitMap -> Planes[i] = AllocRaster(Width,Height)))
					Success = FALSE;
			}

				/* Did we get all the planes we wanted? */

			if(Success)
			{
				struct IFFHandle *Handle;

					/* Copy the window contents to the
					 * local bitmap.
					 */

				ClipBlit(Window -> RPort,Window -> BorderLeft,Window -> BorderTop,RPort,0,0,Width,Height,MINTERM_COPY);

					/* Release the lock on the window layer. */

				UnlockLayerRom(Window -> RPort -> Layer);

				Locked = FALSE;

					/* Reset the success indicator. */

				Success = FALSE;

					/* Allocate an iff handle. */

				if(Handle = AllocIFF())
				{
						/* Open a file for write access. */

					if(Handle -> iff_Stream = Open(Name,MODE_NEWFILE))
					{
							/* Remember that we succeeded
							 * in creating a new file.
							 */

						NewFile = TRUE;

							/* Tell iffparse.library that it's
							 * a plain AmigaDOS file handle.
							 */

						InitIFFasDOS(Handle);

							/* Open the file for writing. */

						if(!OpenIFF(Handle,IFFF_WRITE))
						{
								/* Push parent chunk on the
								 * stack.
								 */

							if(!PushChunk(Handle,ID_ILBM,ID_FORM,IFFSIZE_UNKNOWN))
							{
									/* Output all the chunk data. */

								if(PutANNO(Handle))
								{
									UBYTE Compression;

										/* Don't compress the bitmap if
										 * isn't really worth it.
										 */

									if(BitMap -> BytesPerRow > 4)
										Compression = cmpByteRun1;
									else
										Compression = cmpNone;

									if(PutBMHD(Handle,Window,Compression,Left,Top,Width,Height))
									{
										if(PutCMAP(Handle,&Window -> WScreen -> ViewPort))
										{
											if(PutCAMG(Handle,&Window -> WScreen -> ViewPort))
											{
												if(PutBODY(Handle,BitMap,Compression))
												{
														/* Pop the parent chunk
														 * from the stack.
														 */

													if(!PopChunk(Handle))
														Success = TRUE;
												}
											}
										}
									}
								}
							}

								/* Close the iff handle. */

							CloseIFF(Handle);
						}

							/* Close the file. */

						if(!Close(Handle -> iff_Stream))
							Success = FALSE;
					}

						/* Free the iff handle. */

					FreeIFF(Handle);
				}
			}

				/* Free all bitplanes. */

			for(i = 0 ; i < BitMap -> Depth ; i++)
			{
				if(BitMap -> Planes[i])
					FreeRaster(BitMap -> Planes[i],Width,Height);
			}

				/* Free the bitmap. */

			FreeVec(BitMap);
		}

			/* Free the rastport. */

		FreeVec(RPort);
	}

		/* Release the window layer in case it is still locked. */

	if(Locked)
		UnlockLayerRom(Window -> RPort -> Layer);

		/* If successful, clear the `executable' bit. */

	if(Success)
		SetProtection(Name,FIBF_EXECUTE);
	else
	{
			/* Delete the remains of the file. */

		if(NewFile)
			DeleteFile(Name);
	}

		/* Return the result. */

	return(Success);
}
