image_libgif.C

00001 /*
00002  * This file is part of the "Archon" framework.
00003  * (http://files3d.sourceforge.net)
00004  *
00005  * Copyright © 2002 by Kristian Spangsege and Brian Kristiansen.
00006  *
00007  * Permission to use, copy, modify, and distribute this software and
00008  * its documentation under the terms of the GNU General Public License is
00009  * hereby granted. No representations are made about the suitability of
00010  * this software for any purpose. It is provided "as is" without express
00011  * or implied warranty. See the GNU General Public License
00012  * (http://www.gnu.org/copyleft/gpl.html) for more details.
00013  *
00014  * The characters in this file are ISO8859-1 encoded.
00015  *
00016  * The documentation in this file is in "Doxygen" style
00017  * (http://www.doxygen.org).
00018  */
00019 
00020 #include <stdio.h>
00021 #include <errno.h>
00022 #include <unistd.h>
00023 #include <iostream>
00024 #include <string>
00025 
00026 extern "C"
00027 {
00028 #include <gif_lib.h>
00029 }
00030 
00031 #include <archon/util/mutex.H>
00032 #include <archon/util/text.H>
00033 #include <archon/util/random.H>
00034 #include <archon/util/image.H>
00035 
00036 using namespace std;
00037 
00038 namespace Archon
00039 {
00040   namespace Utilities
00041   {
00042 
00043 OUCH OUCH OUCH It seems libungif is not thread-safe after all due to its global error code storage.
00044 
00057     struct FormatLibgif: Image::Format
00058     {
00059       string getSpecifier() const
00060       {
00061         return "gif";
00062       }
00063 
00064       bool checkSignature(Ref<Stream::Reader> r) const
00065       {
00066         LoadContext c(r, 0, 0); // No logging since we are only probing.
00067         LoadWrapper w;
00068         w.g = DGifOpen(&c, readCallback);
00069         return w.g;
00070       }
00071 
00072       bool checkSuffix(string s) const
00073       {
00074         return s == "gif";
00075       }
00076 
00077       Image load(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00078                  Logger *logger) const
00079         throw(Image::InvalidFormatException,
00080               IOException, UnexpectedException)
00081       {
00082         LoadContext c(r, tracker, logger);
00083         LoadWrapper w;
00084         w.g = DGifOpen(&c, readCallback);
00085         if(!w.g) error(&context);
00086 
00087       if(DGifSlurp(w.g) != GIF_OK) loadError(&context);
00088       if(w.g->ImageCount<1)
00089       {
00090         if(logger) logger->log("No images in GIF file");
00091         ARCHON_THROW1(Image::FormatException, "");
00092       }
00093 
00094       SavedImage *image = w.g->SavedImages;
00095 
00096       if(image->ImageDesc.Left + image->ImageDesc.Width > w.g->SWidth ||
00097          image->ImageDesc.Top + image->ImageDesc.Height > w.g->SHeight)
00098       {
00099         if(logger) logger->log("Image does not fit on canvas");
00100         ARCHON_THROW1(Image::FormatException, "");
00101       }
00102 
00103       ColorMapObject *palette = image->ImageDesc.ColorMap;
00104       if(!palette) palette = w.g->SColorMap;
00105       if(!palette)
00106       {
00107         if(logger) logger->log("No palette in GIF file");
00108         ARCHON_THROW1(Image::FormatException, "");
00109       }
00110       if(w.g->SBackGroundColor >= palette->ColorCount)
00111       {
00112         if(logger) logger->log("Background color is not in palette");
00113         ARCHON_THROW1(Image::FormatException, "");
00114       }
00115 
00116       /*
00117        * Graphic Control Extension
00118        *
00119        * Byte index 0:
00120        *  Bit 1:   Enable transparency
00121        *  Bit 2:   User Input Flag (What is this?)
00122        *  Bit 3-5: Disposal Method (What to do about this frame when
00123        *           the next frame is to be displayed)
00124        *   0 -> no disposal specified
00125        *   1 -> do not dispose (background for next frame)
00126        *   2 -> restore to background color
00127        *   3 -> restore to previous (with Disposal Method 0/1)
00128        * Byte index 1-2:
00129        *  Animation delay
00130        * Byte index 3:
00131        *  Index of transparent color
00132        *
00133        * \sa http://www.webreference.com/content/studio/disposal.html
00134        * \sa The GIFtrans source code
00135        */
00136       // Check for transparency and comment
00137       int transparentIndex = -1;
00138       for(int i=0; i<image->ExtensionBlockCount; ++i)
00139       {
00140         ExtensionBlock *ext = image->ExtensionBlocks + i;
00141         int n = ext->ByteCount;
00142         switch(ext->Function)
00143         {
00144         case COMMENT_EXT_FUNC_CODE:
00145           {
00146             int m=0;
00147             while(m<n && ext->Bytes[m]) ++m;
00148             if(m && !comment.empty()) comment += "\n";
00149             comment += string(ext->Bytes, m);
00150           }
00151           break;
00152         case GRAPHICS_EXT_FUNC_CODE:
00153           {
00154             if(n>=4 && ext->Bytes[0]&1)
00155               transparentIndex = static_cast<unsigned char>(ext->Bytes[3]);
00156           }
00157           break;
00158         }
00159       }
00160       bool transparent = transparentIndex >= 0;
00161       //cerr << "Transparency: " << (!transparent ? string("none") : Text::toString(transparentIndex)) << "\n";
00162 
00163       width            = w.g->SWidth;
00164       height           = w.g->SHeight;
00165       bitsPerComponent = 8;
00166       components       = transparent ? components_rgba : components_rgb;
00167       charsPerPixel    = (bitsPerComponent*components+7)/8;
00168       charsPerRow      = charsPerPixel*width;
00169 
00170       DataHolder dataHolder(new unsigned char[charsPerRow*height]);
00171 
00172       // Fill canvas with background color
00173       {
00174         GifColorType *color = palette->Colors + w.g->SBackGroundColor;
00175         unsigned char alpha = static_cast<unsigned char>
00176           (w.g->SBackGroundColor == transparentIndex ? 0 : 255);
00177 
00178         unsigned char *p = dataHolder.ptr;
00179         for(unsigned x=0; x<width; ++x)
00180         {
00181           *p++ = static_cast<unsigned char>(color->Red);
00182           *p++ = static_cast<unsigned char>(color->Green);
00183           *p++ = static_cast<unsigned char>(color->Blue);
00184           if(transparent) *p++ = alpha;
00185         }
00186         for(unsigned y=1; y<height; ++y) memcpy(dataHolder.ptr+y*charsPerRow, dataHolder.ptr, charsPerRow);
00187       }
00188 
00189       // Paste first image onto canvas
00190       char *src = image->RasterBits;
00191       unsigned char *dst = dataHolder.ptr + ((height-1-image->ImageDesc.Top)*width + image->ImageDesc.Left)*charsPerPixel;
00192       if(image->ImageDesc.Interlace)
00193       {
00194         int offset[] = { 0, 4, 2, 1 };
00195         int step[]   = { 8, 8, 4, 2 };
00196         for(unsigned i=0; i<4; ++i) for(int y=offset[i]; y<image->ImageDesc.Height; y+=step[i])
00197         {
00198           unsigned char *d = dst - charsPerRow*y;
00199           for(int x=0; x<image->ImageDesc.Width; ++x)
00200           {
00201             if(static_cast<unsigned char>(*src) >= palette->ColorCount)
00202             {
00203               if(logger) logger->log("Pixel color is not in palette");
00204               ARCHON_THROW1(Image::FormatException, "");
00205             }
00206             int s = static_cast<unsigned char>(*src);
00207             GifColorType *color = palette->Colors + s;
00208             *d++ = static_cast<unsigned char>(color->Red);
00209             *d++ = static_cast<unsigned char>(color->Green);
00210             *d++ = static_cast<unsigned char>(color->Blue);
00211             if(transparent) *d++ = static_cast<unsigned char>
00212                               (s == transparentIndex ? 0 : 255);
00213             ++src;
00214           }
00215         }
00216       }
00217       else
00218       {
00219         for(int y=0; y<image->ImageDesc.Height; ++y)
00220         {
00221           unsigned char *d = dst;
00222           for(int x=0; x<image->ImageDesc.Width; ++x)
00223           {
00224             if(static_cast<unsigned char>(*src) >= palette->ColorCount)
00225             {
00226               if(logger) logger->log("Pixel color is not in palette");
00227               ARCHON_THROW1(Image::FormatException, "");
00228             }
00229             int s = static_cast<unsigned char>(*src);
00230             GifColorType *color = palette->Colors + s;
00231             *d++ = static_cast<unsigned char>(color->Red);
00232             *d++ = static_cast<unsigned char>(color->Green);
00233             *d++ = static_cast<unsigned char>(color->Blue);
00234             if(transparent) *d++ = static_cast<unsigned char>
00235                               (s == transparentIndex ? 0 : 255);
00236             ++src;
00237           }
00238           dst -= charsPerRow;
00239         }
00240       }
00241 
00242       data = dataHolder.ptr;
00243       dataHolder.ptr = 0;
00244       }
00245 
00246     private:
00247 
00248       struct Context
00249       {
00250         Image::ProgressTracker *tracker;
00251         Logger *logger;       // For warnings
00252         int errorType;        // 0 for none, 1 for ThreadTerminatedException,
00253                               // 2 for IOException, 3 for UnexpectedException
00254                               // from the stream, 4 for libpng initiated
00255                               // error or invalid format detected.
00256         string errorMessage;  // For fatal errors
00257         string errorLocation; // Source file location from exception
00258         Context(Image::ProgressTracker *tracker, Logger *logger):
00259           tracker(tracker), logger(logger), errorType(0)  {}
00260       };
00261 
00262       struct LoadContext: Context
00263       {
00264         Ref<Stream::Reader> reader;
00265         LoadContext(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00266                     Logger *logger): Context(tracker, logger), reader(reader) {}
00267       };
00268 
00269       struct SaveContext: Context
00270       {
00271         Ref<Stream::Writer> writer;
00272         SaveContext(Ref<Stream::Writer> writer, Image::ProgressTracker *tracker,
00273                     Logger *logger): Context(tracker, logger), writer(writer) {}
00274       };
00275 
00276       struct LoadWrapper
00277       {
00278         GifFileType *g;
00279         LoadWrapper(): g(0) {}
00280         ~LoadWrapper()
00281         {
00282           if(!g) return;
00283           LoadContext *context = static_cast<LoadContext *>(g->UserData);
00284           if(DGifCloseFile(g) != GIF_OK) loadError(context);
00285         }
00286       };
00287 
00288       static int readCallback(GifFileType *g, GifByteType *buffer, int size) throw()
00289       {
00290         LoadContext *c = static_cast<LoadContext *>(g->UserData);
00291         try
00292         {
00293           return c->reader->readAll(reinterpret_cast<char *>(buffer), size);
00294         }
00295         catch(ThreadTerminatedException &e)
00296         {
00297           c->errorType     = 1;
00298           c->errorLocation = e.getLocation();
00299         }
00300         catch(IOException &e)
00301         {
00302           c->errorType     = 2;
00303           c->errorLocation = e.getLocation();
00304           c->errorMessage  = e.getMessage();
00305         }
00306         catch(UnexpectedException &e)
00307         {
00308           c->errorType     = 3;
00309           c->errorLocation = e.getLocation();
00310           c->errorMessage  = e.getMessage();
00311         }
00312 
00313         return -1;
00314       }
00315 
00316 
00317 
00318 
00319 
00320 
00321 
00322       const char *gifErrorMessage(int i)
00323       {
00324         switch(i)
00325         {
00326         case E_GIF_ERR_OPEN_FAILED:
00327           return "Failed to open given file";
00328         case E_GIF_ERR_WRITE_FAILED:
00329           return "Failed to Write to given file";
00330         case E_GIF_ERR_HAS_SCRN_DSCR:
00331           return "Screen Descriptor already been set";
00332         case E_GIF_ERR_HAS_IMAG_DSCR:
00333           return "Image Descriptor is still active";
00334         case E_GIF_ERR_NO_COLOR_MAP:
00335           return "Neither Global Nor Local color map";
00336         case E_GIF_ERR_DATA_TOO_BIG:
00337           return "#Pixels bigger than Width * Height";
00338         case E_GIF_ERR_NOT_ENOUGH_MEM:
00339           return "Fail to allocate required memory";
00340         case E_GIF_ERR_DISK_IS_FULL:
00341           return "Write failed (disk full?)";
00342         case E_GIF_ERR_CLOSE_FAILED:
00343           return "Failed to close given file";
00344         case E_GIF_ERR_NOT_WRITEABLE:
00345           return "Given file was not opened for write";
00346         case D_GIF_ERR_OPEN_FAILED:
00347           return "Failed to open given file";
00348         case D_GIF_ERR_READ_FAILED:
00349           return "Failed to Read from given file";
00350         case D_GIF_ERR_NOT_GIF_FILE:
00351           return "Given file is NOT GIF file";
00352         case D_GIF_ERR_NO_SCRN_DSCR:
00353           return "No Screen Descriptor detected";
00354         case D_GIF_ERR_NO_IMAG_DSCR:
00355           return "No Image Descriptor detected";
00356         case D_GIF_ERR_NO_COLOR_MAP:
00357           return "Neither Global Nor Local color map";
00358         case D_GIF_ERR_WRONG_RECORD:
00359           return "Wrong record type detected";
00360         case D_GIF_ERR_DATA_TOO_BIG:
00361           return "#Pixels bigger than Width * Height";
00362         case D_GIF_ERR_NOT_ENOUGH_MEM:
00363           return "Fail to allocate required memory";
00364         case D_GIF_ERR_CLOSE_FAILED:
00365           return "Failed to close given file";
00366         case D_GIF_ERR_NOT_READABLE:
00367           return "Given file was not opened for read";
00368         case D_GIF_ERR_IMAGE_DEFECT:
00369           return "Image is defective, decoding aborted";
00370         case D_GIF_ERR_EOF_TOO_SOON:
00371           return "Image EOF detected, before image complete";
00372         default:
00373           ARCHON_THROW1(Image::InternalException, "Unexpected GIF error");
00374         }
00375       }
00376 
00377       void loadError(LoadContext *context)
00378       {
00379         if(context->terminated) ARCHON_THROW(ThreadTerminatedException);
00380         if(context->logger) context->logger->log(gifErrorMessage(GifLastError()));
00381         ARCHON_THROW1(Image::FormatException, "");
00382       }
00383 
00384 
00385     }
00386 
00387     void Image::loadGif(Ref<Stream::Reader> reader, Logger *logger,
00388                         LoadTracker *loadTracker)
00389     {
00390     }
00391 
00392     void Image::saveGif(Ref<Stream::Writer> writer, Logger *logger) const
00393     {
00394       ARCHON_THROW1(InternalException, "Save GIF is not yet supported");
00395     }
00396   }
00397 }

Generated on Sun Jul 30 22:55:44 2006 for Archon by  doxygen 1.4.4