/*
**	MandelSquare - AmigaDOS 2.0/3.0 Mandelbrot set explorer
**
**	ILBM.c, Routines to load and save IFF-ILBM files
**
**	Copyright © 1991-1992 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

	/* A picture handle. */

struct PictureInfo
{
	BitMapHeader		BitMapHeader;
	MandelHeader		MandelHeader;
	struct Palette *	Palette;	
	ULONG			ViewModes;
	struct BitMap *		BitMap;
};

	/* Global flag. */

extern BYTE		Is39;

	/* In Packer.c */

extern LONG __regargs	PackRow(BYTE **pSource,BYTE *dest,LONG rowSize);
extern VOID __regargs	UnPackRow(BYTE **pSource,BYTE **pDest,WORD dstBytes);

	/* In Palette.c */

extern VOID		FreePalette(struct Palette *Palette);
extern struct Palette *	AllocPalette(LONG NumColours,BYTE TrueColour);
extern struct Palette *	GetPalette(struct Screen *Screen,struct Palette *Palette);
extern VOID		LoadPalette(struct Screen *Screen,struct Palette *Palette,LONG NumColours);
extern LONG		GetPaletteSize(struct Palette *Palette);
extern BYTE		GetPaletteTriplet(struct Palette *Palette,UBYTE *Triplet,LONG Index);
extern BYTE		SetPaletteTriplet(struct Palette *Palette,UBYTE R,UBYTE G,UBYTE B,LONG Index);
extern ULONG		GetPaletteEntry(struct Palette *Palette,LONG Index);
extern BYTE		SetPaletteEntry(struct Palette *Palette,ULONG Entry,LONG Index);

	/* Local, static routines. */

STATIC VOID __regargs	GetBody(struct BitMap *BitMap,APTR Body);
STATIC BYTE __regargs	GetColourMap(struct PictureInfo *Info,UBYTE *Palette,LONG Size);
STATIC BYTE __regargs	PutMandel(struct IFFHandle *Handle,MandelHeader *Mandel);
STATIC BYTE __regargs	PutCRange(struct IFFHandle *Handle,CRange *Range);

	/* Exported routines. */

VOID			FreeCustomBitMap(struct BitMap *BitMap);
struct BitMap *		AllocCustomBitMap(UWORD Depth,UWORD Width,UWORD Height);
BYTE			PutBitMap(struct IFFHandle *Handle,struct BitMap *BitMap);
BYTE			PutViewModes(struct IFFHandle *Handle,struct ViewPort *VPort);
BYTE			PutColourMap(struct IFFHandle *Handle,struct ViewPort *VPort);
BYTE			PutBitMapHeader(struct IFFHandle *Handle,struct Screen *Screen);
VOID			FreePicture(struct PictureInfo *Info);
struct PictureInfo *	LoadPicture(STRPTR Name);
BYTE			SavePicture(STRPTR Name,struct Screen *Screen,MandelHeader *Mandel);

	/* GetBody(struct BitMap *BitMap,APTR Body):
	 *
	 *	Unpack compressed data.
	 */

STATIC VOID __regargs
GetBody(struct BitMap *BitMap,APTR Body)
{
	PLANEPTR	 Planes[8];
	BYTE		*Source;
	LONG		 i,j;

		/* Get the source data pointer, we will modify it. */

	Source = (BYTE *)Body;

		/* Copy the bitplane pointers. */

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

		/* CmpByteRun decompression. */

	for(i = 0 ; i < BitMap -> Rows ; i++)
	{
		for(j = 0 ; j < BitMap -> Depth ; j++)
			UnPackRow(&Source,&Planes[j],BitMap -> BytesPerRow);
	}
}

	/* GetColourMap(struct PictureInfo *Info,UBYTE *Palette,LONG Size):
	 *
	 *	Fill in colour map information.
	 */

STATIC BYTE __regargs
GetColourMap(struct PictureInfo *Info,UBYTE *Palette,LONG Size)
{
		/* Get the number of colours. */

	LONG NumColours = Size / 3;

		/* Allocate palette information. */

	if(Info -> Palette = AllocPalette(NumColours,NumColours == 256))
	{
		UWORD	R,G,B;
		LONG	i;

			/* Set the 8 bit triplets. */

		if(NumColours == 256)
		{
			for(i = 0 ; i < NumColours ; i++)
			{
				R = *Palette++;
				G = *Palette++;
				B = *Palette++;

				SetPaletteTriplet(Info -> Palette,R,G,B,i);
			}
		}
		else
		{
				/* Set the 4 bit triplets. */

			for(i = 0 ; i < NumColours ; i++)
			{
				R = ((*Palette++) >> 4) & 0xF;
				G = ((*Palette++) >> 4) & 0xF;
				B = ((*Palette++) >> 4) & 0xF;

				SetPaletteTriplet(Info -> Palette,R,G,B,i);
			}
		}

		return(TRUE);
	}
	else
		return(FALSE);
}

	/* PutMandel(struct IFFHandle *Handle,MandelHeader *Mandel):
	 *
	 *	Save the mandelbrot information chunk.
	 */

STATIC BYTE __regargs
PutMandel(struct IFFHandle *Handle,MandelHeader *Mandel)
{
	if(!PushChunk(Handle,0,ID_MAND,sizeof(MandelHeader)))
	{
		if(WriteChunkRecords(Handle,Mandel,sizeof(MandelHeader),1) == 1)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutCRange(struct IFFHandle *Handle,CRange *Range):
	 *
	 *	Save the colour cycling chunk.
	 */

STATIC BYTE __regargs
PutCRange(struct IFFHandle *Handle,CRange *Range)
{
	if(!PushChunk(Handle,0,ID_CRNG,sizeof(CRange)))
	{
		if(WriteChunkRecords(Handle,Range,sizeof(CRange),1) == 1)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* FreeCustomBitMap(struct BitMap *BitMap):
	 *
	 *	Free bitmap memory.
	 */

VOID
FreeCustomBitMap(struct BitMap *BitMap)
{
	if(Is39)
		FreeBitMap(BitMap);
	else
	{
		if(BitMap)
		{
			WORD i;

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

			FreeVec(BitMap);
		}
	}
}

	/* AllocCustomBitMap(UWORD Depth,UWORD Width,UWORD Height):
	 *
	 *	Allocate bitmap memory.
	 */

struct BitMap *
AllocCustomBitMap(UWORD Depth,UWORD Width,UWORD Height)
{
	if(Is39)
	{
		extern struct Screen *Screen;

			/* Allocate a suitable bitmap. */

		return(AllocBitMap(Width,Height,Depth,BMF_CLEAR|BMF_DISPLAYABLE,Screen -> RastPort . BitMap));
	}
	else
	{
		struct BitMap *BitMap;

			/* Allocate the bitmap body. */

		if(BitMap = (struct BitMap *)AllocVec(sizeof(struct BitMap),MEMF_ANY|MEMF_CLEAR))
		{
			BYTE	Success = TRUE;
			LONG	PlaneSize,
				i;

				/* Initialize the bitmap. */

			InitBitMap(BitMap,Depth,Width,Height);

				/* Get the plane size. */

			PlaneSize = BitMap -> BytesPerRow * BitMap -> Rows;

				/* Allocate the single bitplanes. */

			for(i = 0 ; Success && i < Depth ; i++)
			{
				if(!(BitMap -> Planes[i] = (PLANEPTR)AllocVec(PlaneSize,MEMF_CHIP|MEMF_CLEAR)))
					Success = FALSE;
			}

				/* Return the result. */

			if(Success)
				return(BitMap);
			else
				FreeCustomBitMap(BitMap);
		}
	}

	return(NULL);
}

	/* PutBitMap(struct IFFHandle *Handle,struct BitMap *BitMap):
	 *
	 *	Save a bitmap to disk.
	 */

BYTE
PutBitMap(struct IFFHandle *Handle,struct BitMap *BitMap)
{
	PLANEPTR	*Planes;
	BYTE		*PackBuffer,
			 Success = FALSE;
	LONG		 PackedBytes,
			 i,j;

		/* Allocate the bitplane information. */

	if(Planes = (PLANEPTR *)AllocVec(BitMap -> Depth * sizeof(PLANEPTR *),MEMF_ANY | MEMF_CLEAR))
	{
			/* Allocate the compression buffer. */

		if(PackBuffer = (BYTE *)AllocVec(BitMap -> BytesPerRow * 2,MEMF_ANY))
		{
				/* Copy the planes over. */

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

			if(!PushChunk(Handle,0,ID_BODY,IFFSIZE_UNKNOWN))
			{
				Success = TRUE;

					/* Run down the rows. */

				for(i = 0 ; Success && i < BitMap -> Rows ; i++)
				{
					for(j = 0 ; Success && j < BitMap -> Depth ; j++)
					{
							/* Pack the data. */

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

							/* Write it to disk. */

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

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

			FreeVec(PackBuffer);
		}

		FreeVec(Planes);
	}

	return(Success);
}

	/* PutViewModes(struct IFFHandle *Handle,struct ViewPort *VPort):
	 *
	 *	Save the display modes to disk.
	 */

BYTE
PutViewModes(struct IFFHandle *Handle,struct ViewPort *VPort)
{
	ULONG ViewModes = GetVPModeID(VPort);

	if(!PushChunk(Handle,0,ID_CAMG,sizeof(ULONG)))
	{
		if(WriteChunkRecords(Handle,&ViewModes,sizeof(ULONG),1) == 1)
		{
			if(!PopChunk(Handle))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* PutColourMap(struct IFFHandle *Handle,struct ViewPort *VPort):
	 *
	 *	Save the colour map to disk.
	 */

BYTE
PutColourMap(struct IFFHandle *Handle,struct ViewPort *VPort)
{
	ColorRegister	 Colour;
	LONG		 i;

	if(!PushChunk(Handle,0,ID_CMAP,3 * VPort -> ColorMap -> Count))
	{
			/* Eight or four bit triplets? */

		if(VPort -> ColorMap -> Count == 256)
		{
			ULONG Triplet[3];

				/* Run down the colour map... */

			for(i = 0 ; i < VPort -> ColorMap -> Count ; i++)
			{
					/* Get the colour information, returned
					 * as three 32 bit quantities.
					 */

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

					/* As of this writing, only eight
					 * bit quantities are supported for
					 * CMAP chunks.
					 */

				Colour . red	= Triplet[0] >> 24;
				Colour . green	= Triplet[1] >> 24;
				Colour . blue	= Triplet[2] >> 24;

					/* Write the data. */

				if(WriteChunkRecords(Handle,&Colour,3,1) != 1)
					return(FALSE);
			}
		}
		else
		{
			ULONG	Value,
				R,G,B;

				/* Run down the colour map... */

			for(i = 0 ; i < VPort -> ColorMap -> Count ; i++)
			{
					/* Get the colour map entry. */

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

					/* Split the colour information. */

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

					/* Fill it in. */

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

					/* Write the data. */

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

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

	return(FALSE);
}

	/* PutBitMapHeader(struct IFFHandle *Handle,struct Screen *Screen):
	 *
	 *	Save the bitmap header to disk.
	 */

BYTE
PutBitMapHeader(struct IFFHandle *Handle,struct Screen *Screen)
{
	BitMapHeader		Header;
	struct DisplayInfo	DisplayInfo;

		/* Fill in the standard data. */

	Header . w			= Screen -> Width;
	Header . h			= Screen -> Height;
	Header . pageWidth		= Screen -> Width;
	Header . pageHeight		= Screen -> Height;
	Header . x			= 0;
	Header . y			= 0;
	Header . nPlanes		= Screen -> RastPort . BitMap -> Depth;
	Header . masking		= 0;
	Header . compression		= 1;
	Header . pad1			= 0;
	Header . transparentColor	= 0;

		/* Get the aspect ration information. */

	if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,GetVPModeID(&Screen -> ViewPort)))
	{
			/* Fill it in. */

		Header . xAspect = DisplayInfo . Resolution . x;
		Header . yAspect = DisplayInfo . Resolution . y;

			/* Write the data. */

		if(!PushChunk(Handle,0,ID_BMHD,sizeof(BitMapHeader)))
		{
			if(WriteChunkRecords(Handle,&Header,sizeof(BitMapHeader),1) == 1)
			{
				if(!PopChunk(Handle))
					return(TRUE);
			}
		}
	}

	return(FALSE);
}

	/* FreePicture(struct PictureInfo *Info):
	 *
	 *	Free picture information.
	 */

VOID
FreePicture(struct PictureInfo *Info)
{
	if(Info)
	{
			/* Free the bitmap. */

		if(Info -> BitMap)
			FreeCustomBitMap(Info -> BitMap);

			/* Free the palette. */

		if(Info -> Palette)
			FreePalette(Info -> Palette);

			/* Free the information buffer. */

		FreeVec(Info);
	}
}

	/* LoadPicture(STRPTR Name):
	 *
	 *	Load an IFF-ILBM image file.
	 */

struct PictureInfo *
LoadPicture(STRPTR Name)
{
		/* The information we wil pick up on the fly. */

	STATIC ULONG Properties[2 * 4] =
	{
		ID_ILBM,ID_BMHD,
		ID_ILBM,ID_CAMG,
		ID_ILBM,ID_CMAP,
		ID_ILBM,ID_MAND
	};

	struct PictureInfo *Info;

		/* Allocate the information buffer. */

	if(Info = (struct PictureInfo *)AllocVec(sizeof(struct PictureInfo),MEMF_ANY | MEMF_CLEAR))
	{
		struct IFFHandle	*Handle;
		BYTE			 Success = FALSE;

			/* Allocate iff handle. */

		if(Handle = AllocIFF())
		{
				/* Open file for reading. */

			if(Handle -> iff_Stream = Open(Name,MODE_OLDFILE))
			{
					/* Make it known as a DOS file. */

				InitIFFasDOS(Handle);

				if(!OpenIFF(Handle,IFFF_READ))
				{
						/* Remember the properties to store. */

					if(!PropChunks(Handle,Properties,4))
					{
							/* Stop at the BODY chunk. */

						if(!StopChunk(Handle,ID_ILBM,ID_BODY))
						{
							struct StoredProperty *Prop;

								/* Scan for it... */

							if(!ParseIFF(Handle,IFFPARSE_SCAN))
							{
								Success = TRUE;

									/* Try to find the bitmap header. */

								if(Prop = FindProp(Handle,ID_ILBM,ID_BMHD))
								{
									CopyMem(Prop -> sp_Data,&Info -> BitMapHeader,sizeof(BitMapHeader));

										/* No weird data, please. */

									if(Info -> BitMapHeader . nPlanes < 1 || Info -> BitMapHeader . nPlanes > 8 || Info -> BitMapHeader . w != Info -> BitMapHeader . pageWidth || Info -> BitMapHeader . h != Info -> BitMapHeader . pageHeight)
										Success = FALSE;

										/* No masking and CmpByteRun compression, please. */

									if(Info -> BitMapHeader . masking || Info -> BitMapHeader . compression != 1)
										Success = FALSE;
								}
								else
									Success = FALSE;

									/* Can we continue? */

								if(Success)
								{
										/* Check for CAMG chunk... */

									if(Prop = FindProp(Handle,ID_ILBM,ID_CAMG))
									{
										Info -> ViewModes = *(ULONG *)Prop -> sp_Data;

											/* Mask out the old 1.3 bits... */

										if(!(Info -> ViewModes & MONITOR_ID_MASK) || ((Info -> ViewModes & EXTENDED_MODE) && !(Info -> ViewModes & 0xFFFF0000)))
											Info -> ViewModes &= ~(EXTENDED_MODE | SPRITES | VP_HIDE | GENLOCK_AUDIO | GENLOCK_VIDEO);

											/* Can we get along with it? */

										if(!(Info -> ViewModes & EXTENDED_MODE) && (Info -> ViewModes & 0xFFFF0000))
											Prop = NULL;
										else
										{
												/* Is the display mode available? */

											if(ModeNotAvailable(Info -> ViewModes))
											{
													/* Mask out the valid bits. */

												Info -> ViewModes &= (LACE | HIRES | HAM | EXTRA_HALFBRITE);

													/* Any chance? */

												if(ModeNotAvailable(Info -> ViewModes))
													Success = FALSE;
											}
										}
									}

										/* No chance so far, try to make up the
										 * data from scratch.
										 */

									if(!Prop && Success)
									{
										extern struct GfxBase *GfxBase;

										Info -> ViewModes = NULL;

										if(Info -> BitMapHeader . pageHeight > ((GfxBase -> DisplayFlags & PAL) ? 256 : 200))
											Info -> ViewModes |= LACE;

										if(Info -> BitMapHeader . pageWidth >= 640)
											Info -> ViewModes |= HIRES;

										if(Info -> BitMapHeader . nPlanes == 6)
											Info -> ViewModes |= HAM;
									}

										/* Try to pick up the colour map. */

									if(Success)
									{
										if(Prop = FindProp(Handle,ID_ILBM,ID_CMAP))
										{
											if(!GetColourMap(Info,(UBYTE *)Prop -> sp_Data,Prop -> sp_Size))
												Success = FALSE;
										}
									}

										/* We _need_ the mandelbrot data. */

									if(Success)
									{
										if(Prop = FindProp(Handle,ID_ILBM,ID_MAND))
											CopyMem(Prop -> sp_Data,&Info -> MandelHeader,sizeof(MandelHeader));
										else
											Success = FALSE;
									}

										/* Successful so far? */

									if(Success)
									{
										struct ContextNode *BodyChunk;

										Success = FALSE;

										if(BodyChunk = CurrentChunk(Handle))
										{
											APTR Body;

												/* Allocate space for the body data. */

											if(Body = AllocVec(BodyChunk -> cn_Size,MEMF_ANY))
											{
													/* Read the data. */

												if(ReadChunkRecords(Handle,Body,BodyChunk -> cn_Size,1) == 1)
												{
														/* Allocate a suitable bitmap. */

													if(Info -> BitMap = AllocCustomBitMap(Info -> BitMapHeader . nPlanes,Info -> BitMapHeader . w,Info -> BitMapHeader . h))
													{
															/* Unpack the data. */

														GetBody(Info -> BitMap,Body);

														Success = TRUE;
													}
												}

												FreeVec(Body);
											}
										}
									}
								}
							}
						}
					}

					CloseIFF(Handle);
				}

				Close(Handle -> iff_Stream);
			}

			FreeIFF(Handle);
		}

		if(!Success)
		{
			FreePicture(Info);

			Info = NULL;
		}
	}

	return(Info);
}

	/* SavePicture(STRPTR Name,struct Screen *Screen,MandelHeader *Mandel):
	 *
	 *	Save the screen to an IFF-ILBM disk file.
	 */

BYTE
SavePicture(STRPTR Name,struct Screen *Screen,MandelHeader *Mandel)
{
	struct BitMap 	*BitMap;
	BYTE		 Success	= FALSE,
			 Delete		= FALSE;

		/* Allocate local bitmap, don't use the plain screen bitmap. */

	if(BitMap = AllocCustomBitMap(Screen -> RastPort . BitMap -> Depth,Screen -> Width,Screen -> Height))
	{
		struct IFFHandle *Handle;

			/* Copy the screen bitmap. */

		BltBitMap(Screen -> RastPort . BitMap,0,0,BitMap,0,0,Screen -> Width,Screen -> Height,0xC0,0xFF,NULL);

			/* Do the vanilla setup. */

		if(Handle = AllocIFF())
		{
			if(Handle -> iff_Stream = Open(Name,MODE_NEWFILE))
			{
				Delete = TRUE;

				InitIFFasDOS(Handle);

				if(!OpenIFF(Handle,IFFF_WRITE))
				{
					if(!PushChunk(Handle,ID_ILBM,ID_FORM,IFFSIZE_UNKNOWN))
					{
							/* Put the bitmap header. */

						if(PutBitMapHeader(Handle,Screen))
						{
								/* Put the colour information. */

							if(PutColourMap(Handle,&Screen -> ViewPort))
							{
									/* Put the display modes. */

								if(PutViewModes(Handle,&Screen -> ViewPort))
								{
									CRange Range;

										/* Initialize the colour cycling chunk. */

									Range . pad1	= 0;
									Range . rate	= 16384 / (60 / 20);
									Range . flags	= RNG_ACTIVE;
									Range . low	= 2;
									Range . high	= Screen -> ViewPort . ColorMap -> Count - 1;

										/* Put the colour cycling chunk. */

									if(PutCRange(Handle,&Range))
									{
											/* Put the mandelbrot information chunk. */

										if(PutMandel(Handle,Mandel))
										{
												/* Put the bitmap data. */

											if(PutBitMap(Handle,BitMap))
											{
													/* We're done now. */

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

					CloseIFF(Handle);
				}

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

			FreeIFF(Handle);
		}

		FreeCustomBitMap(BitMap);
	}

	if(Success)
		SetProtection(Name,FIBF_EXECUTE);
	else
	{
		if(Delete)
			DeleteFile(Name);
	}

	return(Success);
}
