/*
**	termSaveWindow.c
**
**	Support routines for saving IFF-ILBM files
**
**	Copyright © 1990-1994 by Olaf `Olsen' Barthel
**		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 __far	VersTag[];
		WORD		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 BYTE __regargs
PutBMHD(struct IFFHandle *Handle,struct Window *Window,UBYTE Compression,LONG Left,LONG Top,LONG Width,LONG Height)
{
	struct DisplayInfo	DisplayInfo;
	LONG			Error = 0;

		/* 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(!(Error = PushChunk(Handle,0,ID_BMHD,sizeof(BitMapHeader))))
		{
				/* Write the bitmap header. */

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

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

	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 BYTE __regargs
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)))
	{
		ColorRegister	Colour;
		LONG		i;
		ULONG		Value,
				R,G,B;

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

		if(Kick30)
		{
			ULONG RGB[3];

			for(i = 0 ; i < VPort -> ColorMap -> Count ; i++)
			{
					/* 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;
	
					/* Write the colours. */
	
				if(WriteChunkBytes(Handle,&Colour,3) != 3)
					return(FALSE);
			}
		}
		else
		{
			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(!(Error = PopChunk(Handle)))
			return(TRUE);
	}

	SetIoErr(Error);

	return(FALSE);
}

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

STATIC BYTE __regargs
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 BYTE __regargs
PutBODY(struct IFFHandle *Handle,struct BitMap *BitMap,UBYTE Compression)
{
	BYTE		 Success = FALSE;
	PLANEPTR	*Planes;
	LONG		 Error = 0;

		/* Allocate temporary bitplane pointers. */

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

				if(!(Error = 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)
							{
								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 = ERR_NO_MEM;
		}
		else
		{
				/* Push the `BODY' chunk on the stack. */

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

	if(Error)
		SetIoErr(Error);

		/* Return the result. */

	return(Success);
}

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

BYTE __regargs
SaveWindow(STRPTR Name,struct Window *Window)
{
	BYTE	Success	= FALSE,
		NewFile	= FALSE,
		Locked	= TRUE;
	LONG	Error	= 0;

	LockLayerRom(Window -> RPort -> Layer);

		// Datatypes available?

	if(DataTypesBase)
	{
		struct BitMap	*BitMap;
		struct Screen	*Screen = Window -> WScreen;
		LONG		 Left,Top,
				 Width,Height;
		Object		*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);

			// 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 __aligned	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);

				// Release the layer

			UnlockLayerRom(Window -> RPort -> Layer);

			Locked = FALSE;

				// 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	*BitMapHeader;
				ULONG			*Colours;

					// Get the internal data arrays

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

						// Fill in the bitmap header

					BitMapHeader -> bmh_Left	= Left;
					BitMapHeader -> bmh_Top		= Top;
					BitMapHeader -> bmh_Width	= Width;
					BitMapHeader -> bmh_Height	= Height;
					BitMapHeader -> bmh_Depth	= GetBitMapAttr(BitMap,BMA_DEPTH);
					BitMapHeader -> bmh_PageWidth	= Screen -> Width;
					BitMapHeader -> 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))
					Success = TRUE;
				else
				{
					Error = IoErr();

					NewFile = TRUE;
				}

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

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

			/* 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	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);

						/* Wait for the data to arrive. */

					WaitBlit();

					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(!(Error = OpenIFF(Handle,IFFF_WRITE)))
							{
									/* 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.
															 */

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

									/* Close the iff handle. */

								CloseIFF(Handle);
							}

								/* Close the file. */

							if(!Close(Handle -> iff_Stream))
							{
								Error = IoErr();

								Success = FALSE;
							}
						}
						else
							Error = IoErr();

							/* Free the iff handle. */

						FreeIFF(Handle);
					}
					else
						Error = ERR_NO_MEM;
				}
				else
					Error = ERR_NO_MEM;

					/* 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 = ERR_NO_MEM;

				/* Free the rastport. */

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

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

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

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

	if(Success)
	{
		AddProtection(Name,FIBF_EXECUTE);

		if(Config -> MiscConfig -> CreateIcons)
			AddIcon(CaptureName,FILETYPE_PICTURE,TRUE);
	}
	else
	{
			/* Delete the remains of the file. */

		if(NewFile)
			DeleteFile(Name);
	}

		/* Return the result. */

	return(Success);
}
