From 321c8f2c2e54d7b50147e24607acd7c414524eaa Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 16 Aug 2023 10:05:32 +0200 Subject: [PATCH] =?UTF-8?q?A=C3=B1adido=20scr2gif.c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scr2gif.c | 1047 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1047 insertions(+) create mode 100644 scr2gif.c diff --git a/scr2gif.c b/scr2gif.c new file mode 100644 index 0000000..6e40c9f --- /dev/null +++ b/scr2gif.c @@ -0,0 +1,1047 @@ +/**********************************************************************************************************************************/ +/* 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); +}