/**********************************************************************************************************************************/ /* Module : SCR2GIF.C */ /* Executable : SCR2GIF.EXE */ /* Version type : Standalone program */ /* Last changed : 25-05-1998 20:30 */ /* Update count : 2 */ /* OS type : Generic */ /* Description : Spectrum .SCR to compressed GIF87a convertor. */ /* Copyrights : GIF87a functions based on a source from Sverre H. Huseby, Bjoelsengt. 17, N-0468 Oslo, Norway 26-09-1992 */ /* Implementation taken from Workbench! v2.71.3, copyright (C) 1995-1998 ThunderWare Research Center */ /* GIF89a extensions added 25-05-1998, "NETSCAPE v2.0" compliant (web-based default) LOOPing */ /* */ /* The Graphics Interchange Format(c) is the Copyright property of CompuServe Incorporated. */ /* GIF(sm) is a Service Mark property of CompuServe Incorporated. */ /* */ /* Copyright (C) 1998 by M. van der Heide of ThunderWare Research Center */ /**********************************************************************************************************************************/ #include #include #include #include #include #include /**********************************************************************************************************************************/ /* Add some variables types */ /**********************************************************************************************************************************/ typedef char bool; typedef unsigned char byte; typedef unsigned short word; /* (Must be 16-bit) */ typedef unsigned long dword; /* (Must be 32-bit) */ #ifndef TRUE #define TRUE (bool)1 #define FALSE (bool)0 #endif /**********************************************************************************************************************************/ /* Define the GIF return codes */ /**********************************************************************************************************************************/ #define GIF_OK 0x00 /* No errors */ #define GIF_ERRCREATE 0x01 /* Error creating GIF file */ #define GIF_ERRWRITE 0x02 /* Error writing to GIF file */ #define GIF_OUTMEM 0x03 /* Cannot allocate resources */ #define GIF_OUTMEM2 0x04 /* Cannot allocate resources */ /**********************************************************************************************************************************/ /* Define the static variables */ /**********************************************************************************************************************************/ static FILE *_OutFile; /* File to write to */ static char _GIFErrorMessage[50]; /**********************************************************************************************************************************/ /* Define the information to write a bit-file */ /**********************************************************************************************************************************/ static byte _Buffer[256]; static int _Index; /* Current byte in buffer */ static int _BitsLeft; /* Bits left to fill in current byte. These are right-justified */ /**********************************************************************************************************************************/ /* Define the information to maintain an LZW-string table */ /**********************************************************************************************************************************/ #define RES_CODES 2 #define HASH_FREE 0xFFFF #define NEXT_FIRST 0xFFFF #define MAXBITS 12 #define MAXSTR (1 << MAXBITS) #define HASHSIZE 9973 #define HASHSTEP 2039 #define HASH(Index, Lastbyte) (((Lastbyte << 8) ^ Index) % HASHSIZE) static byte *StrChr = NULL; static word *StrNxt = NULL; static word *StrHsh = NULL; static word NumStrings; /**********************************************************************************************************************************/ /* Define the information on GIF images */ /**********************************************************************************************************************************/ typedef struct { word LocalScreenWidth; word LocalScreenHeight; byte GlobalColourTableSize : 3; byte SortFlag : 1; byte ColourResolution : 3; byte GlobalColourTableFlag : 1; byte BackgroundColourIndex; byte PixelAspectRatio; } _ScreenDescriptor; typedef struct { byte Separator; word LeftPosition; word TopPosition; word Width; word Height; byte LocalColourTableSize : 3; byte Reserved : 2; byte SortFlag : 1; byte InterlaceFlag : 1; byte LocalColourTableFlag : 1; } _ImageDescriptor; typedef struct { byte ExtensionIntroducer; byte GraphicControlLabel; byte BlockSize; byte TransparantColorFlag : 1; byte UserInputFlag : 1; byte DisposalMethod : 3; byte Reserved : 3; word DelayTime; /* BIG endian! */ byte TransparantColorIndex; byte BlockTerminator; } _GraphicControlExtension; static byte _ApplicationExtensionHeader[19] = { 0x21, /* GIF Extension Code */ 0xFF, /* Application Extension Label */ 0x0B, /* Length of Application Block */ 'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', /* Application Name */ '2', '.', '0', /* Application ID */ 0x03, /* Length of Data Sub-Block */ 0x01, /* Sub-Block ID */ 0x00, /* Loop Count higher value (big endian) */ 0x00, /* Loop Count lower value (0 = infinite) */ 0x00 /* Data Sub-Block Terminator */ }; static short _BitsPrPrimColour; /* Bits per primary colour */ static short _NumColours; /* Number of colours in colour table */ static byte *_ColourTable = NULL; static word _ScreenHeight; static word _ScreenWidth; static word _ImageHeight; static word _ImageWidth; static word _ImageLeft; static word _ImageTop; static word _RelPixX; static word _RelPixY; /**********************************************************************************************************************************/ /* Define the static functions */ /**********************************************************************************************************************************/ static byte _Create (char *FileName); static byte _Write (void *Buf, word Length); static byte _WriteByte (byte B); static byte _WriteWord (word W); static void _Close (void); static void _InitBitFile (void); static int _ResetOutBitFile (void); static int _WriteBits (short Bits, short NumBits); static void _FreeStrtab (void); static byte _AllocStrtab (void); static word _AddCharString (word Index, byte B); static word _FindCharString (word Index, byte B); static void _ClearStrtab (short CodeSize); static byte _LZW_Compress (short CodeSize, short (*GetPixelFunction)(short PixX, short PixY)); static short _BitsNeeded (word N); static short (*_GetPixel) (short PixX, short PixY); static short _InputByte (void); static byte _WriteScreenDescriptor (_ScreenDescriptor *Sd); static byte _WriteImageDescriptor (_ImageDescriptor *Id); static byte _WriteGraphicControlExtension (_GraphicControlExtension *Ext); static byte _GIFCreate (char *FileName, short Width, short Height, short NumColours, short ColourRes, bool GIF89a); static void _GIFSetColour (byte ColourNum, byte Red, byte Green, byte Blue); static byte _GIFWriteGlobalColorTable (bool GIF89a); static byte _GIFCompressImage (short StartX, short StartY, int Width, int Height, short (*GetPixelFunction)(short PixX, short PixY), bool GIF89a, word PictureDelayTime); static byte _GIFClose (void); static char *_GIFstrerror (byte ErrorCode); /**********************************************************************************************************************************/ /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START OF GENERIC LIBRARY FUNCTIONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */ /**********************************************************************************************************************************/ static byte _Create (char *FileName) /**********************************************************************************************************************************/ /* Pre : `FileName' points to the name of the file to be created. */ /* Post : Creates a new file and enables referencing using the global variable _OutFile. */ /* The return value is GIF_ERRCREATE if the file could not be created, GIF_OK otherwise. */ /* Import: None. */ /**********************************************************************************************************************************/ { return (((_OutFile = fopen (FileName, "wb")) == NULL) ? GIF_ERRCREATE : GIF_OK); } static byte _Write (void *Buf, word Length) /**********************************************************************************************************************************/ /* Pre : `Buf' points to the buffer to be written, `Length' holds the length. */ /* Post : The buffer has been written to the _OutFile. */ /* The return value is GIF_ERRWRITE if a write error occured, GIF_OK otherwise. */ /* Import: None. */ /**********************************************************************************************************************************/ { return ((fwrite (Buf, 1, Length, _OutFile) < Length) ? GIF_ERRWRITE : GIF_OK); } static byte _WriteByte (byte B) /**********************************************************************************************************************************/ /* Pre : `B' holds the byte to be written. */ /* Post : The byte has been written. The return value is GIF_ERRWRITE if an error occured, GIF_OK otherwise. */ /* Import: None. */ /**********************************************************************************************************************************/ { return ((putc (B, _OutFile) == EOF) ? GIF_ERRWRITE : GIF_OK); } static byte _WriteWord (word W) /**********************************************************************************************************************************/ /* Pre : `W' holds the word to be written. */ /* Post : The word has been written BIG-endian. The return value is GIF_ERRWRITE if an error occured, GIF_OK otherwise. */ /* Import: None. */ /**********************************************************************************************************************************/ { if (putc (W & 0xFF, _OutFile) == EOF) return (GIF_ERRWRITE); return ((putc ((W >> 8), _OutFile) == EOF) ? GIF_ERRWRITE : GIF_OK); } static void _Close (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Closes the _OutFile. */ /* Import: None. */ /**********************************************************************************************************************************/ { fclose (_OutFile); } static void _InitBitFile (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Initiate when using a bitfile. */ /* Import: None. */ /**********************************************************************************************************************************/ { _Buffer[_Index = 0] = 0; _BitsLeft = 8; } static int _ResetOutBitFile (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Tidy up after using a bitfile. Returns 0 if no errors occured, -1 otherwise. */ /* Import: _WriteByte, _Write. */ /**********************************************************************************************************************************/ { byte NumBytes; NumBytes = _Index + (_BitsLeft == 8 ? 0 : 1); /* Find out how much is in the buffer */ if (NumBytes) /* Write whatever is in the buffer to the file */ { if (_WriteByte (NumBytes) != GIF_OK) return (-1); if (_Write (_Buffer, NumBytes) != GIF_OK) return (-1); _Buffer[_Index = 0] = 0; _BitsLeft = 8; } return (0); } static int _WriteBits (short Bits, short NumBits) /**********************************************************************************************************************************/ /* Pre : 'Bits' holds the (right justified) bits to be written, `NumBits' holds the number of bits to be written. */ /* Post : The given number of bits are written to the _OutFile. If an error occured, -1 is returned. */ /* Import: _WriteByte, _Write. */ /**********************************************************************************************************************************/ { int register BitsWritten = 0; byte register NumBytes = 255; do /* If the buffer is full, write it */ { if ((_Index == 254 && !_BitsLeft) || _Index > 254) { if (_WriteByte (NumBytes) != GIF_OK) return (-1); if (_Write (_Buffer, NumBytes) != GIF_OK) return (-1); _Buffer[_Index = 0] = 0; _BitsLeft = 8; } if (NumBits <= _BitsLeft) /* Now take care of the two special cases */ { _Buffer[_Index] |= (Bits & ((1 << NumBits) - 1)) << (8 - _BitsLeft); BitsWritten += NumBits; _BitsLeft -= NumBits; NumBits = 0; } else { _Buffer[_Index] |= (Bits & ((1 << _BitsLeft) - 1)) << (8 - _BitsLeft); BitsWritten += _BitsLeft; Bits >>= _BitsLeft; NumBits -= _BitsLeft; _Buffer[++ _Index] = 0; _BitsLeft = 8; } } while (NumBits); return (BitsWritten); } static void _FreeStrtab (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Free arrays used in string table routines. */ /* Import: None. */ /**********************************************************************************************************************************/ { if (StrHsh) { free (StrHsh); StrHsh = NULL; } if (StrNxt) { free (StrNxt); StrNxt = NULL; } if (StrChr) { free (StrChr); StrChr = NULL; } } static byte _AllocStrtab (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Allocate arrays used in string table routines. Returns GIF_OUTMEM or GIF_OK. */ /* Import: _FreeStrtab. */ /**********************************************************************************************************************************/ { _FreeStrtab (); /* Just in case ... */ if ((StrNxt = (word *)malloc (MAXSTR * 2)) == NULL) { _FreeStrtab (); return (GIF_OUTMEM2); } if ((StrChr = (byte *)malloc (MAXSTR)) == NULL) { _FreeStrtab (); return (GIF_OUTMEM2); } if ((StrHsh = (word *)malloc (HASHSIZE * 2)) == NULL) { _FreeStrtab (); return (GIF_OUTMEM2); } return (GIF_OK); } static word _AddCharString (word Index, byte B) /**********************************************************************************************************************************/ /* Pre : `Index' holds the index to the first part of the string, or 0xFFFF is only 1 byte is wanted, `B' holds the last byte */ /* in the new string. */ /* Post : Add a string consisting of the string of Index plus the byte B. The return value is 0xFFFF if room is not available. */ /* Import: None. */ /**********************************************************************************************************************************/ { word register HshIdx; if (NumStrings >= MAXSTR) /* Check if there is more room */ return 0xFFFF; HshIdx = HASH (Index, B); /* Search the string table until a free position is found */ while (StrHsh[HshIdx] != 0xFFFF) HshIdx = (HshIdx + HASHSTEP) % HASHSIZE; StrHsh[HshIdx] = NumStrings; /* Insert new string */ StrChr[NumStrings] = B; StrNxt[NumStrings] = (Index != 0xFFFF ? Index : NEXT_FIRST); return (NumStrings ++); } static word _FindCharString (word Index, byte B) /**********************************************************************************************************************************/ /* Pre : `Index' holds the index to the first part of the string, or 0xFFFF is only 1 byte is wanted, `B' holds the last byte */ /* in the new string. */ /* Post : Find index of string consisting of the string of index plus the byte B. The return value is 0xFFFF if not found. */ /* Import: None. */ /**********************************************************************************************************************************/ { word register HshIdx; word register NxtIdx; if (Index == 0xFFFF) /* Check if Index is 0xFFFF. In that case we need only return B, */ return (B); /* since all one-character strings has their bytevalue as their index */ HshIdx = HASH (Index, B); /* Search the string table until the string is found, or */ while ((NxtIdx = StrHsh[HshIdx]) != 0xFFFF) /* we find HASH_FREE. In that case the string does not exist. */ { if (StrNxt[NxtIdx] == Index && StrChr[NxtIdx] == B) return (NxtIdx); HshIdx = (HshIdx + HASHSTEP) % HASHSIZE; } return (0xFFFF); /* No match is found */ } static void _ClearStrtab (short CodeSize) /**********************************************************************************************************************************/ /* Pre : `CodeSize' holds the number of bits to encode one pixel. */ /* Post : Mark the entire table as free, enter the 2**CodeSize one-byte strings and reserve the RES_CODES reserved codes. */ /* Import: _AddCharString. */ /**********************************************************************************************************************************/ { int register Q; int register W; word register *Wp; NumStrings = 0; /* No strings currently in the table */ Wp = StrHsh; /* Mark entire hashtable as free */ for (Q = 0 ; Q < HASHSIZE ; Q ++) *Wp ++ = HASH_FREE; W = (1 << CodeSize) + RES_CODES; /* Insert 2**CodeSize one-character strings, and reserved codes */ for (Q = 0 ; Q < W ; Q ++) _AddCharString (0xFFFF, (byte)Q); } static byte _LZW_Compress (short CodeSize, short (*GetPixelFunction)(short PixX, short PixY)) /**********************************************************************************************************************************/ /* Pre : `CodeSize' holds the number of bits needed to represent one pixelvalue. */ /* `GetPixelFunction' points to a callback function used to fetch the color value of each pixel of the image. */ /* Post : Perform LZW compression as specified in the GIF87a standard. The return value is one of GIF_OK or GIF_OUTMEM. */ /* Import: _InitBitFile, _ClearStrtab, _WriteBits, _FindCharString, _AddCharString, _ResetOutBitFile, _FreeStrtab. */ /**********************************************************************************************************************************/ { word register Index; short register C; int ClearCode; int EndOfInfo; int NumBits; int Limit; int ErrCode; word Prefix = 0xFFFF; _GetPixel = GetPixelFunction; _InitBitFile (); /* Set up the given _OutFile */ ClearCode = 1 << CodeSize; /* Set up variables and tables */ EndOfInfo = ClearCode + 1; NumBits = CodeSize + 1; Limit = (1 << NumBits) - 1; if ((ErrCode = _AllocStrtab ()) != GIF_OK) return (ErrCode); _ClearStrtab (CodeSize); _WriteBits (ClearCode, NumBits); /* First send a code telling the unpacker to clear the stringtable */ while ((C = _InputByte ()) != -1) { /* Now perform the packing */ if ((Index = _FindCharString (Prefix, (byte)C)) != 0xFFFF) /* Check if the prefix + the new character is a string that */ { /* exists in the table */ Prefix = Index; /* The string exists in the table. Make this string the new prefix */ } else { /* The string does not exist in the table */ _WriteBits (Prefix, NumBits); /* First write code of the old prefix to the file */ if (_AddCharString (Prefix, (byte)C) > Limit) /* Add the new string (the prefix + the new character) to the stringtable */ { if (++ NumBits > 12) { _WriteBits (ClearCode, NumBits - 1); _ClearStrtab (CodeSize); NumBits = CodeSize + 1; } Limit = (1 << NumBits) - 1; } Prefix = C; /* Set prefix to a string containing only the character read. Since all possible one-character */ } /* strings exist in the table, there's no need to check if it is found */ } if (Prefix != 0xFFFF) /* End of info is reached. Write last prefix */ _WriteBits (Prefix, NumBits); _WriteBits (EndOfInfo, NumBits); /* Write end of info-mark */ _ResetOutBitFile (); /* Flush the buffer */ _FreeStrtab (); /* Tidy up */ return (GIF_OK); } static short _BitsNeeded (word N) /**********************************************************************************************************************************/ /* Pre : `N' holds the number of numbers to store (0 to (n - 1)). */ /* Post : Calculates the number of bits needed to store numbers between 0 and (n - 1). This number is returned. */ /* Import: None. */ /**********************************************************************************************************************************/ { short Ret = 1; if (!N --) return (0); while (N >>= 1) Ret ++; return (Ret); } static short _InputByte (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Get next pixel from the image. Called by the _LZW_Compress function. Returns next pixelvalue or -1 at the end. */ /* Import: _GetPixel. */ /**********************************************************************************************************************************/ { short Ret; if (_RelPixY >= _ImageHeight) return (-1); Ret = _GetPixel (_ImageLeft + _RelPixX, _ImageTop + _RelPixY); if (++ _RelPixX >= _ImageWidth) { _RelPixX = 0; _RelPixY ++; } return (Ret); } static byte _WriteScreenDescriptor (_ScreenDescriptor *Sd) /**********************************************************************************************************************************/ /* Pre : `Sd' points to the screen descriptor to output. */ /* Post : Output a screen descriptor to the current GIF-file. Returns GIF_ERRWRITE if an error occured. */ /* Import: _WriteWord, _WriteByte. */ /**********************************************************************************************************************************/ { byte Tmp; if (_WriteWord (Sd->LocalScreenWidth) != GIF_OK) return (GIF_ERRWRITE); if (_WriteWord (Sd->LocalScreenHeight) != GIF_OK) return (GIF_ERRWRITE); Tmp = (Sd->GlobalColourTableFlag << 7) | (Sd->ColourResolution << 4) | (Sd->SortFlag << 3) | Sd->GlobalColourTableSize; if (_WriteByte (Tmp) != GIF_OK) return (GIF_ERRWRITE); if (_WriteByte (Sd->BackgroundColourIndex) != GIF_OK) return (GIF_ERRWRITE); if (_WriteByte (Sd->PixelAspectRatio) != GIF_OK) return (GIF_ERRWRITE); return (GIF_OK); } static byte _WriteImageDescriptor (_ImageDescriptor *Id) /**********************************************************************************************************************************/ /* Pre : `Id' points to the image descriptor to output. */ /* Post : Output an image descriptor to the current GIF-file. Returns GIF_ERRWRITE if an error occured. */ /* Import: _WriteWord, _WriteByte. */ /**********************************************************************************************************************************/ { byte Tmp; if (_WriteByte (Id->Separator) != GIF_OK) return (GIF_ERRWRITE); if (_WriteWord (Id->LeftPosition) != GIF_OK) return (GIF_ERRWRITE); if (_WriteWord (Id->TopPosition) != GIF_OK) return (GIF_ERRWRITE); if (_WriteWord (Id->Width) != GIF_OK) return (GIF_ERRWRITE); if (_WriteWord (Id->Height) != GIF_OK) return (GIF_ERRWRITE); Tmp = (Id->LocalColourTableFlag << 7) | (Id->InterlaceFlag << 6) | (Id->SortFlag << 5) | (Id->Reserved << 3) | Id->LocalColourTableSize; if (_WriteByte (Tmp) != GIF_OK) return (GIF_ERRWRITE); return (GIF_OK); } static byte _WriteGraphicControlExtension (_GraphicControlExtension *Ext) /**********************************************************************************************************************************/ /* Pre : `Ext' points to the graphic control extension block to output. */ /* Post : Output a graphic control extension block to the current GIF-file. Returns GIF_ERRWRITE if an error occured. */ /* Import: _WriteWord, _WriteByte. */ /**********************************************************************************************************************************/ { byte Tmp; if (_WriteByte (Ext->ExtensionIntroducer) != GIF_OK) return (GIF_ERRWRITE); if (_WriteByte (Ext->GraphicControlLabel) != GIF_OK) return (GIF_ERRWRITE); if (_WriteByte (Ext->BlockSize) != GIF_OK) return (GIF_ERRWRITE); Tmp = (Ext->Reserved << 5) | (Ext->DisposalMethod << 2) | (Ext->UserInputFlag << 1) | Ext->TransparantColorFlag; if (_WriteByte (Tmp) != GIF_OK) return (GIF_ERRWRITE); if (_WriteWord (Ext->DelayTime) != GIF_OK) return (GIF_ERRWRITE); if (_WriteByte (Ext->TransparantColorIndex) != GIF_OK) return (GIF_ERRWRITE); if (_WriteByte (Ext->BlockTerminator) != GIF_OK) return (GIF_ERRWRITE); return (GIF_OK); } static byte _GIFCreate (char *FileName, short Width, short Height, short NumColours, short ColourRes, bool GIF89a) /**********************************************************************************************************************************/ /* Pre : `FileName' points to the filename to create, `Width', `Height' hold the dimensions on the screen, `NumColours' holds */ /* the number of colours in the colourmaps, `ColourRes' holds the colour resolution - number of bits for each primary */ /* colour. `GIF89a' is TRUE is the GIF89a header should be written rather than the GIF87a header. */ /* Post : Create a GIF-file and write the GIF and screen header. The return value is one of: */ /* GIF_OK : Ok; */ /* GIF_ERRCREATE : Could not create file; */ /* GIF_ERRWRITE : Error writing to the file; */ /* GIF_OUTMEM : Could not allocate the colour table. */ /* Import: _Create, _Write, _BitsNeeded. */ /**********************************************************************************************************************************/ { int register Q; int TabSize; byte *Bp; _ScreenDescriptor SD; _NumColours = NumColours ? (1 << _BitsNeeded (NumColours)) : 0; /* Initiate variables for new GIF-file */ _BitsPrPrimColour = ColourRes; _ScreenHeight = Height; _ScreenWidth = Width; if (_Create (FileName) != GIF_OK) /* Create file specified */ return (GIF_ERRCREATE); if (GIF89a) { if ((_Write ("GIF89a", 6)) != GIF_OK) /* Write GIF signature */ return (GIF_ERRWRITE); } else if ((_Write ("GIF87a", 6)) != GIF_OK) return (GIF_ERRWRITE); SD.LocalScreenWidth = Width; /* Initiate and write screen descriptor */ SD.LocalScreenHeight = Height; if (_NumColours) { SD.GlobalColourTableSize = _BitsNeeded (_NumColours) - 1; SD.GlobalColourTableFlag = 1; } else { SD.GlobalColourTableSize = 0; SD.GlobalColourTableFlag = 0; } SD.SortFlag = 0; SD.ColourResolution = ColourRes - 1; SD.BackgroundColourIndex = 0; SD.PixelAspectRatio = 0; if (_WriteScreenDescriptor (&SD) != GIF_OK) return (GIF_ERRWRITE); if (_ColourTable) /* Allocate colour table */ { free (_ColourTable); _ColourTable = NULL; } if (_NumColours) { TabSize = _NumColours * 3; if ((_ColourTable = (byte *)malloc (TabSize)) == NULL) return (GIF_OUTMEM); else { Bp = _ColourTable; for (Q = 0 ; Q < TabSize ; Q ++) *Bp ++ = 0; } } return (0); } static void _GIFSetColour (byte ColourNum, byte Red, byte Green, byte Blue) /**********************************************************************************************************************************/ /* Pre : `ColourNum' holds the the colour index to set in range [0, _NumColours - 1], `Red', `Green', `Blue' hold the */ /* components. */ /* Post : Set red, green and blue component of one of the colours. They are all in the range [0, (1 << _BitsPrPrimColour) -1]. */ /* Import: None. */ /**********************************************************************************************************************************/ { dword MaxColour; byte *P; MaxColour = (1L << _BitsPrPrimColour) - 1L; P = _ColourTable + ColourNum * 3; *P ++ = (byte)((Red * 255L) / MaxColour); *P ++ = (byte)((Green * 255L) / MaxColour); *P ++ = (byte)((Blue * 255L) / MaxColour); } static byte _GIFWriteGlobalColorTable (bool GIF89a) /**********************************************************************************************************************************/ /* Pre : `GIF89a' is TRUE if animated GIFs are written. Should be used after each successive _GETSetColor call. */ /* Post : Write the global color table to the GIF file. If animated GIFs are written, insert a LOOP control block as well. */ /* Import: _Write. */ /**********************************************************************************************************************************/ { if (_NumColours) /* Write global colour table if any */ if ((_Write (_ColourTable, _NumColours * 3)) != GIF_OK) return (GIF_ERRWRITE); if (GIF89a) if ((_Write (&_ApplicationExtensionHeader, 19)) != GIF_OK) /* Write Netscape Loop Header */ return (GIF_ERRWRITE); return (0); } static byte _GIFCompressImage (short StartX, short StartY, int Width, int Height, short (*GetPixelFunction)(short PixX, short PixY), bool GIF89a, word PictureDelayTime) /**********************************************************************************************************************************/ /* Pre : `StartX', `StartY' holds the screen-relative pixel coordinates of the image, `Width', `Height' the dimensions. */ /* `GetPixelFunction' points to a callback function used to fetch the color value of each pixel of the image. */ /* `GIF89a' is TRUE is animated GIFs are written, in which case `PictureDelayTime' holds the delay time to write this */ /* image in 1/100'th second resolution. */ /* Post : Compress an image into the GIF-file previously created using _GIFCreate. All colour values should have been specified */ /* before this function is called. The pixels are retrieved using a user defined callback function. This function should */ /* accept two parameters, X and Y, specifying which pixel to retrieve. The pixel values sent to this function are as */ /* follows: X = [Left, Left + Width - 1], Y = [Top, Top + Height - 1]. The function should return the pixel value for the */ /* point given, in the interval [0, _NumColours - 1]. The return value is GIF_OK, GIF_OUTMEM or GIF_ERRWRITE. */ /* Import: _Write, _WriteImageDescriptor, _BitsNeeded, _LZW_Compress, _WriteByte. */ /**********************************************************************************************************************************/ { int CodeSize; int ErrCode; _ImageDescriptor ID; _GraphicControlExtension Ext; if (Width < 0) { Width = _ScreenWidth; StartX = 0; } if (Height < 0) { Height = _ScreenHeight; StartY = 0; } if (StartX < 0) StartX = 0; if (StartY < 0) StartY = 0; if (GIF89a) { Ext.ExtensionIntroducer = 0x21; /* Initiate and write graphic control extension */ Ext.GraphicControlLabel = 0xF9; Ext.BlockSize = 4; Ext.DisposalMethod = 0; /* (No disposal specified) */ Ext.UserInputFlag = 0; /* (No user input expected) */ Ext.TransparantColorFlag = 0; /* (No transparant colour) */ Ext.DelayTime = PictureDelayTime; Ext.TransparantColorIndex = 0; Ext.BlockTerminator = 0; if (_WriteGraphicControlExtension (&Ext) != GIF_OK) return (GIF_ERRWRITE); } ID.Separator = 0x2C; /* Initiate and write image descriptor */ ID.LeftPosition = _ImageLeft = StartX; ID.TopPosition = _ImageTop = StartY; ID.Width = _ImageWidth = Width; ID.Height = _ImageHeight = Height; ID.LocalColourTableSize = 0; ID.Reserved = 0; ID.SortFlag = 0; ID.InterlaceFlag = 0; ID.LocalColourTableFlag = 0; if (_WriteImageDescriptor (&ID) != GIF_OK) return (GIF_ERRWRITE); CodeSize = _BitsNeeded (_NumColours); /* Write code size */ if (CodeSize == 1) CodeSize ++; if (_WriteByte ((byte)CodeSize) != GIF_OK) return (GIF_ERRWRITE); _RelPixX = _RelPixY = 0; /* Perform compression */ if ((ErrCode = _LZW_Compress (CodeSize, GetPixelFunction)) != GIF_OK) return (ErrCode); if (_WriteByte (0) != GIF_OK) /* Write terminating 0-byte */ return (GIF_ERRWRITE); return (GIF_OK); } static byte _GIFClose (void) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : Close the GIF-file. */ /* Import: None. */ /**********************************************************************************************************************************/ { _ImageDescriptor ID; ID.Separator = ';'; /* Initiate and write ending image descriptor */ if (_WriteImageDescriptor (&ID) != GIF_OK) return (GIF_ERRWRITE); _Close (); /* Close file */ _FreeStrtab (); /* Just in case ... */ if (_ColourTable) /* Release colour table */ { free (_ColourTable); _ColourTable = NULL; } return (GIF_OK); } static char *_GIFstrerror (byte ErrorCode) /**********************************************************************************************************************************/ /* Pre : `ErrorCode' holds the error code to be returned. */ /* Post : A string describing the error is returned. */ /* Import: None. */ /**********************************************************************************************************************************/ { switch (ErrorCode) { case GIF_OK : strcpy (_GIFErrorMessage, "(Call succesfull)"); break; case GIF_ERRCREATE : strcpy (_GIFErrorMessage, "Error creating file"); break; case GIF_ERRWRITE : strcpy (_GIFErrorMessage, "Error writing to file"); break; case GIF_OUTMEM : strcpy (_GIFErrorMessage, "Out of memory"); break; case GIF_OUTMEM2 : strcpy (_GIFErrorMessage, "Out of memory!"); break; default : sprintf (_GIFErrorMessage, "Undefined error %02X", ErrorCode); } return (_GIFErrorMessage); } /**********************************************************************************************************************************/ /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START OF MAIN PROGRAM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */ /**********************************************************************************************************************************/ dword SpecPalette[16] = { 0x00000000, 0x002F0000, 0x0000002F, 0x002F002F, 0x00002F00, 0x002F2F00, 0x00002F2F, 0x002A2A2A, 0x00000000, 0x003F0000, 0x0000003F, 0x003F003F, 0x00003F00, 0x003F3F00, 0x00003F3F, 0x003F3F3F }; byte ScreenBuffer[6912]; byte PreparedScreenData[49152U]; /* (Make room for 256*192 pixels) */ bool Use89aForFLASH = FALSE; bool ScreenContainsFLASH; bool ConvertTo89a; short MyGetPixel (short PixX, short PixY) /**********************************************************************************************************************************/ /* Pre : (`PixX', `PixY') are the coordinates of the requested pixel. */ /* (This is the callback function from _LZX_Compress) */ /* Post : The appropriate pixel colour value has been returned. */ /**********************************************************************************************************************************/ { return ((short)PreparedScreenData[PixY * 256 + PixX]); } byte SpectrumScreenToGIF (bool FLASHState) /**********************************************************************************************************************************/ /* Pre : `FLASHState' is TRUE if animated GIFs are written and this is the FLASH 1 image. */ /* Post : The buffered SCR image has been inserted into the open GIF file. */ /**********************************************************************************************************************************/ { byte PaletteData; byte PaletteINK; byte PalettePAPER; byte PixelData; byte PixelMask; byte ScreenBlockIndex; word ScreenBlockCount; word register PreparedDataIndex; word register ScreenDataIndex; word register PaletteIndex; short register CntY; /* Vertical scanline counter */ short register CntX; /* Horizontal byte counter */ PaletteIndex = 6144; /* Start of the attributes area */ PreparedDataIndex = 0; for (ScreenBlockCount = 0 ; ScreenBlockCount < 6144 ; ScreenBlockCount += 2048) /* A Spectrum screen is composed of 3 blocks */ for (CntY = 0 ; CntY < 256 ; CntY += 32) /* 8 character lines in each block */ { ScreenDataIndex = ScreenBlockCount + CntY; for (ScreenBlockIndex = 0 ; ScreenBlockIndex < 8 ; ScreenBlockIndex ++) /* Each character has 8 scan lines */ { for (CntX = 0 ; CntX < 32 ; CntX ++) /* And each scanline contains 32 bytes */ { PaletteData = *(ScreenBuffer + PaletteIndex + CntX); if ((PaletteData & 0x80) && FLASHState) /* FLASH 1 state ? */ { PaletteINK = (PaletteData & 0x78) >> 3; PalettePAPER = (PaletteData & 0x07) | ((PaletteData & 0x40) >> 3); } else /* (No FLASH or first state) */ { PaletteINK = (PaletteData & 0x07) | ((PaletteData & 0x40) >> 3); PalettePAPER = (PaletteData & 0x78) >> 3; } PixelData = *(ScreenBuffer + ScreenDataIndex + CntX); for (PixelMask = 128 ; PixelMask ; PixelMask >>= 1) /* 8 bits in a byte (left to right) */ PreparedScreenData[PreparedDataIndex ++] = (PixelData & PixelMask ? PaletteINK : PalettePAPER); } ScreenDataIndex += 256; /* One spectrum scan line down is 256 bytes */ } PaletteIndex += 32; /* Palette is `correctly' addressed */ } /* All set? Here goes! */ /* If a FLASHING screen is written, the inter-image pause is 32/100 = 16/50's of a second, just like a real Speccy :-) */ return (_GIFCompressImage (0, 0, 256, 192, &MyGetPixel, ConvertTo89a, 32)); } int main (int argc, char **argv) /**********************************************************************************************************************************/ /* Pre : None. */ /* Post : All SCR files specified on the command line have been converted to (animated) GIF files. */ /**********************************************************************************************************************************/ { FILE *InputFile; char InputFileName[256]; char OutputFileName[256]; int FileIndex = 0; word BIndex; byte register ColourCount; byte ErrCode; dword *PP; /* Palette Pointer */ printf ("\nSCR2GIF v1.0 - Copyright (C) 1998 M. van der Heide, ThunderWare Research Center\n\n"); if (argc < 2) /* Moron check */ { fprintf (stderr, "Usage: scr2gif [-f] filename[.scr] ...\n"); fprintf (stderr, " -f = Convert FLASHing screens to animated GIF\n"); exit (1); } if (!strcmp (argv[1], "-f") || !strcmp (argv[1], "-F")) /* Want animated GIFs for FLASHing screens ? */ { if (argc < 3) /* (Moron check) */ { fprintf (stderr, "Usage: scr2gif [-f] filename[.scr] ...\n"); fprintf (stderr, " -f = Convert FLASHing screens to animated GIF\n"); exit (1); } Use89aForFLASH = TRUE; FileIndex = 1; } while (++ FileIndex < argc) /* Do all SCR files specified on the command line */ { strcpy (InputFileName, argv[FileIndex]); if ((InputFile = fopen (InputFileName, "rb")) == NULL) /* Try to open the SCR file */ { strcat (InputFileName, ".scr"); if ((InputFile = fopen (InputFileName, "rb")) == NULL) { strcpy (InputFileName + strlen (InputFileName) - 3, "SCR"); if ((InputFile = fopen (InputFileName, "rb")) == NULL) { printf ("Cannot find any file named %s, %s.scr or %s.SCR, so there!\n", argv[FileIndex], argv[FileIndex], argv[FileIndex]); exit (1); } } } strcpy (OutputFileName, InputFileName); if (!strcmp (OutputFileName + strlen (OutputFileName) - 4, ".scr")) strcpy (OutputFileName + strlen (OutputFileName) - 3, "gif"); /* Determine output file name */ else if (!strcmp (OutputFileName + strlen (OutputFileName) - 4, ".SCR")) strcpy (OutputFileName + strlen (OutputFileName) - 3, "GIF"); /* (Maintain case) */ else strcat (OutputFileName, ".gif"); printf ("Converting %s to %s ... ", InputFileName, OutputFileName); fflush (stdout); if (fread (ScreenBuffer, 1, 6912, InputFile) != 6912) { printf ("\nSorry, %s doesn't look like a SCR file to me!\n", InputFileName); fclose (InputFile); exit (1); } fclose (InputFile); ScreenContainsFLASH = FALSE; if (Use89aForFLASH) for (BIndex = 6144 ; BIndex < 6912 && !ScreenContainsFLASH ; BIndex ++) /* Check if there's FLASH */ if (*(ScreenBuffer + BIndex) & 0x80) ScreenContainsFLASH = TRUE; ConvertTo89a = (bool)(Use89aForFLASH && ScreenContainsFLASH); /* No need for animation is there's no FLASH! */ printf (ConvertTo89a ? "animated ... " : "single image ... "); fflush (stdout); if ((ErrCode = _GIFCreate (OutputFileName, 256, 192, 16, 6, ConvertTo89a)) != GIF_OK) { printf ("\nCannot create GIF file (%s)\n", _GIFstrerror (ErrCode)); exit (1); } PP = SpecPalette; for (ColourCount = 0 ; ColourCount < 16 ; ColourCount ++, PP ++) _GIFSetColour (ColourCount, (byte)(*PP & 0x000000FF), (byte)((*PP & 0x0000FF00) >> 8), (byte)((*PP & 0x00FF0000) >> 16)); _GIFWriteGlobalColorTable (ConvertTo89a); if ((ErrCode = SpectrumScreenToGIF (FALSE)) != GIF_OK) { printf ("\nCannot compress GIF image (%s)\n", _GIFstrerror (ErrCode)); _GIFClose (); unlink (OutputFileName); exit (1); } if (ConvertTo89a) if ((ErrCode = SpectrumScreenToGIF (TRUE)) != GIF_OK) { printf ("\nCannot compress GIF image (%s)\n", _GIFstrerror (ErrCode)); _GIFClose (); unlink (OutputFileName); exit (1); } if ((ErrCode = _GIFClose ()) != GIF_OK) { printf ("\nCannot close GIF file (%s)\n", _GIFstrerror (ErrCode)); unlink (OutputFileName); exit (1); } puts ("done"); } return (0); }