gifutil.cpp

Go to the documentation of this file.
00001 // $Header: /Camelot/winoil/gifutil.cpp 19    25/03/99 16:16 Markn $
00002 
00003 // Contains useful routines for compressing a bitmap out to a GIF format file and
00004 // routines to load that file back in.
00005 
00006 /*
00007 */
00008 
00009 #include "camtypes.h"
00010 #include "ensure.h"
00011 #include "fixmem.h"
00012 #include "errors.h"
00013 #include "progress.h"       // For hourglass stuff
00014 //#include "resource.h"     // IDS_OUTOFMEMORY
00015 //#include "accures.h"      // IDW_CANCELLEDBMPIMPORT
00016 #include "gifutil.h"
00017 //#include "outptgif.h"
00018 //#include "andy.h"
00019 //#include "dibconv.h"
00020 //#include "string.h"           // memcpy
00021 #include "camfiltr.h"       // BaseCamelotFilter
00022 
00023 #define new CAM_DEBUG_NEW
00024 
00025 
00026 CC_IMPLEMENT_MEMDUMP(Palette, CC_CLASS_MEMDUMP)
00027 Palette::~Palette()
00028 {
00029     if (m_pColours)
00030         delete [] m_pColours;
00031     m_pColours = NULL;          // ensure state correct
00032     m_nSize = 0;
00033 }
00034 
00035 
00036 
00037 Palette::Palette(const Palette& otherPalette)
00038 {
00039     if (&otherPalette == this) return;
00040 
00041     if (otherPalette.m_nSize > m_nSize)
00042         delete [] m_pColours;
00043         m_pColours = new RGBQUAD[otherPalette.m_nSize];
00044         if (m_pColours == NULL)
00045         {
00046             m_bInitOK = FALSE;
00047             return;
00048         }
00049     m_nSize = otherPalette.m_nSize;
00050     memcpy(m_pColours, otherPalette.m_pColours, m_nSize);
00051     m_bInitOK = TRUE;
00052 }
00053 
00054 #define BitSet(byte, bit)      (((byte) & (bit)) == (bit))
00055 
00056 // define the statics that we require
00057 BOOL    GIFUtil::Interlace      = FALSE;    // Use interlace or not
00058 int     GIFUtil::Transparent    = -1;       // colour or -1 = no transparency
00059 UINT32  GIFUtil::m_Delay        = 0;        // Data member, to recieve a bitmaps Animation delay value
00060 BOOL    GIFUtil::ZeroDataBlock  = FALSE;    // flag to say whether we have a zero length block or not 
00061 int     GIFUtil::m_nCurrentBitmap = 0;
00062 GIFDisposalMethod GIFUtil::m_Restore = GDM_LEAVE;   // Data member, to recieve a bitmaps' Animation  Restore value.
00063 
00064 LPRGBQUAD   GIFUtil::lpGlobalPalette = NULL;        // pointer to temporary palette store
00065 int         GIFUtil::GlobalPaletteSize = 0;         // size of the global palette found  
00066 FilePos     GIFUtil::m_NextImageStartPosition = 0;  // Position in file of next image 
00067 BOOL        GIFUtil::m_bImageRead = FALSE;          // Did we get something?
00068 
00069 WORD        GIFUtil::m_GlobalWidth  = 0;            // The overall width from the GIF header
00070 WORD        GIFUtil::m_GlobalHeight = 0;            // The overall height from the GIF header
00071 
00072 /********************************************************************************************
00073 
00074 >   GIFUtil::GIFUtil()
00075 
00076     Author:     Neville Humphrys
00077     Created:    29/6/95
00078     Purpose:    Default constructor for the class. 
00079     SeeAlso:    
00080 
00081 ********************************************************************************************/
00082 
00083 //GIFUtil::GIFUtil()
00084 //{
00085 //}
00086 
00087 /********************************************************************************************
00088 >   GIFUtil::GIFUtil()
00089 
00090     Author:     Neville Humphrys
00091     Created:    29/6/95
00092     Purpose:    Default destructor for the class.
00093     SeeAlso:    
00094 
00095 ********************************************************************************************/
00096 
00097 //GIFUtil::~GIFUtil()
00098 //{
00099 //}
00100 
00101 /********************************************************************************************
00102 
00103 >   static BOOL GIFUtil::ReadFromFile( CCLexFile *File, LPBITMAPINFO *Info, LPBYTE *Bits,
00104                                        int *TransColour, int& nBitmapToRead, 
00105                                        String_64 *ProgressString, BaseCamelotFilter *pFilter = NULL  )
00106 
00107     Author:     Neville Humphrys
00108     Created:    29/6/95
00109     Inputs:     File            A opened CCLexFile that can be read from. It should be positioned at the
00110                                 start. Caller is responsible for closing it. The file needs to be in
00111                                 Binary mode.
00112                 nBitmapToRead   The number of the bitmap in the GIF that you wish to read.
00113                                 If 1 then the GIF will be read from the beginning obtaining the
00114                                 header information.
00115                                 For any number other than 1 it should be noted that bitmaps can only
00116                                 be read in an incremental sequence and that the first one must have
00117                                 been read prior to any call with a non-1 value.
00118                 ProgressString  allows the user to specify whether they require a progress hourglass or 
00119                                 not. If NULL then none is shown, otherwise an progress bar is shown
00120                                 using the text supplied. Defaults to NULL i.e. no progress bar.
00121                 pFilter         is an alternative way of handling the progress bar, assume the
00122                                 progress bar has been start and just call the IncProgressBarCount in 
00123                                 BaseCamelotFilter to do the progress bar update.
00124                                 Defaults to NULL i.e. no progress bar.
00125     Outputs:    Info points to a new LPBITMAPINFO struct and Bits points to the bytes.
00126                 These can be freed up with FreeDIB.
00127                 TransColour is either -1 == none or equal to the transparency colour found
00128                 nBitmapToRead   Returns the number of the next bitmap that can be read from the GIF
00129                                 or -1 if no further bitmaps exist.
00130     Returns:    TRUE if worked, FALSE if failed (error will be set accordingly but not reported)
00131     Purpose:    Reads a .gif file into memory decompressing it as it goes.
00132                 ***Errors on 16-bit builds***
00133                 A progress hourglass can be shown if required.
00134     Errors:     Calls SetError on FALSE returns.
00135     Scope:      Static, Public
00136     SeeAlso:    DIBUtil::ReadFromFile; AccusoftFilters::ReadFromFile;
00137 
00138 ********************************************************************************************/
00139 BOOL GIFUtil::ReadFromFile( CCLexFile *File, LPBITMAPINFO *Info, LPBYTE *Bits,
00140                             int *TransColour, String_64 *ProgressString,
00141                             BaseCamelotFilter *pFilter )
00142 {
00143     int nBitmap = 1;
00144 
00145     return (ReadFromFile( File, Info, Bits, TransColour, nBitmap, ProgressString, pFilter));
00146 }
00147 
00148 /**********************************************************************************************
00149 
00150 static  BOOL GIFUtil::ReadFromFile( CCLexFile *File, LPBITMAPINFO *Info, LPBYTE *Bits,
00151                                     int *TransColour, int& nBitmapToRead, String_64 *ProgressString = NULL,
00152                                     BaseCamelotFilter *pFilter = NULL, UINT32* Delay=NULL,
00153                                     GIFDisposalMethod *Restore=NULL,
00154                                     UINT32 * pLeftOffset = NULL, UINT32 * pTopOffset = NULL,
00155                                     BOOL * pLocalPalette = NULL)
00156 Author:         -
00157 Created:        -
00158 Inputs/Outputs  Refer to the above Function Header for a full explanation. 
00159 
00160 *************************************************************************************************/
00161 
00162 BOOL GIFUtil::ReadFromFile( CCLexFile *File, LPBITMAPINFO *Info, LPBYTE *Bits,
00163                             int *TransColour, int& nBitmapToRead, String_64 *ProgressString,
00164                             BaseCamelotFilter *pFilter, UINT32* Delay, GIFDisposalMethod *Restore,
00165                             UINT32 * pLeftOffset, UINT32 * pTopOffset,
00166                             BOOL * pLocalPalette)
00167 {
00168     if ((nBitmapToRead > 1 && nBitmapToRead == m_nCurrentBitmap) || nBitmapToRead < 1)
00169     {
00170         ERROR3("GIFUtil::ReadFromFile() - can't read that bitmap");
00171         return FALSE;
00172     }
00173 
00174     *Info           = NULL;     // in case of early exit
00175     *Bits           = NULL;
00176     Transparent     = -1;
00177     *TransColour    = -1;       // in case of early exit set to none
00178     int Background  = 0;        // background colour number !!! not used !!!
00179     Interlace       = FALSE;    // set interlace to false by default
00180 
00181     // Must set the exception throwing flag to True and force reporting of errors to False.
00182     // This means that the caller must report an error if the function returns False.
00183     // Any calls to CCLexFile::GotError will now throw a file exception and should fall into
00184     // the catch handler at the end of the function.
00185     BOOL OldThrowingState = File->SetThrowExceptions( TRUE );
00186     BOOL OldReportingState = File->SetReportErrors( FALSE );
00187 
00188     // If the caller has specified a string then assume they require a progress bar
00189     // Start it up.
00190     if (ProgressString != NULL)
00191         BeginSlowJob(100, FALSE, ProgressString);
00192 
00193     try
00194     {
00195         // If we want the first bitmap we'll assume we're at the start of the file & read the header
00196         if (nBitmapToRead == 1)
00197         {
00198             // place to store the global palette, if present, for later use
00199             lpGlobalPalette         = NULL; // pointer to temporary palette store
00200             GlobalPaletteSize           = 0;    // size of the global palette found  
00201 
00202             GIFINFOHEADER Header;
00203             // This is really sizeof(GIFINFOHEADER) but this returns 14 instead of 13
00204             // as it rounds to the nearest word boundary
00205             const size_t HeaderSize = sizeof(char)* 6 + sizeof(WORD) * 2 + sizeof(BYTE) * 3;
00206 
00207             File->read( &Header, HeaderSize );
00208             if (File->bad())
00209                 File->GotError( _R(IDE_FORMATNOTSUPPORTED) );
00210 
00211             // Just double check that the signature is correct, if not then fail immediately
00212             if (
00213                 (strncmp(Header.giName, "GIF89a", 6) != 0) &&
00214                 (strncmp(Header.giName, "GIF87a", 6) != 0)
00215             )
00216                 File->GotError( _R(IDE_BADFORMAT) );
00217 
00218             // Note the overall size of the GIF from the header, may be the animation size
00219             m_GlobalWidth       = Header.giWidth;
00220             m_GlobalHeight      = Header.giHeight;
00221             // flags word consists of:-
00222             // - bit 7 colour table flag                = set if global colour table follows
00223             // - bits 6-4 colour resolution             = bpps - 1
00224             // - bit 3 sort flag                        = set global colour table sorted
00225             // - bits 3-0 size of global colour table   = size is 2^(value+1)
00226             int ColorResolution = (((Header.giFlags >> 4) & 0x07) + 1);
00227             GlobalPaletteSize   = 2 << (Header.giFlags & 0x07);
00228             Background          = Header.giBackground;
00229 //          int AspectRatio     = Header.giAspect;
00230 
00231 TRACEUSER("Neville",_T("Gif Global Width = %d Height = %d\n"), m_GlobalWidth, m_GlobalHeight);
00232 TRACEUSER("Neville",_T("Gif ColorResolution = %d\n"), ColorResolution);
00233 
00234             // Check if we have a global colour map present or not, if so then read it in.
00235             if (BitSet(Header.giFlags, GLOBALCOLOURMAP))
00236             {
00237 TRACEUSER("Neville",_T("Gif read global colour table size = %d\n"), GlobalPaletteSize);
00238                 // Read in the global colour map into a palette structure for possible later use
00239                 const size_t TotalPal = sizeof(RGBQUAD) * GlobalPaletteSize;
00240                 lpGlobalPalette = (LPRGBQUAD)CCMalloc( TotalPal );
00241                 if (lpGlobalPalette==NULL)
00242                     return FALSE;
00243 
00244                 ReadColourMap(File, GlobalPaletteSize, lpGlobalPalette);
00245             }
00246             m_nCurrentBitmap = 0;
00247             m_NextImageStartPosition = 0;
00248         }
00249 
00250         // We now need to go through and process the data in this file which
00251         // should now consist off GIF blocks until we hit the end of file or
00252         // we have processed the lot.
00253         BOOL FileProcessed = FALSE;
00254 
00255         m_bImageRead = FALSE;
00256         unsigned char c;
00257         // Set up bad values so that the DIB alloc claim will fail if there
00258         // has been no GIFIMAGEBLOCK found
00259 //      int Width           = 0;    // Width of bitmap  
00260 //      int Height          = 0;    // Height of bitmap 
00261 //      int BitsPerPixel    = 0;    //ColorResolution;  // Colour depth required
00262 
00263         if (m_NextImageStartPosition != 0)
00264         {
00265             File->seekIn(m_NextImageStartPosition, ios::beg);
00266             m_NextImageStartPosition = 0;
00267         }
00268 
00269         while (!File->eof() && !File->bad() && !FileProcessed)
00270         {
00271             // Get the next character in the stream and see what it indicates
00272             // comes next in the file
00273             // Use Get as this will read the EOF character (255) and exit via the
00274             // File->eof() test rather than Read which will throw an exception. 
00275             //File->read( &c, 1 );
00276             File->get( (char&)c );
00277             
00278             switch (c)
00279             {
00280                 case ';':
00281                 {
00282                     // GIF terminator
00283                     // Cannot assume that the terminator immediately follows the image data
00284                     // Might have some extensions following the image data.                      
00285                     FileProcessed = TRUE;
00286                     nBitmapToRead = -1;
00287                     break;
00288                 }
00289 
00290                 case '!': //EXTENSIONBLOCK:
00291                 {
00292                     // Extension
00293                     ProcessExtension(File);
00294                     break;
00295                 }
00296                 
00297                 case ',':
00298                 {
00299                     // start of image character
00300                     if (m_bImageRead)   // another bitmap - save it for the next call to ReadFromFile()
00301                     {
00302                         if (m_NextImageStartPosition == 0)
00303                         {
00304                             // No transparency info for the next one so start here
00305                             m_NextImageStartPosition = File->tellIn() - 1;
00306                         }
00307                         ++nBitmapToRead;
00308                         FileProcessed = TRUE;
00309                         break;
00310                     }
00311 
00312                     UINT32 LeftOffset = 0;
00313                     UINT32 TopOffset = 0;
00314                     BOOL LocalPalette = FALSE;
00315 
00316                     // Should be followed by a GIFIMAGE BLOCK
00317                     ProcessImageBlock(File, Info, Bits, &LeftOffset, &TopOffset, &LocalPalette);
00318                     ++m_nCurrentBitmap;
00319 
00320                     if (nBitmapToRead == m_nCurrentBitmap)
00321                     {
00322                         m_bImageRead = TRUE;
00323                     }
00324 
00325                     // return the values back to the caller if it desired them
00326                     if (pLeftOffset)
00327                         *pLeftOffset = LeftOffset;
00328                     if (pTopOffset)
00329                         *pTopOffset = TopOffset;
00330                     if (pLocalPalette)
00331                         *pLocalPalette = LocalPalette;
00332                     break;
00333                 }
00334 
00335                 default:
00336                     // We have found something other than what we are expecting
00337                     // so fail with an error
00338                     //File->GotError( IDE_BADFORMAT );
00339                     // Cannot do this as some files have random bits
00340                     // e.g. galileo.gif has a random zero before the ;
00341                     //      colsec.gif has a rampant > at the end instead of ;
00342                     if (m_bImageRead)
00343                     {
00344                         // We've already got something so ignore anything else in case it goes
00345                         // completely wrong.
00346                         FileProcessed = TRUE;
00347                         nBitmapToRead = -1;
00348                     }
00349                     TRACE( _T("Unrecognized Character %x at %d\n"), (int)c, File->tellIn());
00350 
00351                     break;
00352             }
00353         }
00354 
00355         // If we reach here and the bitmap allocations are still null then no valid image
00356         // was found and so we should error now.
00357         // Might have just been a GIF file with extension tags in and no images!
00358         if (*Info == NULL || *Bits == NULL)
00359             File->GotError( _R(IDE_BADFORMAT) );
00360 
00361         // We read the desired bitmap but the EOF came along before we could try anything else
00362         // Signal this is the last bitmap
00363         if (m_bImageRead && File->eof())
00364         {
00365             TRACE( _T("GIF: Premature end of file") );
00366             nBitmapToRead = -1;
00367         }
00368 
00369         // Return the transparency/delay/restore found to the caller.
00370         *TransColour = Transparent;
00371         if (Delay)
00372         {
00373             *Delay = m_Delay;
00374         }
00375         if (Restore)
00376         {
00377             *Restore = m_Restore;
00378         }
00379 
00380         // Free up the bit of memory for a palette we grabbed, if present & no more images to read
00381         if (lpGlobalPalette && nBitmapToRead == -1)
00382         {
00383             CCFree(lpGlobalPalette);
00384             lpGlobalPalette = NULL;
00385         }
00386 
00387         // If started, then stop then progress bar
00388         if (ProgressString != NULL)
00389             EndSlowJob();
00390 
00391         // Must set the exception throwing and reporting flags back to their entry states
00392         File->SetThrowExceptions( OldThrowingState );
00393         File->SetReportErrors( OldReportingState );
00394 
00395         // er, we seem to have finished OK so say so
00396         return TRUE;
00397     }
00398     catch( CFileException e )
00399     {
00400         // catch our form of a file exception
00401         TRACE( _T("GIFUtil::ReadFromFile CC catch handler\n") );
00402 
00403         FreeDIB( *Info, *Bits );                            // free any alloced memory
00404         *Info = NULL;                                       // and NULL the pointers
00405         *Bits = NULL;
00406         
00407         // Free up the bit of memory for a palette we grabbed, if present
00408         if (lpGlobalPalette)
00409         {
00410             CCFree(lpGlobalPalette);
00411             lpGlobalPalette = NULL;
00412         }
00413 
00414         // If started, then stop then progress bar
00415         if (ProgressString != NULL)
00416             EndSlowJob();
00417 
00418         // Must set the exception throwing and reporting flags back to their entry states
00419         File->SetThrowExceptions( OldThrowingState );
00420         File->SetReportErrors( OldReportingState );
00421 
00422         return FALSE;
00423     }
00424 
00425     ERROR2( FALSE, "Escaped exception clause somehow" );
00426 }
00427 
00428 
00429 /********************************************************************************************
00430 
00431 >   static BOOL GIFUtil::ProcessExtension(CCLexFile *fd)
00432 
00433     Author:     Neville Humphrys
00434     Created:    29/6/95
00435     Inputs:     fd          pointer to a CCLexFile to read the data from
00436     Outputs:    -
00437     Returns:    True if worked ok, False otherwise.
00438     Purpose:    
00439     SeeAlso:    
00440 
00441 ********************************************************************************************/
00442 
00443 BOOL GIFUtil::ProcessExtension(CCLexFile *fd)
00444 {
00445     static char buf[256];
00446 
00447     // Get the type of this extension and then process it
00448     unsigned char ExtensionType;
00449     fd->read( &ExtensionType, 1 );
00450 
00451     switch (ExtensionType)
00452     {
00453         case TRANSPARENTBLOCK:
00454         {
00455             // The next image in the file might need to start here to get transparency
00456             // information. So remember the position
00457             m_NextImageStartPosition = fd->tellIn() - 2;
00458 
00459             // Graphic Control Extension
00460             // We have read in the identfier and the type of extension block so we
00461             // really need to read the data from that point onwards
00462             GIFTRANSBLOCK TransBlock;
00463 
00464             // This is really sizeof(GIFTRANSBLOCK) but this returns 14 instead of 13
00465             // as it rounds to the nearest word boundary
00466             const size_t TransBlockSize = sizeof(WORD) * 1 + sizeof(BYTE) * 6;
00467             fd->read( &(TransBlock.gtbBlockSize), TransBlockSize - 2);
00468             
00469             // Contains lots of random rubbish we are not interested in.            
00470             // Just skip to the crux of the matter and read in the transparency colour,
00471             // if the transparency flag is set.
00472 
00473             if (!m_bImageRead)
00474             {
00475                 BOOL fTrans = TransBlock.gtbFlags & 0x1;  
00476                 if (fTrans != 0)
00477                     Transparent = TransBlock.gtbTransparency;
00478                 else
00479                 {
00480                     Transparent = -1; // no transparency    
00481                 }
00482 
00483                 TRACEUSER("Neville", _T("ProcessExtension - transparent fTrans = %d Transparent = %d\n"), fTrans, Transparent );
00484 
00485                 // Animation Restore\Delay values.
00486                 m_Delay = TransBlock.gtbDelay;
00487                 m_Restore = GIFDisposalMethod((TransBlock.gtbFlags >> 2) & 0x03);
00488             }
00489 
00490             return TRUE;
00491         }
00492 
00493         case PLAINTEXTBLOCK:
00494         {
00495             // We need to bin any transparency information we have since
00496             // "The scope of this [Graphic Control (TRANSPARENTBLOCK)] Extension is the 
00497             // graphic rendering block that follows it"
00498 
00499             if (m_bImageRead)
00500             {
00501                 // We've read the image but don't need to start with any TRANSPARENT block
00502                 // that might have been read after the image, so make sure we don't
00503                 m_NextImageStartPosition = 0;
00504             }
00505             else
00506             {
00507                 // We haven't read any image yet so just make sure no transparency
00508                 Transparent = -1;
00509             }
00510             // ...and drop through
00511         }
00512         
00513         default:
00514             break;
00515     }
00516 
00517     // Must set this as otherwise, if this is after we have loaded an image then we have
00518     // the possibility that the zero block has been encountered and GetDataBlock will
00519     // always return 0, so force it not to. 
00520     ZeroDataBlock = FALSE;
00521 
00522     // If not recognised then read the rest of that extension
00523     while (GetDataBlock(fd, (unsigned char*) buf) != 0)
00524         ;
00525 
00526     return FALSE;
00527 }
00528 
00529 /********************************************************************************************
00530 
00531 >   static BOOL GIFUtil::ReadColourMap(CCLexFile *fd, int number, LPRGBQUAD lpPalette)
00532 
00533     Author:     Neville Humphrys
00534     Created:    29/6/95
00535     Inputs:     fd          pointer to a CCLexFile to read the data from
00536     Outputs:    -
00537     Returns:    True if worked ok, False otherwise.
00538     Purpose:    
00539     SeeAlso:    
00540 
00541 ********************************************************************************************/
00542 
00543 BOOL GIFUtil::ReadColourMap(CCLexFile *fd, int number, LPRGBQUAD lpPalette)
00544 {
00545     GIFRGBTRIPLE rgb;
00546     for (int i = 0; i < number; ++i)
00547     {
00548         fd->read( &rgb, sizeof(GIFRGBTRIPLE) );
00549         lpPalette->rgbBlue = rgb.grgbtBlue;
00550         lpPalette->rgbGreen = rgb.grgbtGreen;
00551         lpPalette->rgbRed = rgb.grgbtRed;
00552         lpPalette->rgbReserved = 0;
00553         lpPalette++;
00554     }
00555     
00556     return TRUE;
00557 }
00558 
00559 
00560 
00561 /********************************************************************************************
00562 
00563 >   static int GIFUtil::GetDataBlock(CCLexFile *fd, unsigned char *buf)
00564 
00565     Author:     Neville Humphrys
00566     Created:    29/6/95
00567     Inputs:     fd          pointer to a CCLexFile to read the data from
00568     Outputs:    -
00569     Returns:    Number of bytes read
00570     Purpose:    To read in a block of data from the specifed file. Reads a 1 byte count
00571                 and then reads that many bytes in, as long as the count is non-zero.
00572                 Any non-image calls must set ZeroDataBlock to False before calling this as
00573                 otherwise it may just return zero. 
00574     SeeAlso:    
00575 
00576 ********************************************************************************************/
00577 
00578 int GIFUtil::GetDataBlock(CCLexFile *fd, unsigned char *buf)
00579 {
00580     unsigned char count;
00581 
00582     // Added, as if we encounter the zero block then that is the end of this image
00583     // and we should never read any more info from that image. (added 14/8/95)
00584     if (ZeroDataBlock)
00585         return 0;
00586     
00587     fd->read( &count, 1 );
00588 
00589     // If there is a count then read that many bytes in
00590     // If zero then this is a terminator so return that
00591     if (count == 0)
00592     {
00593         ZeroDataBlock = TRUE;
00594         // Fall through to return 0
00595     }
00596     else
00597     {
00598         ZeroDataBlock = FALSE;
00599         fd->read( buf, count );
00600         // fall through to return the number of bytes found
00601     }
00602 
00603     return count;
00604 }
00605 
00606 /********************************************************************************************
00607 
00608 >   static int GIFUtil::GetCode(CCLexFile *fd, int code_size, int flag)
00609 
00610     Author:     Neville Humphrys
00611     Created:    29/6/95
00612     Inputs:     fd          pointer to a CCLexFile to read the data from
00613                 code_size
00614                 flag            
00615     Outputs:    -
00616     Returns:    The code found.
00617     Purpose:    Returns the next code in the stream.
00618     SeeAlso:    
00619 
00620 ********************************************************************************************/
00621 
00622 int GIFUtil::GetCode(CCLexFile *fd, int code_size, int flag)
00623 {
00624     static unsigned char    buf[280];
00625     static int  curbit;
00626     static int  lastbit;
00627     static int  done;
00628     static int  last_byte;
00629     int i;
00630     int j;
00631     int ret;
00632     unsigned char   count;
00633 
00634     if (flag)
00635     {
00636         curbit = 0;
00637         lastbit = 0;
00638         done = FALSE;
00639         return 0;
00640     }
00641 
00642     if ( (curbit+code_size) >= lastbit)
00643     {
00644         if (done)
00645         {
00646             if (curbit >= lastbit)
00647             {
00648                 /* Oh well */
00649             }                        
00650             return -1;
00651         }
00652         buf[0] = buf[last_byte-2];
00653         buf[1] = buf[last_byte-1];
00654 
00655         if ((count = GetDataBlock(fd, &buf[2])) == 0)
00656             done = TRUE;
00657 
00658         last_byte = 2 + count;
00659         curbit = (curbit - lastbit) + 16;
00660         lastbit = (2+count)*8 ;
00661     }
00662 
00663     ret = 0;
00664     for (i = curbit, j = 0; j < code_size; ++i, ++j)
00665         ret |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
00666 
00667     curbit += code_size;
00668 
00669     return ret;
00670 }
00671 
00672 
00673 /********************************************************************************************
00674 
00675 >   static int GIFUtil::LWZReadByte(CCLexFile *fd, int flag, int input_code_size)
00676 
00677     Author:     Neville Humphrys
00678     Created:    29/6/95
00679     Inputs:     fd              pointer to a CCLexFile to read the data from
00680                 flag
00681                 input_code_size 
00682     Outputs:    -
00683     Returns:    The next LZW byte in the stream. 
00684     Purpose:    To read the next LZW byte from the stream.
00685     SeeAlso:    
00686 
00687 ********************************************************************************************/
00688 
00689 int GIFUtil::LWZReadByte(CCLexFile *fd, int flag, int input_code_size)
00690 {
00691     static int  fresh = FALSE;
00692     int         code;
00693     int         incode;
00694     static int  code_size;
00695     static int  set_code_size;
00696     static int  max_code;
00697     static int  max_code_size;
00698     static int  firstcode;
00699     static int  oldcode;
00700     static int  clear_code;
00701     static int  end_code;
00702     static int  table[2][(1<< MAX_LWZ_BITS)];
00703     static int  stack[(1<<(MAX_LWZ_BITS))*2];
00704     static int  *sp;
00705     int i;
00706 
00707     if (flag)
00708     {
00709         set_code_size = input_code_size;
00710         code_size = set_code_size+1;
00711         clear_code = 1 << set_code_size ;
00712         end_code = clear_code + 1;
00713         max_code_size = 2*clear_code;
00714         max_code = clear_code+2;
00715 
00716         GetCode(fd, 0, TRUE);
00717 
00718         fresh = TRUE;
00719 
00720         for (i = 0; i < clear_code; ++i)
00721         {
00722             table[0][i] = 0;
00723             table[1][i] = i;
00724         }
00725         for (; i < (1<<MAX_LWZ_BITS); ++i)
00726             table[0][i] = table[1][0] = 0;
00727 
00728         sp = stack;
00729 
00730         return 0;
00731     }
00732     else if (fresh)
00733     {
00734         fresh = FALSE;
00735         do
00736         {
00737             firstcode = oldcode = GetCode(fd, code_size, FALSE);
00738         } while (firstcode == clear_code);
00739 
00740         return firstcode;
00741     }
00742 
00743     if (sp > stack)
00744         return *--sp;
00745 
00746     while ((code = GetCode(fd, code_size, FALSE)) >= 0)
00747     {
00748         if (code == clear_code)
00749         {
00750             for (i = 0; i < clear_code; ++i)
00751             {
00752                    table[0][i] = 0;
00753                    table[1][i] = i;
00754             }
00755 
00756             for (; i < (1<<MAX_LWZ_BITS); ++i)
00757                    table[0][i] = table[1][i] = 0;
00758 
00759             code_size = set_code_size+1;
00760             max_code_size = 2*clear_code;
00761             max_code = clear_code+2;
00762             sp = stack;
00763             firstcode = oldcode = GetCode(fd, code_size, FALSE);
00764 
00765             return firstcode;
00766         }
00767         else if (code == end_code)
00768         {
00769             int             count;
00770             unsigned char   buf[260];
00771 
00772             if (ZeroDataBlock)
00773                 return -2;
00774 
00775             while ((count = GetDataBlock(fd, buf)) > 0)
00776                 ;
00777 
00778             if (count != 0)
00779                 return -2;
00780         }
00781 
00782         incode = code;
00783 
00784         if (code >= max_code)
00785         {
00786             *sp++ = firstcode;
00787             code = oldcode;
00788         }
00789 
00790         while (code >= clear_code)
00791         {
00792             *sp++ = table[1][code];
00793             if (code == table[0][code])
00794             {
00795                 /* Oh well */
00796             }
00797             code = table[0][code];
00798         }
00799 
00800         *sp++ = firstcode = table[1][code];
00801 
00802         if ((code = max_code) <(1<<MAX_LWZ_BITS))
00803         {
00804             table[0][code] = oldcode;
00805             table[1][code] = firstcode;
00806             ++max_code;
00807             if ((max_code >= max_code_size) && (max_code_size < (1<<MAX_LWZ_BITS)))
00808             {
00809                 max_code_size *= 2;
00810                 ++code_size;
00811             }
00812         }
00813 
00814         oldcode = incode;
00815 
00816         if (sp > stack)
00817             return *--sp;
00818     }
00819     return code;
00820 }
00821 
00822 /********************************************************************************************
00823 
00824 >   static BOOL GIFUtil::ReadImage(CCLexFile *fd, LPBYTE pBitsData, int width, int height,
00825                                    BOOL interlace, BaseCamelotFilter *pFilter = NULL )
00826 
00827     Author:     Neville Humphrys
00828     Created:    29/6/95
00829     Inputs:     fd          pointer to a CCLexFile to read the data from
00830                 pBitsData   pointer to the place to put the bitmap data, should have been
00831                             claimed so that it is the correct size to contain it.
00832                 width       width of the bitmap in pixels
00833                 height      height of the bitmap in pixels
00834                 bpp         colour depth of the bitmap in bits per pixel
00835                 interlace   flag to say if this data is interlaced or not.
00836                 pFilter     is an alternative way of handling the progress bar, assume the
00837                             progress bar has been start and just call the IncProgressBarCount in 
00838                             BaseCamelotFilter to do the progress bar update.
00839                             Defaults to NULL i.e. no progress bar.
00840     Purpose:    The read the bitmap data into the specified buffer. It decompresses the
00841                 data from the specified file and then puts the pixel data into the buffer.
00842                 Copes with interlaced GIFs.
00843                 Assumes progress hourglass has been started with 100.
00844     SeeAlso:    GIFUtil::ReadFromFile;
00845 
00846 ********************************************************************************************/
00847 
00848 BOOL GIFUtil::ReadImage(CCLexFile *fd, LPBYTE pBitsData, int width, int height, int bpp,
00849                         BOOL interlace, BaseCamelotFilter *pFilter)
00850 {
00851     ERROR2IF(pBitsData == NULL, FALSE, "GIFUtil::ReadImage given null pointer to bits data");
00852     ERROR2IF(bpp != 8 && bpp != 4 && bpp != 1, FALSE, "GIFUtil::ReadImage - bpp invalid");
00853 
00854     unsigned char c;      
00855     int v = 0;
00856     int xpos = 0;
00857     int ypos = 0;
00858     int pass = 0;
00859     int yposcount = 0;
00860 
00861     ZeroDataBlock = FALSE;
00862     
00863     // Work out how often we need to update the progress bar
00864     int UpdateEvery = 1;
00865     INT32 UpdateValue = 1;
00866     if (pFilter == NULL)
00867         UpdateEvery = height/100 + 1;
00868     else
00869     {
00870         // Ask the filter what the record size for this bitmap is and hence
00871         // what the allocation we have for progress bar updates are.
00872         // We will then have to update our progress bar by
00873         //  current scanline/total number of scanlines * allocation
00874         // so that we get update by a proportion of the value.
00875         // We can assume no interlacing as in native/web files this is turned off.
00876         INT32 RecordSize = pFilter->GetCurrentRecordSize();
00877         if (RecordSize == 0)
00878             RecordSize = 1;
00879         UpdateValue = RecordSize/height;
00880         UpdateEvery = 0;    // So it updates every time round the loop
00881     }
00882         
00883     int LastProgressUpdate = 0;
00884     
00885     // Work out the word/byte rounded line width rather than the pixel width
00886     INT32 WidthOfLine = DIBUtil::ScanlineSize( width, bpp );
00887 
00888     // Initialize the Compression routines
00889     fd->read( &c, 1 );
00890     ERROR3IF(c > GIFBITS,"Bad code size in GIFUtil ReadImage");
00891     
00892     if (LWZReadByte(fd, TRUE, c) < 0)
00893     {
00894         return FALSE;
00895     }
00896 
00897     while ((v = LWZReadByte(fd, FALSE, c)) >= 0 )
00898     {
00899         //GIFSetPixel(pBitsData, xpos, ypos, v);
00900         // Check that the specified pixel is within the bounds specified and that we
00901         // have a buffer to write to
00902 
00903 //      if ( !(((ypos < 0) || (ypos >= height)) || ((xpos < 0) || (xpos >= width))) && pBitsData )
00904 //      {
00905             // Our DIBs are the wrong way up so we must output the data from the last 
00906             // line of the image and go up to the start
00907             // -1 as height = 1 .. Height whereas y goes from 0 .. Height - 1
00908             // Use the word/byte rounded line width rather than the pixel width
00909             if (bpp == 8)
00910             {
00911                 // If 8 bpp then just use the whole byte straight
00912                 LPBYTE pData = pBitsData + xpos + ((height - 1 - ypos)  * WidthOfLine);
00913                 *(pData) = v;
00914             }
00915             else if (bpp == 4)
00916             {
00917                 // 4bpp so we must put the data into either the high or low nibble.
00918                 // This will be dependent on whether we are on an odd or even pixel.
00919                 // So test the LSBit of the curx, if set we will be odd.
00920                 // Only move onto next byte every other pixel hence curx/2.
00921                 LPBYTE pData = pBitsData + xpos/2 + ((height - 1 - ypos)  * WidthOfLine);
00922                 // Get whole present byte 
00923                 if (xpos & 1)
00924                     *(pData) = ((*(pData)) & 0xF0) | (v & 0x0F);    // add into low nibble 
00925                 else
00926                     *(pData) = ((*(pData)) & 0x0F) | ((v << 4) & 0xF0); // add into top nibble
00927             }
00928             else if (bpp == 1)
00929             {
00930                 // 1bpp so we must put the data into either the high or low nibble.
00931                 // This will be dependent on whether we are on an odd or even pixel.
00932                 // So test the LSBit of the curx, if set we will be odd.
00933                 // Only move onto next byte every other pixel hence curx/2.
00934                 LPBYTE pOutputBitmap = pBitsData + (xpos / 8) + ((height - 1 - ypos)  * WidthOfLine);
00935                 // Get whole present byte
00936                 UINT32 BitPosition = 7 - (xpos % 8);
00937                 BYTE SetBit = v << BitPosition;
00938                 BYTE Mask = ~(1 << BitPosition);
00939 
00940                 *pOutputBitmap = ((*pOutputBitmap) & Mask) | SetBit;
00941             }
00942             else
00943             {
00944                 ERROR3IF(TRUE, "GIFUtil::ReadImage() - Invalid bpp");
00945                 // bad bpp but should not get here so do nothing
00946             }
00947             
00948 //      }
00949         
00950         ++xpos;
00951         
00952         BOOL JobState = TRUE;
00953         // if we have reached the end of the current line then work out the next
00954         // line that we must do, if interlacing is on 
00955         if (xpos >= width)
00956         {
00957             xpos = 0;
00958             
00959             // Do different filing methods depending on whether interlaced on non-interlaced.
00960             if (interlace)
00961             {
00962                 // We are interlaced so learn not to count properly!
00963                 switch (pass)
00964                 {
00965                     case 0:
00966                     case 1:
00967                         ypos += 8; break;
00968                     case 2:
00969                         ypos += 4; break;
00970                     case 3:
00971                         ypos += 2; break;
00972                 }
00973 
00974                 while (ypos >= height && pass <= 3)
00975                 {
00976                     ++pass;
00977                     switch (pass)
00978                     {
00979                         case 1:
00980                             ypos = 4;
00981                             break;
00982                         case 2:
00983                             ypos = 2;
00984                             break;
00985                         case 3:
00986                             ypos = 1;
00987                             break;
00988                         default:
00989                             goto finish;
00990                     }
00991                 }
00992             }
00993             else
00994             {
00995                 // Non-interlaced case
00996                 ++ypos;
00997             }
00998 
00999             yposcount++;
01000             
01001             if (yposcount > (LastProgressUpdate + UpdateEvery))
01002             {
01003                 // Note the update point so that we know the next one  
01004                 LastProgressUpdate = yposcount;
01005 
01006                 // Now update the progress display, started with 100
01007                 if (pFilter == NULL)
01008                     JobState = ContinueSlowJob( (long)(100 * yposcount/height) );
01009                 else
01010                     JobState = pFilter->IncProgressBarCount(UpdateValue);
01011 
01012                 // If JobState is False then the user has probably pressed escape and we should
01013                 // immediately stop what we are doing. 
01014                 if (!JobState)
01015                 {
01016                     fd->GotError( _R(IDW_CANCELLEDBMPIMPORT) ); // Expects error set on cancel
01017                     return FALSE;
01018                 }
01019             }
01020         }
01021 
01022         if (ypos >= height)
01023             break;
01024     }
01025 
01026 finish:
01027     if (LWZReadByte(fd, FALSE, c) >= 0)
01028     {
01029         /* Ignore extra */
01030     }
01031 
01032     // Everything seemed to go ok 
01033     return TRUE;
01034 }
01035 
01036 
01037 /********************************************************************************************
01038 
01039 >   static void GIFUtil::GIFSetPixel(LPBYTE pBitsData, int x, int y, int colour)
01040 
01041     Author:     Neville Humphrys
01042     Created:    29/6/95
01043     Inputs:     
01044     Outputs:    
01045     Returns:    
01046     Purpose:    
01047     SeeAlso:    
01048 
01049 ********************************************************************************************/
01050 
01051 //void GIFUtil::GIFSetPixel(LPBYTE pBitsData, int x, int y, int colour)
01052 //{
01053 //  // Check that the specified pixel is within the bounds specified and that we
01054 //  // have a buffer to write to
01055 //  if ( !(((y < 0) || (y >= Height)) || ((x < 0) || (x >= Width))) && pBitsData )
01056 //  {
01057 //      // Our DIBs are the wrong way up so we must output the data from the last 
01058 //      // line of the image and go up to the start
01059 //      // -1 as height = 1 .. Height whereas y goes from 0 .. Height - 1
01060 //      // Use the word/byte rounded line width rather than the pixel width
01061 //      LPBYTE pData = pBitsData + x + ((Height - 1 - y)  * WidthOfLine);
01062 //      *(pData) = colour;
01063 //  }
01064 //}
01065 
01066 /********************************************************************************************
01067 
01068 >   static BOOL ReadColourMap(CCLexFile *fd, Palette& NewPalette)
01069 
01070     Author:     Colin
01071     Created:    29/6/95
01072     Inputs:     fd          pointer to a CCLexFile to read the data from
01073     Outputs:    -
01074     Returns:    True if worked ok, False otherwise.
01075     Purpose:    
01076     SeeAlso:    
01077 
01078 ********************************************************************************************/
01079 BOOL Palette::SetSize(const UINT32 nNewSize)
01080 {
01081     if (nNewSize == m_nSize) return TRUE;
01082     if (m_pColours != NULL)
01083     {
01084         delete [] m_pColours;
01085     }
01086     if (nNewSize == 0)
01087     {
01088         m_pColours = NULL;
01089         return TRUE;
01090     }
01091     m_pColours = new RGBQUAD[nNewSize];
01092 //  m_StartIterator;
01093 //  m_EndIterator;
01094     return (m_pColours == NULL) ? FALSE : TRUE;
01095 }
01096 
01097 
01098 static BOOL ReadColourMap(CCLexFile *fd, Palette& NewPalette)
01099 {
01100     GIFRGBTRIPLE rgb;
01101     PaletteIterator PalColour;
01102     for (PalColour = NewPalette.Start(); PalColour < NewPalette.End(); ++PalColour)
01103     {
01104         fd->read(&rgb, sizeof(GIFRGBTRIPLE));
01105 
01106         PalColour.SetBlue(rgb.grgbtBlue);
01107         PalColour.SetGreen(rgb.grgbtGreen);
01108         PalColour.SetRed(rgb.grgbtRed);
01109         PalColour.SetTransparent(0);
01110     }
01111     
01112     return TRUE;
01113 }
01114 
01115 /********************************************************************************************
01116 
01117 >   BOOL GIFUtil::ProcessHeader()
01118 
01119     Author:     Colin
01120     Created:    11/06/96
01121     Inputs:     
01122     Outputs:    
01123     Returns:    
01124     Purpose:    
01125     SeeAlso:    
01126 
01127 ********************************************************************************************/
01128 BOOL GIFUtil::ProcessHeader()
01129 {
01130     ERROR3("GIFUtil::ProcessHeader : not implemented");
01131     if (TRUE) return FALSE;
01132 
01133     CCLexFile* File;
01134     GIFINFOHEADER Header;
01135     // This is really sizeof(GIFINFOHEADER) but this returns 14 instead of 13
01136     // as it rounds to the nearest word boundary
01137     const size_t HeaderSize = sizeof(char)* 6 + sizeof(WORD) * 2 + sizeof(BYTE) * 3;
01138 //  const size_t HeaderSize = (sizeof(GIFINFOHEADER)* sizeof(WORD)) / sizeof(WORD);
01139 
01140     File->read( &Header, HeaderSize );
01141     if (File->bad())
01142         File->GotError( _R(IDE_FORMATNOTSUPPORTED) );
01143 
01144     // Just double check that the signature is correct, if not then fail immediately
01145     if (
01146         ( strncmp( Header.giName, "GIF89a", 6 ) != 0 ) &&
01147         ( strncmp( Header.giName, "GIF87a", 6 ) != 0 )
01148         )
01149         File->GotError( _R(IDE_BADFORMAT) );
01150 
01151     // Note the overall size of the GIF from the header, may be the animation size
01152     m_GlobalWidth       = Header.giWidth;
01153     m_GlobalHeight      = Header.giHeight;
01154 //  int ColorResolution = (((Header.giFlags >> 4) & 0x07) + 1);
01155 //  int AspectRatio     = Header.giAspect;
01156 
01157     UINT32 nGlobalPaletteSize   = 2 << (Header.giFlags & 0x07);
01158 //  m_nBackground = Header.giBackground;
01159 
01160     // Check if we have a global colour map present or not, if so then read it in.
01161     if (BitSet(Header.giFlags, GLOBALCOLOURMAP))
01162     {
01163         // Read in the global colour map into a palette structure for possible later use
01164         if (m_GlobalPalette.SetSize(nGlobalPaletteSize) == FALSE)
01165         {
01166             ERROR3("GIFUtil::ProcessHeader() - can't renew palette");
01167             return FALSE;
01168         }
01169 		::ReadColourMap(File, m_GlobalPalette);
01170     }
01171     m_nCurrentBitmap = 0;
01172 }
01173 
01174 
01175 /********************************************************************************************
01176 
01177 >   BOOL GIFUtil::ProcessImageBlock(CCLexFile* File, LPBITMAPINFO *Info, LPBYTE *Bits,
01178                                     UINT32 * pLeftOffset = NULL, UINT32 * pTopOffset = NULL,
01179                                     BOOL * pLocalPalette = NULL)
01180 
01181     Author:     Colin
01182     Created:    11/06/96
01183     Inputs:     
01184     Outputs:    
01185     Returns:    
01186     Purpose:    
01187     SeeAlso:    
01188 
01189 ********************************************************************************************/
01190 BOOL GIFUtil::ProcessImageBlock(CCLexFile* File, LPBITMAPINFO *Info, LPBYTE *Bits,
01191                                 UINT32 * pLeftOffset, UINT32 * pTopOffset, BOOL * pLocalPalette)
01192 {
01193     GIFIMAGEBLOCK ImageBlock;
01194     // This is really sizeof(GIFIMAGEBLOCK) but this returns 10 instead of 9 as it rounds to the nearest word boundary
01195     const size_t ImageHeaderSize = sizeof(WORD) * 4 + sizeof(BYTE) * 1;
01196     File->read( &ImageBlock, ImageHeaderSize );
01197 
01198     // Check whether we there is a local colour map or not
01199     // If there is use that otherwise use the global one read in earlier        
01200     BOOL UseLocalColourMap = BitSet(ImageBlock.gibFlags, LOCALCOLOURMAP);
01201     int LocalColourMapSize = 1 << ((ImageBlock.gibFlags & 0x07) + 1);
01202                     
01203     int Width   = ImageBlock.gibWidth;  //LM_to_uint(buf[4],buf[5]);
01204     int Height  = ImageBlock.gibDepth;  //LM_to_uint(buf[6],buf[7]);
01205     
01206     // If the user asked for them and they are within the animation's boundaries
01207     // Then return the offsets to the user
01208     if (pLeftOffset && ImageBlock.gibLeft < m_GlobalWidth)
01209         *pLeftOffset = ImageBlock.gibLeft;
01210     if (pTopOffset && ImageBlock.gibTop < m_GlobalHeight)
01211         *pTopOffset = ImageBlock.gibTop;
01212     if (pLocalPalette)
01213         *pLocalPalette = UseLocalColourMap;
01214     
01215     Interlace       = BitSet(ImageBlock.gibFlags, INTERLACE);
01216 
01217     int ColoursInPalette = UseLocalColourMap ? LocalColourMapSize : GlobalPaletteSize;
01218     int BitsPerPixel;
01219     if (ColoursInPalette > 16)
01220         BitsPerPixel = 8;
01221     else if (ColoursInPalette > 2)
01222         BitsPerPixel = 4;
01223     else
01224         BitsPerPixel = 1;
01225 
01226 TRACEUSER("Neville", _T("Colours in palette = %d bpp = %d\n"), ColoursInPalette, BitsPerPixel );
01227 
01228     // Allocate the space that we require for this bitmap
01229     // Sanity checks on the file that we have been asked to load.
01230     if ((BitsPerPixel != 8) && (BitsPerPixel != 4) && (BitsPerPixel != 1) && (Width != 0) && (Height != 0))
01231         File->GotError( _R(IDE_FORMATNOTSUPPORTED) );
01232 
01233     // we know what sort of bitmap we are - lets allocate a new LPBITMAPINFO and some bytes
01234     *Info = AllocDIB( Width, Height, BitsPerPixel, Bits, NULL );
01235 
01236     if (*Info == NULL || *Bits == NULL)
01237         File->GotError( _R(IDS_OUT_OF_MEMORY) );
01238 
01239     // if the clrUsed field is zero, put a sensible value in it
01240 
01241     BOOL ok = FALSE;
01242     if (UseLocalColourMap)
01243     {
01244         // There is a local colour table specified so read this directly into
01245         // the palette of the DIB
01246         if (LocalColourMapSize > (1 << BitsPerPixel))
01247                 File->GotError( _R(IDE_BADFORMAT) );
01248 
01249         ReadColourMap(File, LocalColourMapSize, (*Info)->bmiColors);
01250     }
01251     else
01252     {
01253         // No local palette so copy the global palette into the palette of the DIB, if 
01254         if (lpGlobalPalette && ((*Info)->bmiColors))
01255         {
01256             const size_t TotalPal = sizeof(RGBQUAD) * GlobalPaletteSize;
01257             memcpy((*Info)->bmiColors, lpGlobalPalette, TotalPal);
01258         }
01259     }
01260 
01261     // Note how many colours are in the palette
01262     (*Info)->bmiHeader.biClrUsed = ColoursInPalette;
01263 
01264     ok = ReadImage(File, *Bits, Width, Height, BitsPerPixel, Interlace);
01265     // Now read all that lovely data in
01266     if (!ok)
01267         File->GotError( _R(IDE_BADFORMAT) );
01268 
01269     return TRUE;
01270 }
01271 
01272 

Generated on Sat Nov 10 03:48:31 2007 for Camelot by  doxygen 1.4.4