/*
**	SaveWindow.c
**
**	Support routines for saving IFF-ILBM files
**
**	Copyright © 1990-1996 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	:ts=4
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

	/* Current compression mode. */

#define DUMP		0
#define RUN			1

	/* ByteRun compression data. */

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

	/* Local packer data. */

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

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

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

	*Destination++ = Count - 1;

	PackedBytes += Count + 1;

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

	return(Destination);
}

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

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

	PackedBytes += 2;

	return(Destination);
}

	/* PackRow(PLANEPTR *SourcePtr,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
PackRow(PLANEPTR *SourcePtr,PLANEPTR Destination,LONG RowSize)
{
	PLANEPTR Source;
	LONG Buffered,RunStart;
	LONG Mode,LastChar,Char;

	Source		= *SourcePtr;
	Buffered	= 1,
	RunStart	= 0;
	Mode		= DUMP;

	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 BOOL
PutANNO(struct IFFHandle *Handle)
{
	LONG Len;
	LONG Error;

	Len = strlen(&VersTag[1]);

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

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

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

			if(!(Error = PopChunk(Handle)))
				return(TRUE);
		}
		else
			Error = IoErr();
	}

	SetIoErr(Error);

	return(FALSE);
}

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

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

		/* Get the display aspect ratio. */

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

			/* Fill in the bitmap header. */

		Header.bmh_Width		= Width;
		Header.bmh_Height		= Height;
		Header.bmh_PageWidth	= Window->WScreen->Width;
		Header.bmh_PageHeight	= Window->WScreen->Height;
		Header.bmh_Left			= Left;
		Header.bmh_Top			= Top;
		Header.bmh_Depth		= GetBitMapDepth(Window->WScreen->RastPort.BitMap);
		Header.bmh_Masking		= mskNone;
		Header.bmh_Compression	= Compression;
		Header.bmh_Pad			= 0;
		Header.bmh_Transparent	= 0;
		Header.bmh_XAspect		= DisplayInfo.Resolution.x;
		Header.bmh_YAspect		= DisplayInfo.Resolution.y;

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

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

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

				if(!(Error = PopChunk(Handle)))
					return(TRUE);
			}
			else
				Error = IoErr();
		}
	}
	else
		Error = ERROR_NO_FREE_STORE;

	if(Error)
		SetIoErr(Error);

	return(FALSE);
}

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

STATIC BOOL
PutCMAP(struct IFFHandle *Handle,struct ViewPort *VPort)
{
	LONG Error;

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

	if(!(Error = PushChunk(Handle,0,ID_CMAP,3 * VPort->ColorMap->Count)))
	{
		struct ColorRegister Colour;
		LONG i;

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


		for(i = 0 ; i < VPort->ColorMap->Count ; i++)
		{
			if(Kick30)
			{
				ULONG RGB[3];

					/* Read colour value. */

				GetRGB32(VPort->ColorMap,i,1,RGB);

					/* Store the colour components. */

				Colour.red		= RGB[0] >> 24;
				Colour.green	= RGB[1] >> 24;
				Colour.blue		= RGB[2] >> 24;
			}
			else
			{
				ULONG Value;

					/* Read colour value. */

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

					/* Split the value into components and store them. */

				Colour.red		= ((Value >> 8) & 0xF) * 0x11;
				Colour.green	= ((Value >> 4) & 0xF) * 0x11;
				Colour.blue		= ((Value     ) & 0xF) * 0x11;
			}

				/* Write the colours. */

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

			/* Pop the `CMAP' chunk. */

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

	SetIoErr(Error);

	return(FALSE);
}

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

STATIC BOOL
PutCAMG(struct IFFHandle *Handle,struct ViewPort *VPort)
{
	LONG Error;

	if(!(Error = PushChunk(Handle,0,ID_CAMG,sizeof(ULONG))))
	{
		ULONG ViewModes = GetVPModeID(VPort);

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

	if(Error)
		SetIoErr(Error);

	return(FALSE);
}

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

STATIC BOOL
PutBODY(struct IFFHandle *Handle,struct BitMap *BitMap,UBYTE Compression)
{
	PLANEPTR *Planes;
	BOOL Success;
	LONG Error;

	Success = FALSE;

		/* Allocate temporary bitplane pointers. */

	if(Planes = (PLANEPTR *)AllocVecPooled(BitMap->Depth * sizeof(PLANEPTR *),MEMF_ANY | MEMF_CLEAR))
	{
		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)AllocVecPooled(BitMap->BytesPerRow * 2,MEMF_ANY))
			{
					/* Push the `BODY' chunk on the stack. */

				if(!(Error = PushChunk(Handle,0,ID_BODY,IFFSIZE_UNKNOWN)))
				{
					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)
							{
								Error = IoErr();

								Success = FALSE;
							}
						}
					}

						/* Pop the `BODY' chunk. */

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

					/* Free the line compression buffer. */

				FreeVecPooled(PackBuffer);
			}
			else
				Error = ERROR_NO_FREE_STORE;
		}
		else
		{
				/* Push the `BODY' chunk on the stack. */

			if(!(Error = PushChunk(Handle,0,ID_BODY,IFFSIZE_UNKNOWN)))
			{
				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)
						{
							Error = IoErr();

							Success = FALSE;
						}
						else
							Planes[j] += BitMap->BytesPerRow;
					}
				}

					/* Pop the `BODY' chunk. */

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

			/* Free the temporary bitplane pointers. */

		FreeVecPooled(Planes);
	}
	else
		Error = ERROR_NO_FREE_STORE;

	if(Error)
		SetIoErr(Error);

		/* Return the result. */

	return(Success);
}

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

BOOL
SaveWindow(STRPTR Name,struct Window *Window)
{
	BOOL NewFile;
	LONG Error;

	NewFile	= FALSE,
	Error	= 0;

		/* Datatypes available? */

	if(DataTypesBase)
	{
		struct BitMap *BitMap;
		struct Screen *Screen;
		LONG Left,Top,Width,Height;
		Object *Picture;

		Screen = Window->WScreen;
		Picture = NULL;

			/* Keep these handy */

		Left	= Window->LeftEdge	+ Window->BorderLeft;
		Top		= Window->TopEdge	+ Window->BorderTop;
		Width	= Window->Width		- (Window->BorderLeft + Window->BorderRight);
		Height	= Window->Height	- (Window->BorderTop + Window->BorderBottom);

		if(!StatusWindow)
			Height -= StatusDisplayHeight;

			/* This is where the window contents will go */

		if(BitMap = AllocBitMap(Width,Height,GetBitMapAttr(Window->RPort->BitMap,BMA_DEPTH),NULL,Window->RPort->BitMap))
		{
			struct RastPort RPort;
			LONG NumColours;

				/* Provide temporary storage */

			InitRastPort(&RPort);

			RPort.BitMap = BitMap;

				/* Copy the window contents */

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

				/* Wait until all data is transferred */

			WaitBlit();

				/* Get the number of colours available on this screen */

			NumColours = Screen->ViewPort.ColorMap->Count;

				/* Create a picture object */

			if(Picture = NewDTObject("term image",
				DTA_SourceType,	DTST_RAM,
				DTA_GroupID,	GID_PICTURE,
				PDTA_NumColors,	NumColours,
				PDTA_BitMap,	BitMap,
				PDTA_ModeID,	GetVPModeID(&Screen->ViewPort),
			TAG_DONE))
			{
				struct ColorRegister *ColourMap;
				struct BitMapHeader *Header;
				ULONG *Colours;

					/* Get the internal data arrays */

				if(GetDTAttrs(Picture,
					PDTA_BitMapHeader,		&Header,
					PDTA_ColorRegisters,	&ColourMap,
					PDTA_CRegs,				&Colours,
				TAG_DONE) == 3)
				{
					LONG i;

						/* Fill in the bitmap header */

					Header->bmh_Left		= Left;
					Header->bmh_Top			= Top;
					Header->bmh_Width		= Width;
					Header->bmh_Height		= Height;
					Header->bmh_Depth		= GetBitMapAttr(BitMap,BMA_DEPTH);
					Header->bmh_PageWidth	= Screen->Width;
					Header->bmh_PageHeight	= Screen->Height;

						/* Get the 32 colours */

					GetRGB32(Screen->ViewPort.ColorMap,0,NumColours,Colours);

						/* Get the 24 bit colours */

					for(i = 0 ; i < NumColours ; i++)
					{
						ColourMap[i].red	= (UBYTE)(Colours[i * 3 + 0] >> 24);
						ColourMap[i].green	= (UBYTE)(Colours[i * 3 + 1] >> 24);
						ColourMap[i].blue	= (UBYTE)(Colours[i * 3 + 2] >> 24);
					}
				}
				else
				{
					Error = IoErr();

					DisposeDTObject(Picture);

					Picture = NULL;
				}
			}
			else
				Error = IoErr();
		}
		else
			Error = ERROR_NO_FREE_STORE;

			/* Successful so far? */

		if(Picture)
		{
			BPTR FileHandle;

				/* Open the output file */

			if(FileHandle = Open(Name,MODE_NEWFILE))
			{
					/* Save the image */

				if(!DoMethod(Picture,DTM_WRITE,NULL,FileHandle,DTWM_IFF,NULL))
				{
					Error = IoErr();

					NewFile = TRUE;
				}

				Close(FileHandle);
			}
			else
				Error = IoErr();

			DisposeDTObject(Picture);
		}
		else
		{
			if(BitMap)
				FreeBitMap(BitMap);
		}
	}
	else
	{
		struct RastPort *RPort;
		LONG Depth;

			/* Get the depth. */

		Depth = GetBitMapDepth(Window->WScreen->RastPort.BitMap);

		if(Depth > 8)
		{
			SetIoErr(ERROR_OBJECT_WRONG_TYPE);

			return(FALSE);
		}

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

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

				/* Initialize the rastport with defaults. */

			InitRastPort(RPort);

				/* Allocate a bitmap. */

			if(BitMap = (struct BitMap *)AllocVecPooled(sizeof(struct BitMap),MEMF_ANY))
			{
				LONG i,Left,Top,Width,Height;

				Width	= Window->Width		- (Window->BorderLeft + Window->BorderRight);
				Height	= Window->Height	- (Window->BorderTop + Window->BorderBottom);
				Left	= Window->LeftEdge	+ Window->BorderLeft;
				Top		= Window->TopEdge	+ Window->BorderTop;

				if(!StatusWindow)
					Height -= StatusDisplayHeight;

					/* Put it into the rastport. */

				RPort->BitMap = BitMap;

					/* Initialize it with the window dimensions. */

				InitBitMap(BitMap,Depth,Width,Height);

					/* Allocate all the bitplanes necessary. */

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

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

				if(!Error)
				{
					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);

						/* Wait for the data to arrive. */

					WaitBlit();

						/* Allocate an iff handle. */

					if(Handle = OpenIFFStream(Name,MODE_NEWFILE))
					{
						NewFile = TRUE;

							/* Push parent chunk on the
							 * stack.
							 */

						if(!(Error = 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.
													 */

												Error = PopChunk(Handle);
											}
											else
												Error = IoErr();
										}
										else
											Error = IoErr();
									}
									else
										Error = IoErr();
								}
								else
									Error = IoErr();
							}
							else
								Error = IoErr();
						}

							/* Free the iff handle. */

						CloseIFFStream(Handle);
					}
					else
						Error = IoErr();
				}
				else
					Error = ERROR_NO_FREE_STORE;

					/* Free all bitplanes. */

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

					/* Free the bitmap. */

				FreeVecPooled(BitMap);
			}
			else
				Error = ERROR_NO_FREE_STORE;

				/* Free the rastport. */

			FreeVecPooled(RPort);
		}
		else
			Error = ERROR_NO_FREE_STORE;
	}

		/* Did we get an error? */

	if(Error)
	{
		if(NewFile)
			DeleteFile(Name);

		SetIoErr(Error);

		return(FALSE);
	}
	else
	{
			/* If successful, clear the `executable' bit. */

		AddProtection(Name,FIBF_EXECUTE);

		if(Config->MiscConfig->CreateIcons)
			AddIcon(CaptureName,FILETYPE_PICTURE,TRUE);

		return(TRUE);
	}
}
