1377 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1377 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* vim:expandtab:ts=2 sw=2:
 | 
						|
*/
 | 
						|
/*  Grafx2 - The Ultimate 256-color bitmap paint program
 | 
						|
 | 
						|
	Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.
 | 
						|
 | 
						|
    Grafx2 is free software; you can redistribute it and/or
 | 
						|
    modify it under the terms of the GNU General Public License
 | 
						|
    as published by the Free Software Foundation; version 2
 | 
						|
    of the License.
 | 
						|
 | 
						|
    Grafx2 is distributed in the hope that it will be useful,
 | 
						|
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
    GNU General Public License for more details.
 | 
						|
 | 
						|
    You should have received a copy of the GNU General Public License
 | 
						|
    along with Grafx2; if not, see <http://www.gnu.org/licenses/>
 | 
						|
*/
 | 
						|
 | 
						|
///@file giformat.c
 | 
						|
/// Saving and loading GIF
 | 
						|
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include "struct.h"
 | 
						|
#include "global.h"
 | 
						|
#include "oldies.h"
 | 
						|
#include "io.h"
 | 
						|
#include "loadsave.h"
 | 
						|
#include "loadsavefuncs.h"
 | 
						|
#include "gfx2mem.h"
 | 
						|
#include "gfx2log.h"
 | 
						|
 | 
						|
#ifndef MIN
 | 
						|
#define MIN(a,b) ((a)<(b)?(a):(b))
 | 
						|
#endif
 | 
						|
#ifndef MAX
 | 
						|
#define MAX(a,b) ((a)>(b)?(a):(b))
 | 
						|
#endif
 | 
						|
 | 
						|
/**
 | 
						|
 * @defgroup GIF GIF format
 | 
						|
 * @ingroup loadsaveformats
 | 
						|
 * Graphics Interchange Format
 | 
						|
 *
 | 
						|
 * The GIF format uses LZW compression and stores indexed color pictures
 | 
						|
 * up to 256 colors. It has the ability to store several pictures in the same
 | 
						|
 * file : GrafX2 takes advantage of this feature for storing layered images
 | 
						|
 * and animations.
 | 
						|
 *
 | 
						|
 * GrafX2 implements GIF89a :
 | 
						|
 * https://www.w3.org/Graphics/GIF/spec-gif89a.txt
 | 
						|
 *
 | 
						|
 * @{
 | 
						|
 */
 | 
						|
 | 
						|
/// Logical Screen Descriptor Block
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  word Width;   ///< Width of the complete image area
 | 
						|
  word Height;  ///< Height of the complete image area
 | 
						|
  byte Resol;   ///< Informations about the resolution (and other)
 | 
						|
  byte Backcol; ///< Proposed background color
 | 
						|
  byte Aspect;  ///< Informations about aspect ratio. Ratio = (Aspect + 15) / 64
 | 
						|
} T_GIF_LSDB;
 | 
						|
 | 
						|
/// Image Descriptor Block
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  word Pos_X;         ///< X offset where the image should be pasted
 | 
						|
  word Pos_Y;         ///< Y offset where the image should be pasted
 | 
						|
  word Image_width;   ///< Width of image
 | 
						|
  word Image_height;  ///< Height of image
 | 
						|
  byte Indicator;     ///< Misc image information
 | 
						|
  byte Nb_bits_pixel; ///< Nb de bits par pixel
 | 
						|
} T_GIF_IDB;
 | 
						|
 | 
						|
/// Graphic Control Extension
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  byte Block_identifier;  ///< 0x21
 | 
						|
  byte Function;          ///< 0xF9
 | 
						|
  byte Block_size;        ///< 4
 | 
						|
  byte Packed_fields;     ///< 11100000 : Reserved
 | 
						|
                          ///< 00011100 : Disposal method
 | 
						|
                          ///< 00000010 : User input flag
 | 
						|
                          ///< 00000001 : Transparent flag
 | 
						|
  word Delay_time;        ///< Time for this frame to stay displayed
 | 
						|
  byte Transparent_color; ///< Which color index acts as transparent
 | 
						|
  word Block_terminator;  ///< 0x00
 | 
						|
} T_GIF_GCE;
 | 
						|
 | 
						|
enum DISPOSAL_METHOD
 | 
						|
{
 | 
						|
  DISPOSAL_METHOD_UNDEFINED = 0,
 | 
						|
  DISPOSAL_METHOD_DO_NOT_DISPOSE = 1,
 | 
						|
  DISPOSAL_METHOD_RESTORE_BGCOLOR = 2,
 | 
						|
  DISPOSAL_METHOD_RESTORE_PREVIOUS = 3,
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/// Test if a file is GIF format
 | 
						|
void Test_GIF(T_IO_Context * context, FILE * file)
 | 
						|
{
 | 
						|
  char signature[6];
 | 
						|
 | 
						|
  (void)context;
 | 
						|
  File_error=1;
 | 
						|
 | 
						|
  if (Read_bytes(file,signature,6))
 | 
						|
  {
 | 
						|
    /// checks if the signature (6 first bytes) is either GIF87a or GIF89a
 | 
						|
    if ((!memcmp(signature,"GIF87a",6))||(!memcmp(signature,"GIF89a",6)))
 | 
						|
      File_error=0;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// -- Lire un fichier au format GIF -----------------------------------------
 | 
						|
 | 
						|
typedef struct {
 | 
						|
  word nb_bits;        ///< bits for a code
 | 
						|
  word remainder_bits; ///< available bits in @ref last_byte field
 | 
						|
  byte remainder_byte; ///< Remaining bytes in current block
 | 
						|
  word current_code;   ///< current code (generally the one just read)
 | 
						|
  byte last_byte;      ///< buffer byte for reading bits for codes
 | 
						|
  word pos_X;          ///< Current coordinates
 | 
						|
  word pos_Y;
 | 
						|
  word interlaced;     ///< interlaced flag
 | 
						|
  word pass;           ///< current pass in interlaced decoding
 | 
						|
  word stop;           ///< Stop flag (end of picture)
 | 
						|
} T_GIF_context;
 | 
						|
 | 
						|
 | 
						|
/// Reads the next code (GIF.nb_bits bits)
 | 
						|
static word GIF_get_next_code(FILE * GIF_file, T_GIF_context * gif)
 | 
						|
{
 | 
						|
  word nb_bits_to_process = gif->nb_bits;
 | 
						|
  word nb_bits_processed = 0;
 | 
						|
  word current_nb_bits;
 | 
						|
 | 
						|
  gif->current_code = 0;
 | 
						|
 | 
						|
  while (nb_bits_to_process)
 | 
						|
  {
 | 
						|
    if (gif->remainder_bits == 0) // Il ne reste plus de bits...
 | 
						|
    {
 | 
						|
      // Lire l'octet suivant:
 | 
						|
 | 
						|
      // Si on a atteint la fin du bloc de Raster Data
 | 
						|
      if (gif->remainder_byte == 0)
 | 
						|
      {
 | 
						|
        // Lire l'octet nous donnant la taille du bloc de Raster Data suivant
 | 
						|
        if(Read_byte(GIF_file, &gif->remainder_byte)!=1)
 | 
						|
        {
 | 
						|
          File_error=2;
 | 
						|
          return 0;
 | 
						|
        }
 | 
						|
        if (gif->remainder_byte == 0) // still nothing ? That is the end data block
 | 
						|
        {
 | 
						|
          File_error = 2;
 | 
						|
          GFX2_Log(GFX2_WARNING, "GIF 0 sized data block\n");
 | 
						|
          return gif->current_code;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if(Read_byte(GIF_file,&gif->last_byte)!=1)
 | 
						|
      {
 | 
						|
        File_error = 2;
 | 
						|
        GFX2_Log(GFX2_ERROR, "GIF failed to load data byte\n");
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
      gif->remainder_byte--;
 | 
						|
      gif->remainder_bits=8;
 | 
						|
    }
 | 
						|
 | 
						|
    current_nb_bits=(nb_bits_to_process<=gif->remainder_bits)?nb_bits_to_process:gif->remainder_bits;
 | 
						|
 | 
						|
    gif->current_code |= (gif->last_byte & ((1<<current_nb_bits)-1))<<nb_bits_processed;
 | 
						|
    gif->last_byte >>= current_nb_bits;
 | 
						|
    nb_bits_processed += current_nb_bits;
 | 
						|
    nb_bits_to_process -= current_nb_bits;
 | 
						|
    gif->remainder_bits -= current_nb_bits;
 | 
						|
  }
 | 
						|
 | 
						|
  return gif->current_code;
 | 
						|
}
 | 
						|
 | 
						|
/// Put a new pixel
 | 
						|
static void GIF_new_pixel(T_IO_Context * context, T_GIF_context * gif, T_GIF_IDB *idb, int is_transparent, byte color)
 | 
						|
{
 | 
						|
  if (!is_transparent || color!=context->Transparent_color)
 | 
						|
    Set_pixel(context, idb->Pos_X+gif->pos_X, idb->Pos_Y+gif->pos_Y,color);
 | 
						|
 | 
						|
  gif->pos_X++;
 | 
						|
 | 
						|
  if (gif->pos_X >= idb->Image_width)
 | 
						|
  {
 | 
						|
    gif->pos_X=0;
 | 
						|
 | 
						|
    if (!gif->interlaced)
 | 
						|
    {
 | 
						|
      gif->pos_Y++;
 | 
						|
      if (gif->pos_Y >= idb->Image_height)
 | 
						|
        gif->stop = 1;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      switch (gif->pass)
 | 
						|
      {
 | 
						|
        case 0 :
 | 
						|
        case 1 : gif->pos_Y+=8;
 | 
						|
                 break;
 | 
						|
        case 2 : gif->pos_Y+=4;
 | 
						|
                 break;
 | 
						|
        default: gif->pos_Y+=2;
 | 
						|
      }
 | 
						|
 | 
						|
      if (gif->pos_Y >= idb->Image_height)
 | 
						|
      {
 | 
						|
        switch(++(gif->pass))
 | 
						|
        {
 | 
						|
        case 1 : gif->pos_Y=4;
 | 
						|
                 break;
 | 
						|
        case 2 : gif->pos_Y=2;
 | 
						|
                 break;
 | 
						|
        case 3 : gif->pos_Y=1;
 | 
						|
                 break;
 | 
						|
        case 4 : gif->stop = 1;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/// Load GIF file
 | 
						|
void Load_GIF(T_IO_Context * context)
 | 
						|
{
 | 
						|
  FILE *GIF_file;
 | 
						|
  int image_mode = -1;
 | 
						|
  char signature[6];
 | 
						|
 | 
						|
  word * alphabet_stack;     // Pile de décodage d'une chaîne
 | 
						|
  word * alphabet_prefix;  // Table des préfixes des codes
 | 
						|
  word * alphabet_suffix;  // Table des suffixes des codes
 | 
						|
  word   alphabet_free;     // Position libre dans l'alphabet
 | 
						|
  word   alphabet_max;      // Nombre d'entrées possibles dans l'alphabet
 | 
						|
  word   alphabet_stack_pos; // Position dans la pile de décodage d'un chaîne
 | 
						|
 | 
						|
  T_GIF_context GIF;
 | 
						|
  T_GIF_LSDB LSDB;
 | 
						|
  T_GIF_IDB IDB;
 | 
						|
  T_GIF_GCE GCE;
 | 
						|
 | 
						|
  word nb_colors;       // Nombre de couleurs dans l'image
 | 
						|
  word color_index; // index de traitement d'une couleur
 | 
						|
  byte size_to_read; // Nombre de données à lire      (divers)
 | 
						|
  byte block_identifier;  // Code indicateur du type de bloc en cours
 | 
						|
  byte initial_nb_bits;   // Nb de bits au début du traitement LZW
 | 
						|
  word special_case=0;       // Mémoire pour le cas spécial
 | 
						|
  word old_code=0;       // Code précédent
 | 
						|
  word byte_read;         // Sauvegarde du code en cours de lecture
 | 
						|
  word value_clr;        // Valeur <=> Clear tables
 | 
						|
  word value_eof;        // Valeur <=> End d'image
 | 
						|
  long file_size;
 | 
						|
  int number_LID; // Nombre d'images trouvées dans le fichier
 | 
						|
  int current_layer = 0;
 | 
						|
  int last_delay = 0;
 | 
						|
  byte is_transparent = 0;
 | 
						|
  byte is_looping=0;
 | 
						|
  enum PIXEL_RATIO ratio;
 | 
						|
  byte disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR;
 | 
						|
 | 
						|
  byte previous_disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR;
 | 
						|
  word previous_width=0;
 | 
						|
  word previous_height=0;
 | 
						|
  word previous_pos_x=0;
 | 
						|
  word previous_pos_y=0;
 | 
						|
 | 
						|
  /////////////////////////////////////////////////// FIN DES DECLARATIONS //
 | 
						|
 | 
						|
 | 
						|
  number_LID=0;
 | 
						|
 | 
						|
  if ((GIF_file=Open_file_read(context)))
 | 
						|
  {
 | 
						|
    file_size=File_length_file(GIF_file);
 | 
						|
    if ( (Read_bytes(GIF_file,signature,6)) &&
 | 
						|
         ( (memcmp(signature,"GIF87a",6)==0) ||
 | 
						|
           (memcmp(signature,"GIF89a",6)==0) ) )
 | 
						|
    {
 | 
						|
 | 
						|
      // Allocation de mémoire pour les tables & piles de traitement:
 | 
						|
      alphabet_stack  = (word *)GFX2_malloc(4096*sizeof(word));
 | 
						|
      alphabet_prefix = (word *)GFX2_malloc(4096*sizeof(word));
 | 
						|
      alphabet_suffix = (word *)GFX2_malloc(4096*sizeof(word));
 | 
						|
 | 
						|
      if (Read_word_le(GIF_file,&(LSDB.Width))
 | 
						|
      && Read_word_le(GIF_file,&(LSDB.Height))
 | 
						|
      && Read_byte(GIF_file,&(LSDB.Resol))
 | 
						|
      && Read_byte(GIF_file,&(LSDB.Backcol))
 | 
						|
      && Read_byte(GIF_file,&(LSDB.Aspect))
 | 
						|
        )
 | 
						|
      {
 | 
						|
        // Lecture du Logical Screen Descriptor Block réussie:
 | 
						|
 | 
						|
        Original_screen_X=LSDB.Width;
 | 
						|
        Original_screen_Y=LSDB.Height;
 | 
						|
 | 
						|
        ratio=PIXEL_SIMPLE;          //  (49 + 15) / 64 = 1:1
 | 
						|
        if (LSDB.Aspect != 0) {
 | 
						|
          if (LSDB.Aspect < 25)      //  (17 + 15) / 64 = 1:2
 | 
						|
            ratio=PIXEL_TALL;
 | 
						|
          else if (LSDB.Aspect < 41) //  (33 + 15) / 64 = 3:4
 | 
						|
            ratio=PIXEL_TALL3;
 | 
						|
          else if (LSDB.Aspect > 82) // (113 + 15) / 64 = 2:1
 | 
						|
            ratio=PIXEL_WIDE;
 | 
						|
        }
 | 
						|
 | 
						|
        Pre_load(context, LSDB.Width,LSDB.Height,file_size,FORMAT_GIF,ratio,(LSDB.Resol&7)+1);
 | 
						|
 | 
						|
        // Palette globale dispo = (LSDB.Resol  and $80)
 | 
						|
        // Profondeur de couleur =((LSDB.Resol  and $70) shr 4)+1
 | 
						|
        // Nombre de bits/pixel  = (LSDB.Resol  and $07)+1
 | 
						|
        // Ordre de Classement   = (LSDB.Aspect and $80)
 | 
						|
 | 
						|
        nb_colors=(1 << ((LSDB.Resol & 0x07)+1));
 | 
						|
        if (LSDB.Resol & 0x80)
 | 
						|
        {
 | 
						|
          // Palette globale dispo:
 | 
						|
 | 
						|
          if (Config.Clear_palette)
 | 
						|
            memset(context->Palette,0,sizeof(T_Palette));
 | 
						|
 | 
						|
          // Load the palette
 | 
						|
          for(color_index=0;color_index<nb_colors;color_index++)
 | 
						|
          {
 | 
						|
            Read_byte(GIF_file,&(context->Palette[color_index].R));
 | 
						|
            Read_byte(GIF_file,&(context->Palette[color_index].G));
 | 
						|
            Read_byte(GIF_file,&(context->Palette[color_index].B));
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        // On lit un indicateur de block
 | 
						|
        Read_byte(GIF_file,&block_identifier);
 | 
						|
        while (block_identifier!=0x3B && !File_error)
 | 
						|
        {
 | 
						|
          switch (block_identifier)
 | 
						|
          {
 | 
						|
            case 0x21: // Bloc d'extension
 | 
						|
            {
 | 
						|
              byte function_code;
 | 
						|
              // Lecture du code de fonction:
 | 
						|
              Read_byte(GIF_file,&function_code);
 | 
						|
              // Lecture de la taille du bloc:
 | 
						|
              Read_byte(GIF_file,&size_to_read);
 | 
						|
              while (size_to_read!=0 && !File_error)
 | 
						|
              {
 | 
						|
                switch(function_code)
 | 
						|
                {
 | 
						|
                  case 0xFE: // Comment Block Extension
 | 
						|
                    // On récupère le premier commentaire non-vide,
 | 
						|
                    // on jette les autres.
 | 
						|
                    if (context->Comment[0]=='\0')
 | 
						|
                    {
 | 
						|
                      int nb_char_to_keep = MIN(size_to_read, COMMENT_SIZE);
 | 
						|
 | 
						|
                      Read_bytes(GIF_file,context->Comment,nb_char_to_keep);
 | 
						|
                      context->Comment[nb_char_to_keep+1]='\0';
 | 
						|
                      // Si le commentaire etait trop long, on fait avance-rapide
 | 
						|
                      // sur la suite.
 | 
						|
                      if (size_to_read>nb_char_to_keep)
 | 
						|
                        fseek(GIF_file,size_to_read-nb_char_to_keep,SEEK_CUR);
 | 
						|
                    }
 | 
						|
                    // Lecture de la taille du bloc suivant:
 | 
						|
                    Read_byte(GIF_file,&size_to_read);
 | 
						|
                    break;
 | 
						|
                  case 0xF9: // Graphics Control Extension
 | 
						|
                    // Prévu pour la transparence
 | 
						|
                    if ( Read_byte(GIF_file,&(GCE.Packed_fields))
 | 
						|
                      && Read_word_le(GIF_file,&(GCE.Delay_time))
 | 
						|
                      && Read_byte(GIF_file,&(GCE.Transparent_color)))
 | 
						|
                    {
 | 
						|
                      previous_disposal_method = disposal_method;
 | 
						|
                      disposal_method = (GCE.Packed_fields >> 2) & 7;
 | 
						|
                      last_delay = GCE.Delay_time;
 | 
						|
                      context->Transparent_color= GCE.Transparent_color;
 | 
						|
                      is_transparent = GCE.Packed_fields & 1;
 | 
						|
                      GFX2_Log(GFX2_DEBUG, "GIF Graphics Control Extension : transp=%d (color #%u) delay=%ums disposal_method=%d\n", is_transparent, GCE.Transparent_color, 10*GCE.Delay_time, disposal_method);
 | 
						|
                      if (number_LID == 0)
 | 
						|
                        context->Background_transparent = is_transparent;
 | 
						|
                      is_transparent &= is_looping;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                      File_error=2;
 | 
						|
                    // Lecture de la taille du bloc suivant:
 | 
						|
                    Read_byte(GIF_file,&size_to_read);
 | 
						|
                    break;
 | 
						|
 | 
						|
                  case 0xFF: // Application Extension
 | 
						|
                    // Normally, always a 11-byte block
 | 
						|
                    if (size_to_read == 0x0B)
 | 
						|
                    {
 | 
						|
                      char aeb[0x0B];
 | 
						|
                      Read_bytes(GIF_file,aeb, 0x0B);
 | 
						|
                      GFX2_Log(GFX2_DEBUG, "GIF extension \"%.11s\"\n", aeb);
 | 
						|
                      if (File_error)
 | 
						|
                        ;
 | 
						|
                      else if (!memcmp(aeb,"NETSCAPE2.0",0x0B))
 | 
						|
                      {
 | 
						|
                        is_looping=1;
 | 
						|
                        // The well-known Netscape extension.
 | 
						|
                        // Load as an animation
 | 
						|
                        Set_image_mode(context, IMAGE_MODE_ANIMATION);
 | 
						|
                        // Skip sub-block
 | 
						|
                        do
 | 
						|
                        {
 | 
						|
                          if (! Read_byte(GIF_file,&size_to_read))
 | 
						|
                            File_error=1;
 | 
						|
                          fseek(GIF_file,size_to_read,SEEK_CUR);
 | 
						|
                        } while (!File_error && size_to_read!=0);
 | 
						|
                      }
 | 
						|
                      else if (!memcmp(aeb,"GFX2PATH\x00\x00\x00",0x0B))
 | 
						|
                      {
 | 
						|
                        // Original file path
 | 
						|
                        Read_byte(GIF_file,&size_to_read);
 | 
						|
                        if (!File_error && size_to_read > 0)
 | 
						|
                        {
 | 
						|
                          free(context->Original_file_directory);
 | 
						|
                          context->Original_file_directory = GFX2_malloc(size_to_read);
 | 
						|
                          Read_bytes(GIF_file, context->Original_file_directory, size_to_read);
 | 
						|
                          Read_byte(GIF_file, &size_to_read);
 | 
						|
                          if (!File_error && size_to_read > 0)
 | 
						|
                          {
 | 
						|
                            free(context->Original_file_name);
 | 
						|
                            context->Original_file_name = GFX2_malloc(size_to_read);
 | 
						|
                            Read_bytes(GIF_file, context->Original_file_name, size_to_read);
 | 
						|
                            Read_byte(GIF_file, &size_to_read); // Normally 0
 | 
						|
                          }
 | 
						|
                        }
 | 
						|
                      }
 | 
						|
                      else if (!memcmp(aeb,"CRNG\0\0\0\0" "1.0",0x0B))
 | 
						|
                      {
 | 
						|
                        // Color animation. Similar to a CRNG chunk in IFF file format.
 | 
						|
                        word rate;
 | 
						|
                        word flags;
 | 
						|
                        byte col1;
 | 
						|
                        byte col2;
 | 
						|
                        //
 | 
						|
                        Read_byte(GIF_file,&size_to_read);
 | 
						|
                        for(;size_to_read>0 && !File_error;size_to_read-=6)
 | 
						|
                        {
 | 
						|
                          if ( (Read_word_be(GIF_file, &rate))
 | 
						|
                            && (Read_word_be(GIF_file, &flags))
 | 
						|
                            && (Read_byte(GIF_file, &col1))
 | 
						|
                            && (Read_byte(GIF_file, &col2)))
 | 
						|
                          {
 | 
						|
                            if (col1 != col2)
 | 
						|
                            {
 | 
						|
                              // Valid cycling range
 | 
						|
                              context->Cycle_range[context->Color_cycles].Start = MIN(col1, col2);
 | 
						|
                              context->Cycle_range[context->Color_cycles].End = MAX(col1, col2);
 | 
						|
                              context->Cycle_range[context->Color_cycles].Inverse = (flags&2)?1:0;
 | 
						|
                              context->Cycle_range[context->Color_cycles].Speed = (flags&1)?rate/78:0;
 | 
						|
 | 
						|
                              context->Color_cycles++;
 | 
						|
                            }
 | 
						|
                          }
 | 
						|
                          else
 | 
						|
                          {
 | 
						|
                            File_error=1;
 | 
						|
                          }
 | 
						|
                        }
 | 
						|
                        // Read end-of-block delimiter
 | 
						|
                        if (!File_error)
 | 
						|
                          Read_byte(GIF_file,&size_to_read);
 | 
						|
                        if (size_to_read!=0)
 | 
						|
                          File_error=1;
 | 
						|
                      }
 | 
						|
                      else if (0 == memcmp(aeb, "GFX2MODE", 8))
 | 
						|
                      {
 | 
						|
                        Read_byte(GIF_file,&size_to_read);
 | 
						|
                        if (size_to_read > 0)
 | 
						|
                        { // read the image mode. We'll set it after having loaded all layers.
 | 
						|
                          char * label = GFX2_malloc((size_t)size_to_read + 1);
 | 
						|
                          Read_bytes(GIF_file, label, size_to_read);
 | 
						|
                          label[size_to_read] = '\0';
 | 
						|
                          image_mode = Constraint_mode_from_label(label);
 | 
						|
                          GFX2_Log(GFX2_DEBUG, "    mode = %s (%d)\n", label, image_mode);
 | 
						|
                          free(label);
 | 
						|
                          Read_byte(GIF_file,&size_to_read);
 | 
						|
                          // be future proof, skip following sub-blocks :
 | 
						|
                          while (size_to_read!=0 && !File_error)
 | 
						|
                          {
 | 
						|
                            if (fseek(GIF_file,size_to_read,SEEK_CUR) < 0)
 | 
						|
                              File_error = 1;
 | 
						|
                            if (!Read_byte(GIF_file,&size_to_read))
 | 
						|
                              File_error = 1;
 | 
						|
                          }
 | 
						|
                        }
 | 
						|
                      }
 | 
						|
                      else
 | 
						|
                      {
 | 
						|
                        // Unknown extension, skip.
 | 
						|
                        Read_byte(GIF_file,&size_to_read);
 | 
						|
                        while (size_to_read!=0 && !File_error)
 | 
						|
                        {
 | 
						|
                          if (fseek(GIF_file,size_to_read,SEEK_CUR) < 0)
 | 
						|
                            File_error = 1;
 | 
						|
                          if (!Read_byte(GIF_file,&size_to_read))
 | 
						|
                            File_error = 1;
 | 
						|
                        }
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                      fseek(GIF_file,size_to_read,SEEK_CUR);
 | 
						|
                      // Lecture de la taille du bloc suivant:
 | 
						|
                      Read_byte(GIF_file,&size_to_read);
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                  default:
 | 
						|
                    // On saute le bloc:
 | 
						|
                    fseek(GIF_file,size_to_read,SEEK_CUR);
 | 
						|
                    // Lecture de la taille du bloc suivant:
 | 
						|
                    Read_byte(GIF_file,&size_to_read);
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
              }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
            case 0x2C: // Local Image Descriptor
 | 
						|
            {
 | 
						|
              if (number_LID!=0)
 | 
						|
              {
 | 
						|
                // This a second layer/frame, or more.
 | 
						|
                // Attempt to add a layer to current image
 | 
						|
                current_layer++;
 | 
						|
                Set_loading_layer(context, current_layer);
 | 
						|
                if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
 | 
						|
                {
 | 
						|
                  // Copy the content of previous layer.
 | 
						|
                  memcpy(
 | 
						|
                    Main.backups->Pages->Image[Main.current_layer].Pixels,
 | 
						|
                    Main.backups->Pages->Image[Main.current_layer-1].Pixels,
 | 
						|
                    Main.backups->Pages->Width*Main.backups->Pages->Height);
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                  Fill_canvas(context, is_transparent ? context->Transparent_color : LSDB.Backcol);
 | 
						|
                }
 | 
						|
              }
 | 
						|
              else
 | 
						|
              {
 | 
						|
                // First frame/layer, fill canvas with backcolor
 | 
						|
                Fill_canvas(context, is_transparent ? context->Transparent_color : LSDB.Backcol);
 | 
						|
              }
 | 
						|
              // Duration was set in the previously loaded GCE
 | 
						|
              Set_frame_duration(context, last_delay*10);
 | 
						|
              number_LID++;
 | 
						|
 | 
						|
              // lecture de 10 derniers octets
 | 
						|
              if ( Read_word_le(GIF_file,&(IDB.Pos_X))
 | 
						|
                && Read_word_le(GIF_file,&(IDB.Pos_Y))
 | 
						|
                && Read_word_le(GIF_file,&(IDB.Image_width))
 | 
						|
                && Read_word_le(GIF_file,&(IDB.Image_height))
 | 
						|
                && Read_byte(GIF_file,&(IDB.Indicator))
 | 
						|
                && IDB.Image_width && IDB.Image_height)
 | 
						|
              {
 | 
						|
                GFX2_Log(GFX2_DEBUG, "GIF Image descriptor %u Pos (%u,%u) %ux%u %s%slocal palette(%ubpp)\n",
 | 
						|
                         number_LID, IDB.Pos_X, IDB.Pos_Y, IDB.Image_width, IDB.Image_height,
 | 
						|
                         (IDB.Indicator & 0x40) ? "interlaced " : "", (IDB.Indicator & 0x80) ? "" : "no ",
 | 
						|
                         (IDB.Indicator & 7) + 1);
 | 
						|
                // Palette locale dispo = (IDB.Indicator and $80)
 | 
						|
                // Image entrelacée     = (IDB.Indicator and $40)
 | 
						|
                // Ordre de classement  = (IDB.Indicator and $20)
 | 
						|
                // Nombre de bits/pixel = (IDB.Indicator and $07)+1 (si palette locale dispo)
 | 
						|
 | 
						|
                if (IDB.Indicator & 0x80)
 | 
						|
                {
 | 
						|
                  // Palette locale dispo
 | 
						|
 | 
						|
                  if (Config.Clear_palette)
 | 
						|
                    memset(context->Palette,0,sizeof(T_Palette));
 | 
						|
 | 
						|
                  nb_colors=(1 << ((IDB.Indicator & 0x07)+1));
 | 
						|
                  // Load the palette
 | 
						|
                  for(color_index=0;color_index<nb_colors;color_index++)
 | 
						|
                  {
 | 
						|
                    Read_byte(GIF_file,&(context->Palette[color_index].R));
 | 
						|
                    Read_byte(GIF_file,&(context->Palette[color_index].G));
 | 
						|
                    Read_byte(GIF_file,&(context->Palette[color_index].B));
 | 
						|
                  }
 | 
						|
 | 
						|
                }
 | 
						|
                if (number_LID!=1)
 | 
						|
                {
 | 
						|
                  // This a second layer/frame, or more.
 | 
						|
                  if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
 | 
						|
                  {
 | 
						|
                    // Need to clear previous image to back-color.
 | 
						|
                    if (previous_disposal_method==DISPOSAL_METHOD_RESTORE_BGCOLOR)
 | 
						|
                    {
 | 
						|
                      int y;
 | 
						|
                      for (y=0; y<previous_height; y++)
 | 
						|
                        memset(
 | 
						|
                          Main.backups->Pages->Image[Main.current_layer].Pixels
 | 
						|
                           + (previous_pos_y+y)* Main.backups->Pages->Width+previous_pos_x,
 | 
						|
                          is_transparent ? context->Transparent_color : LSDB.Backcol,
 | 
						|
                          previous_width);
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                previous_height=IDB.Image_height;
 | 
						|
                previous_width=IDB.Image_width;
 | 
						|
                previous_pos_x=IDB.Pos_X;
 | 
						|
                previous_pos_y=IDB.Pos_Y;
 | 
						|
 | 
						|
                File_error=0;
 | 
						|
                if (!Read_byte(GIF_file,&(initial_nb_bits)))
 | 
						|
                  File_error=1;
 | 
						|
 | 
						|
                value_clr    =(1<<initial_nb_bits)+0;
 | 
						|
                value_eof    =(1<<initial_nb_bits)+1;
 | 
						|
                alphabet_free=(1<<initial_nb_bits)+2;
 | 
						|
 | 
						|
                GIF.nb_bits  =initial_nb_bits + 1;
 | 
						|
                alphabet_max      =((1 <<  GIF.nb_bits)-1);
 | 
						|
                GIF.interlaced    =(IDB.Indicator & 0x40);
 | 
						|
                GIF.pass         =0;
 | 
						|
 | 
						|
                /*Init_lecture();*/
 | 
						|
 | 
						|
 | 
						|
                GIF.stop = 0;
 | 
						|
 | 
						|
                //////////////////////////////////////////// DECOMPRESSION LZW //
 | 
						|
 | 
						|
                GIF.pos_X=0;
 | 
						|
                GIF.pos_Y=0;
 | 
						|
                alphabet_stack_pos=0;
 | 
						|
                GIF.last_byte    =0;
 | 
						|
                GIF.remainder_bits    =0;
 | 
						|
                GIF.remainder_byte    =0;
 | 
						|
 | 
						|
                while ( (GIF_get_next_code(GIF_file, &GIF)!=value_eof) && (!File_error) )
 | 
						|
                {
 | 
						|
                  if (GIF.current_code > alphabet_free)
 | 
						|
                  {
 | 
						|
                    GFX2_Log(GFX2_INFO, "Load_GIF() Invalid code %u (should be <=%u)\n", GIF.current_code, alphabet_free);
 | 
						|
                    File_error=2;
 | 
						|
                    break;
 | 
						|
                  }
 | 
						|
                  else if (GIF.current_code != value_clr)
 | 
						|
                  {
 | 
						|
                    byte_read = GIF.current_code;
 | 
						|
                    if (alphabet_free == GIF.current_code)
 | 
						|
                    {
 | 
						|
                      GIF.current_code=old_code;
 | 
						|
                      alphabet_stack[alphabet_stack_pos++]=special_case;
 | 
						|
                    }
 | 
						|
 | 
						|
                    while (GIF.current_code > value_clr)
 | 
						|
                    {
 | 
						|
                      if (GIF.current_code >= 4096)
 | 
						|
                      {
 | 
						|
                        GFX2_Log(GFX2_ERROR, "Load_GIF() GIF.current_code = %u >= 4096\n", GIF.current_code);
 | 
						|
                        File_error = 2;
 | 
						|
                        break;
 | 
						|
                      }
 | 
						|
                      alphabet_stack[alphabet_stack_pos++] = alphabet_suffix[GIF.current_code];
 | 
						|
                      GIF.current_code = alphabet_prefix[GIF.current_code];
 | 
						|
                    }
 | 
						|
 | 
						|
                    special_case = alphabet_stack[alphabet_stack_pos++] = GIF.current_code;
 | 
						|
 | 
						|
                    do
 | 
						|
                      GIF_new_pixel(context, &GIF, &IDB, is_transparent, alphabet_stack[--alphabet_stack_pos]);
 | 
						|
                    while (alphabet_stack_pos!=0);
 | 
						|
 | 
						|
                    alphabet_prefix[alphabet_free  ]=old_code;
 | 
						|
                    alphabet_suffix[alphabet_free++]=GIF.current_code;
 | 
						|
                    old_code=byte_read;
 | 
						|
 | 
						|
                    if (alphabet_free>alphabet_max)
 | 
						|
                    {
 | 
						|
                      if (GIF.nb_bits<12)
 | 
						|
                        alphabet_max      =((1 << (++GIF.nb_bits))-1);
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
                  else // Clear code
 | 
						|
                  {
 | 
						|
                    GIF.nb_bits   = initial_nb_bits + 1;
 | 
						|
                    alphabet_max  = ((1 <<  GIF.nb_bits)-1);
 | 
						|
                    alphabet_free = (1<<initial_nb_bits)+2;
 | 
						|
                    special_case  = GIF_get_next_code(GIF_file, &GIF);
 | 
						|
                    if (GIF.current_code >= value_clr)
 | 
						|
                    {
 | 
						|
                      GFX2_Log(GFX2_INFO, "Load_GIF() Invalid code %u just after clear (=%u)!\n",
 | 
						|
                               GIF.current_code, value_clr);
 | 
						|
                      File_error = 2;
 | 
						|
                      break;
 | 
						|
                    }
 | 
						|
                    old_code      = GIF.current_code;
 | 
						|
                    GIF_new_pixel(context, &GIF, &IDB, is_transparent, GIF.current_code);
 | 
						|
                  }
 | 
						|
                }
 | 
						|
 | 
						|
                if (File_error == 2 && GIF.pos_X == 0 && GIF.pos_Y == IDB.Image_height)
 | 
						|
                  File_error=0;
 | 
						|
 | 
						|
                if (File_error >= 0 && !GIF.stop)
 | 
						|
                  File_error=2;
 | 
						|
 | 
						|
                // No need to read more than one frame in animation preview mode
 | 
						|
                if (context->Type == CONTEXT_PREVIEW && is_looping)
 | 
						|
                {
 | 
						|
                  goto early_exit;
 | 
						|
                }
 | 
						|
                // Same with brush
 | 
						|
                if (context->Type == CONTEXT_BRUSH && is_looping)
 | 
						|
                {
 | 
						|
                  goto early_exit;
 | 
						|
                }
 | 
						|
 | 
						|
              } // Le fichier contenait un IDB
 | 
						|
              else
 | 
						|
                File_error=2;
 | 
						|
            }
 | 
						|
            default:
 | 
						|
            break;
 | 
						|
          }
 | 
						|
          // Lecture du code de fonction suivant:
 | 
						|
          if (!Read_byte(GIF_file,&block_identifier))
 | 
						|
            File_error=2;
 | 
						|
        }
 | 
						|
 | 
						|
        // set the mode that have been read previously.
 | 
						|
        if (image_mode > 0)
 | 
						|
          Set_image_mode(context, image_mode);
 | 
						|
      } // Le fichier contenait un LSDB
 | 
						|
      else
 | 
						|
        File_error=1;
 | 
						|
 | 
						|
      early_exit:
 | 
						|
 | 
						|
      // Libération de la mémoire utilisée par les tables & piles de traitement:
 | 
						|
      free(alphabet_suffix);
 | 
						|
      free(alphabet_prefix);
 | 
						|
      free(alphabet_stack);
 | 
						|
      alphabet_suffix = alphabet_prefix = alphabet_stack = NULL;
 | 
						|
    } // Le fichier contenait au moins la signature GIF87a ou GIF89a
 | 
						|
    else
 | 
						|
      File_error=1;
 | 
						|
 | 
						|
    fclose(GIF_file);
 | 
						|
 | 
						|
  } // Le fichier était ouvrable
 | 
						|
  else
 | 
						|
    File_error=1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// -- Sauver un fichier au format GIF ---------------------------------------
 | 
						|
 | 
						|
/// Flush the buffer
 | 
						|
static void GIF_empty_buffer(FILE * file, T_GIF_context *gif, byte * GIF_buffer)
 | 
						|
{
 | 
						|
  if (gif->remainder_byte)
 | 
						|
  {
 | 
						|
    GIF_buffer[0] = gif->remainder_byte;
 | 
						|
 | 
						|
    if (!Write_bytes(file, GIF_buffer, (size_t)gif->remainder_byte + 1))
 | 
						|
      File_error = 1;
 | 
						|
 | 
						|
    gif->remainder_byte = 0;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/// Write a code (GIF_nb_bits bits)
 | 
						|
static void GIF_set_code(FILE * GIF_file, T_GIF_context * gif, byte * GIF_buffer, word Code)
 | 
						|
{
 | 
						|
  word nb_bits_to_process = gif->nb_bits;
 | 
						|
  word nb_bits_processed  =0;
 | 
						|
  word current_nb_bits;
 | 
						|
 | 
						|
  while (nb_bits_to_process)
 | 
						|
  {
 | 
						|
    current_nb_bits = (nb_bits_to_process <= (8-gif->remainder_bits)) ?
 | 
						|
                          nb_bits_to_process: (8-gif->remainder_bits);
 | 
						|
 | 
						|
    gif->last_byte |= (Code & ((1<<current_nb_bits)-1))<<gif->remainder_bits;
 | 
						|
    Code>>=current_nb_bits;
 | 
						|
    gif->remainder_bits    +=current_nb_bits;
 | 
						|
    nb_bits_processed  +=current_nb_bits;
 | 
						|
    nb_bits_to_process-=current_nb_bits;
 | 
						|
 | 
						|
    if (gif->remainder_bits==8) // Il ne reste plus de bits à coder sur l'octet courant
 | 
						|
    {
 | 
						|
      // Ecrire l'octet à balancer:
 | 
						|
      GIF_buffer[++(gif->remainder_byte)] = gif->last_byte;
 | 
						|
 | 
						|
      // Si on a atteint la fin du bloc de Raster Data
 | 
						|
      if (gif->remainder_byte==255)
 | 
						|
        // On doit vider le buffer qui est maintenant plein
 | 
						|
        GIF_empty_buffer(GIF_file, gif, GIF_buffer);
 | 
						|
 | 
						|
      gif->last_byte=0;
 | 
						|
      gif->remainder_bits=0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/// Read the next pixel
 | 
						|
static byte GIF_next_pixel(T_IO_Context *context, T_GIF_context *gif, T_GIF_IDB *idb)
 | 
						|
{
 | 
						|
  byte temp;
 | 
						|
 | 
						|
  temp = Get_pixel(context, gif->pos_X, gif->pos_Y);
 | 
						|
 | 
						|
  if (++gif->pos_X >= (idb->Image_width + idb->Pos_X))
 | 
						|
  {
 | 
						|
    gif->pos_X = idb->Pos_X;
 | 
						|
    if (++gif->pos_Y >= (idb->Image_height + idb->Pos_Y))
 | 
						|
      gif->stop = 1;
 | 
						|
  }
 | 
						|
 | 
						|
  return temp;
 | 
						|
}
 | 
						|
 | 
						|
struct gif_alphabet {
 | 
						|
  word prefix[4096];    // code prefix array
 | 
						|
  word suffix[4096];    // code suffix array
 | 
						|
  word daughter[4096];  // daughter strings array (greater length)
 | 
						|
  word sister[4096];    // sister strings array (same length)
 | 
						|
  word free;            // first free slot in the alphabet
 | 
						|
  word max;             // maximum number of entry in the alphabet
 | 
						|
};
 | 
						|
 | 
						|
/// Save a GIF file
 | 
						|
void Save_GIF(T_IO_Context * context)
 | 
						|
{
 | 
						|
  FILE * GIF_file;
 | 
						|
  byte GIF_buffer[256];   // buffer d'écriture de bloc de données compilées
 | 
						|
 | 
						|
  struct gif_alphabet * alphabet;
 | 
						|
  word   start;            // Code précédent (sert au linkage des chaînes)
 | 
						|
  int    descend;          // Booléen "On vient de descendre"
 | 
						|
 | 
						|
  T_GIF_context GIF;
 | 
						|
  T_GIF_LSDB LSDB;
 | 
						|
  T_GIF_IDB IDB;
 | 
						|
 | 
						|
 | 
						|
  byte block_identifier;  // Code indicateur du type de bloc en cours
 | 
						|
  word current_string;   // Code de la chaîne en cours de traitement
 | 
						|
  byte current_char;         // Caractère à coder
 | 
						|
  word index;            // index de recherche de chaîne
 | 
						|
  int current_layer;
 | 
						|
 | 
						|
  word clear;   // LZW clear code
 | 
						|
  word eof;     // End of image code
 | 
						|
 | 
						|
  /////////////////////////////////////////////////// FIN DES DECLARATIONS //
 | 
						|
 | 
						|
  File_error=0;
 | 
						|
 | 
						|
  if ((GIF_file=Open_file_write(context)))
 | 
						|
  {
 | 
						|
    // On écrit la signature du fichier
 | 
						|
    if (Write_bytes(GIF_file,"GIF89a",6))
 | 
						|
    {
 | 
						|
      // La signature du fichier a été correctement écrite.
 | 
						|
 | 
						|
      // Allocation de mémoire pour les tables
 | 
						|
      alphabet = (struct gif_alphabet *)GFX2_malloc(sizeof(struct gif_alphabet));
 | 
						|
      if (alphabet == NULL)
 | 
						|
      {
 | 
						|
        File_error = 1;
 | 
						|
        fclose(GIF_file);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // On initialise le LSDB du fichier
 | 
						|
      if (Config.Screen_size_in_GIF && Screen_width >= context->Width && Screen_height >= context->Height)
 | 
						|
      {
 | 
						|
        // Canvas bigger than the image
 | 
						|
        LSDB.Width=Screen_width;
 | 
						|
        LSDB.Height=Screen_height;
 | 
						|
      }
 | 
						|
      else
 | 
						|
      {
 | 
						|
        LSDB.Width=context->Width;
 | 
						|
        LSDB.Height=context->Height;
 | 
						|
      }
 | 
						|
      LSDB.Resol  = 0xF7;  // Global palette of 256 entries, 256 color image
 | 
						|
      // 0xF7 = 1111 0111
 | 
						|
      // <Packed Fields>  =      Global Color Table Flag       1 Bit
 | 
						|
      //                         Color Resolution              3 Bits
 | 
						|
      //                         Sort Flag                     1 Bit
 | 
						|
      //                         Size of Global Color Table    3 Bits
 | 
						|
      LSDB.Backcol=context->Transparent_color;
 | 
						|
      switch(context->Ratio)
 | 
						|
      {
 | 
						|
        case PIXEL_TALL:
 | 
						|
        case PIXEL_TALL2:
 | 
						|
          LSDB.Aspect = 17; // 1:2 = 2:4
 | 
						|
          break;
 | 
						|
        case PIXEL_TALL3:
 | 
						|
          LSDB.Aspect = 33; // 3:4
 | 
						|
          break;
 | 
						|
        case PIXEL_WIDE:
 | 
						|
        case PIXEL_WIDE2:
 | 
						|
          LSDB.Aspect = 113; // 2:1 = 4:2
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          LSDB.Aspect = 0; // undefined, which is most frequent.
 | 
						|
          // 49 would be 1:1 ratio
 | 
						|
      }
 | 
						|
 | 
						|
      // On sauve le LSDB dans le fichier
 | 
						|
 | 
						|
      if (Write_word_le(GIF_file,LSDB.Width) &&
 | 
						|
          Write_word_le(GIF_file,LSDB.Height) &&
 | 
						|
          Write_byte(GIF_file,LSDB.Resol) &&
 | 
						|
          Write_byte(GIF_file,LSDB.Backcol) &&
 | 
						|
          Write_byte(GIF_file,LSDB.Aspect) )
 | 
						|
      {
 | 
						|
        // Le LSDB a été correctement écrit.
 | 
						|
        int i;
 | 
						|
        // On sauve la palette
 | 
						|
        for(i=0;i<256 && !File_error;i++)
 | 
						|
        {
 | 
						|
          if (!Write_byte(GIF_file,context->Palette[i].R)
 | 
						|
            ||!Write_byte(GIF_file,context->Palette[i].G)
 | 
						|
            ||!Write_byte(GIF_file,context->Palette[i].B))
 | 
						|
            File_error=1;
 | 
						|
        }
 | 
						|
        if (!File_error)
 | 
						|
        {
 | 
						|
          // La palette a été correctement écrite.
 | 
						|
 | 
						|
          /// - "Netscape" animation extension :
 | 
						|
          /// <pre>
 | 
						|
          ///   0x21       Extension Label
 | 
						|
          ///   0xFF       Application Extension Label
 | 
						|
          ///   0x0B       Block Size
 | 
						|
          ///   "NETSCAPE" Application Identifier (8 bytes)
 | 
						|
          ///   "2.0"      Application Authentication Code (3 bytes)
 | 
						|
          ///   0x03       Sub-block Data Size
 | 
						|
          ///   0xLL       01 to loop
 | 
						|
          ///   0xSSSS     (little endian) number of loops, 0 means infinite loop
 | 
						|
          ///   0x00 Block terminator </pre>
 | 
						|
          /// see http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
 | 
						|
          if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
 | 
						|
          {
 | 
						|
            if (context->Nb_layers>1)
 | 
						|
              Write_bytes(GIF_file,"\x21\xFF\x0BNETSCAPE2.0\x03\x01\x00\x00\x00",19);
 | 
						|
          }
 | 
						|
          else if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode > IMAGE_MODE_ANIMATION)
 | 
						|
          {
 | 
						|
            /// - GrafX2 extension to store ::IMAGE_MODES :
 | 
						|
            /// <pre>
 | 
						|
            ///   0x21       Extension Label
 | 
						|
            ///   0xFF       Application Extension Label
 | 
						|
            ///   0x0B       Block Size
 | 
						|
            ///   "GFX2MODE" Application Identifier (8 bytes)
 | 
						|
            ///   "2.6"      Application Authentication Code (3 bytes)
 | 
						|
            ///   0xll       Sub-block Data Size
 | 
						|
            ///   string     label
 | 
						|
            ///   0x00 Block terminator </pre>
 | 
						|
            /// @see Constraint_mode_label()
 | 
						|
            const char * label = Constraint_mode_label(Main.backups->Pages->Image_mode);
 | 
						|
            if (label != NULL)
 | 
						|
            {
 | 
						|
              size_t len = strlen(label);
 | 
						|
              // Write extension for storing IMAGE_MODE
 | 
						|
              Write_byte(GIF_file,0x21);  // Extension Introducer
 | 
						|
              Write_byte(GIF_file,0xff);  // Extension Label
 | 
						|
              Write_byte(GIF_file,  11);  // Block size
 | 
						|
              Write_bytes(GIF_file, "GFX2MODE2.6", 11); // Application Identifier + Appl. Authentication Code
 | 
						|
              Write_byte(GIF_file, (byte)len);    // Block size
 | 
						|
              Write_bytes(GIF_file, label, len);  // Data
 | 
						|
              Write_byte(GIF_file, 0);    // Block terminator
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          // Ecriture du commentaire
 | 
						|
          if (context->Comment[0])
 | 
						|
          {
 | 
						|
            Write_bytes(GIF_file,"\x21\xFE",2);
 | 
						|
            Write_byte(GIF_file, (byte)strlen(context->Comment));
 | 
						|
            Write_bytes(GIF_file,context->Comment,strlen(context->Comment)+1);
 | 
						|
          }
 | 
						|
          /// - "CRNG" Color cycing extension :
 | 
						|
          /// <pre>
 | 
						|
          ///   0x21       Extension Label
 | 
						|
          ///   0xFF       Application Extension Label
 | 
						|
          ///   0x0B       Block Size
 | 
						|
          ///   "CRNG\0\0\0\0" "CRNG" Application Identifier (8 bytes)
 | 
						|
          ///   "1.0"      Application Authentication Code (3 bytes)
 | 
						|
          ///   0xll       Sub-block Data Size (6 bytes per color cycle)
 | 
						|
          ///   For each color cycle :
 | 
						|
          ///     0xRRRR   (big endian) Rate
 | 
						|
          ///     0xFFFF   (big endian) Flags
 | 
						|
          ///     0xSS     start (lower color index)
 | 
						|
          ///     0xEE     end (higher color index)
 | 
						|
          ///   0x00       Block terminator </pre>
 | 
						|
          if (context->Color_cycles)
 | 
						|
          {
 | 
						|
            int i;
 | 
						|
 | 
						|
            Write_bytes(GIF_file,"\x21\xff\x0B" "CRNG\0\0\0\0" "1.0",14);
 | 
						|
            Write_byte(GIF_file,context->Color_cycles*6);
 | 
						|
            for (i=0; i<context->Color_cycles; i++)
 | 
						|
            {
 | 
						|
              word flags=0;
 | 
						|
              flags|= context->Cycle_range[i].Speed?1:0; // Cycling or not
 | 
						|
              flags|= context->Cycle_range[i].Inverse?2:0; // Inverted
 | 
						|
 | 
						|
              Write_word_be(GIF_file,context->Cycle_range[i].Speed*78); // Rate
 | 
						|
              Write_word_be(GIF_file,flags); // Flags
 | 
						|
              Write_byte(GIF_file,context->Cycle_range[i].Start); // Min color
 | 
						|
              Write_byte(GIF_file,context->Cycle_range[i].End); // Max color
 | 
						|
            }
 | 
						|
            Write_byte(GIF_file,0);
 | 
						|
          }
 | 
						|
 | 
						|
          // Loop on all layers
 | 
						|
          for (current_layer=0;
 | 
						|
            current_layer < context->Nb_layers && !File_error;
 | 
						|
            current_layer++)
 | 
						|
          {
 | 
						|
            // Write a Graphic Control Extension
 | 
						|
            T_GIF_GCE GCE;
 | 
						|
            byte disposal_method;
 | 
						|
 | 
						|
            Set_saving_layer(context, current_layer);
 | 
						|
 | 
						|
            GCE.Block_identifier = 0x21;
 | 
						|
            GCE.Function = 0xF9;
 | 
						|
            GCE.Block_size=4;
 | 
						|
 | 
						|
            if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
 | 
						|
            {
 | 
						|
              // Animation frame
 | 
						|
              int duration;
 | 
						|
              if(context->Background_transparent)
 | 
						|
                disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR;
 | 
						|
              else
 | 
						|
                disposal_method = DISPOSAL_METHOD_DO_NOT_DISPOSE;
 | 
						|
              GCE.Packed_fields=(disposal_method<<2)|(context->Background_transparent);
 | 
						|
              duration=Get_frame_duration(context)/10;
 | 
						|
              GCE.Delay_time=duration<0xFFFF?duration:0xFFFF;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
              // Layered image or brush
 | 
						|
              disposal_method = DISPOSAL_METHOD_DO_NOT_DISPOSE;
 | 
						|
              if (current_layer==0)
 | 
						|
                GCE.Packed_fields=(disposal_method<<2)|(context->Background_transparent);
 | 
						|
              else
 | 
						|
                GCE.Packed_fields=(disposal_method<<2)|(1);
 | 
						|
              GCE.Delay_time=5; // Duration 5/100s (minimum viable value for current web browsers)
 | 
						|
              if (current_layer == context->Nb_layers -1)
 | 
						|
                GCE.Delay_time=0xFFFF; // Infinity (10 minutes)
 | 
						|
            }
 | 
						|
            GCE.Transparent_color=context->Transparent_color;
 | 
						|
            GCE.Block_terminator=0x00;
 | 
						|
 | 
						|
            if (Write_byte(GIF_file,GCE.Block_identifier)
 | 
						|
             && Write_byte(GIF_file,GCE.Function)
 | 
						|
             && Write_byte(GIF_file,GCE.Block_size)
 | 
						|
             && Write_byte(GIF_file,GCE.Packed_fields)
 | 
						|
             && Write_word_le(GIF_file,GCE.Delay_time)
 | 
						|
             && Write_byte(GIF_file,GCE.Transparent_color)
 | 
						|
             && Write_byte(GIF_file,GCE.Block_terminator)
 | 
						|
             )
 | 
						|
            {
 | 
						|
              byte temp, max = 0;
 | 
						|
 | 
						|
              IDB.Pos_X=0;
 | 
						|
              IDB.Pos_Y=0;
 | 
						|
              IDB.Image_width=context->Width;
 | 
						|
              IDB.Image_height=context->Height;
 | 
						|
              if(current_layer > 0)
 | 
						|
              {
 | 
						|
                word min_X, max_X, min_Y, max_Y;
 | 
						|
                // find bounding box of changes for Animated GIFs
 | 
						|
                min_X = min_Y = 0xffff;
 | 
						|
                max_X = max_Y = 0;
 | 
						|
                for(GIF.pos_Y = 0; GIF.pos_Y < context->Height; GIF.pos_Y++) {
 | 
						|
                  for(GIF.pos_X = 0; GIF.pos_X < context->Width; GIF.pos_X++) {
 | 
						|
                    if (GIF.pos_X >= min_X && GIF.pos_X <= max_X && GIF.pos_Y >= min_Y && GIF.pos_Y <= max_Y)
 | 
						|
                      continue; // already in the box
 | 
						|
                    if(disposal_method == DISPOSAL_METHOD_DO_NOT_DISPOSE)
 | 
						|
                    {
 | 
						|
                      // if that pixel has same value in previous layer, no need to save it
 | 
						|
                      Set_saving_layer(context, current_layer - 1);
 | 
						|
                      temp = Get_pixel(context, GIF.pos_X, GIF.pos_Y);
 | 
						|
                      Set_saving_layer(context, current_layer);
 | 
						|
                      if(temp == Get_pixel(context, GIF.pos_X, GIF.pos_Y))
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
                    if (disposal_method == DISPOSAL_METHOD_RESTORE_BGCOLOR
 | 
						|
                      || context->Background_transparent
 | 
						|
                      || Main.backups->Pages->Image_mode != IMAGE_MODE_ANIMATION)
 | 
						|
                    {
 | 
						|
                      // if that pixel is Backcol, no need to save it
 | 
						|
                      if (LSDB.Backcol == Get_pixel(context, GIF.pos_X, GIF.pos_Y))
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
                    if(GIF.pos_X < min_X) min_X = GIF.pos_X;
 | 
						|
                    if(GIF.pos_X > max_X) max_X = GIF.pos_X;
 | 
						|
                    if(GIF.pos_Y < min_Y) min_Y = GIF.pos_Y;
 | 
						|
                    if(GIF.pos_Y > max_Y) max_Y = GIF.pos_Y;
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                if((min_X <= max_X) && (min_Y <= max_Y))
 | 
						|
                {
 | 
						|
                  IDB.Pos_X = min_X;
 | 
						|
                  IDB.Pos_Y = min_Y;
 | 
						|
                  IDB.Image_width = max_X + 1 - min_X;
 | 
						|
                  IDB.Image_height = max_Y + 1 - min_Y;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                  // if no pixel changes, store a 1 pixel image
 | 
						|
                  IDB.Image_width = 1;
 | 
						|
                  IDB.Image_height = 1;
 | 
						|
                }
 | 
						|
              }
 | 
						|
 | 
						|
              // look for the maximum pixel value
 | 
						|
              // to decide how many bit per pixel are needed.
 | 
						|
              for(GIF.pos_Y = IDB.Pos_Y; GIF.pos_Y < IDB.Image_height + IDB.Pos_Y; GIF.pos_Y++) {
 | 
						|
                for(GIF.pos_X = IDB.Pos_X; GIF.pos_X < IDB.Image_width + IDB.Pos_X; GIF.pos_X++) {
 | 
						|
                  temp=Get_pixel(context, GIF.pos_X, GIF.pos_Y);
 | 
						|
                  if(temp > max) max = temp;
 | 
						|
                }
 | 
						|
              }
 | 
						|
              IDB.Nb_bits_pixel=2;  // Find the minimum bpp value to fit all pixels
 | 
						|
              while((int)max >= (1 << IDB.Nb_bits_pixel)) {
 | 
						|
                IDB.Nb_bits_pixel++;
 | 
						|
              }
 | 
						|
              GFX2_Log(GFX2_DEBUG, "GIF image #%d %ubits (%u,%u) %ux%u\n",
 | 
						|
                       current_layer, IDB.Nb_bits_pixel, IDB.Pos_X, IDB.Pos_Y,
 | 
						|
                       IDB.Image_width, IDB.Image_height);
 | 
						|
 | 
						|
              // On va écrire un block indicateur d'IDB et l'IDB du fichier
 | 
						|
              block_identifier=0x2C;
 | 
						|
              IDB.Indicator=0x07;    // Image non entrelacée, pas de palette locale.
 | 
						|
              clear = 1 << IDB.Nb_bits_pixel; // Clear Code
 | 
						|
              eof = clear + 1;                // End of Picture Code
 | 
						|
 | 
						|
              if ( Write_byte(GIF_file,block_identifier) &&
 | 
						|
                   Write_word_le(GIF_file,IDB.Pos_X) &&
 | 
						|
                   Write_word_le(GIF_file,IDB.Pos_Y) &&
 | 
						|
                   Write_word_le(GIF_file,IDB.Image_width) &&
 | 
						|
                   Write_word_le(GIF_file,IDB.Image_height) &&
 | 
						|
                   Write_byte(GIF_file,IDB.Indicator) &&
 | 
						|
                   Write_byte(GIF_file,IDB.Nb_bits_pixel))
 | 
						|
              {
 | 
						|
                //   Le block indicateur d'IDB et l'IDB ont étés correctements
 | 
						|
                // écrits.
 | 
						|
 | 
						|
                GIF.pos_X=IDB.Pos_X;
 | 
						|
                GIF.pos_Y=IDB.Pos_Y;
 | 
						|
                GIF.last_byte=0;
 | 
						|
                GIF.remainder_bits=0;
 | 
						|
                GIF.remainder_byte=0;
 | 
						|
 | 
						|
#define GIF_INVALID_CODE (65535)
 | 
						|
                index=GIF_INVALID_CODE;
 | 
						|
                File_error=0;
 | 
						|
                GIF.stop=0;
 | 
						|
 | 
						|
                // Réintialisation de la table:
 | 
						|
                alphabet->free = clear + 2;  // 258 for 8bpp
 | 
						|
                GIF.nb_bits = IDB.Nb_bits_pixel + 1; // 9 for 8 bpp
 | 
						|
                alphabet->max = clear+clear-1;  // 511 for 8bpp
 | 
						|
                GIF_set_code(GIF_file, &GIF, GIF_buffer, clear);  //256 for 8bpp
 | 
						|
                for (start=0; start<4096; start++)
 | 
						|
                {
 | 
						|
                  alphabet->daughter[start] = GIF_INVALID_CODE;
 | 
						|
                  alphabet->sister[start] = GIF_INVALID_CODE;
 | 
						|
                }
 | 
						|
 | 
						|
                ////////////////////////////////////////////// COMPRESSION LZW //
 | 
						|
 | 
						|
                start=current_string=GIF_next_pixel(context, &GIF, &IDB);
 | 
						|
                descend=1;
 | 
						|
 | 
						|
                while ((!GIF.stop) && (!File_error))
 | 
						|
                {
 | 
						|
                  current_char=GIF_next_pixel(context, &GIF, &IDB);
 | 
						|
 | 
						|
                  // look for (current_string,current_char) in the alphabet
 | 
						|
                  while ( (index != GIF_INVALID_CODE) &&
 | 
						|
                          ( (current_string != alphabet->prefix[index]) ||
 | 
						|
                            (current_char   != alphabet->suffix[index]) ) )
 | 
						|
                  {
 | 
						|
                    descend = 0;
 | 
						|
                    start = index;
 | 
						|
                    index = alphabet->sister[index];
 | 
						|
                  }
 | 
						|
 | 
						|
                  if (index != GIF_INVALID_CODE)
 | 
						|
                  {
 | 
						|
                    // (current_string,current_char) == (alphabet_prefix,alphabet_suffix)[index]
 | 
						|
                    // We have found (current_string,current_char) in the alphabet
 | 
						|
                    // at the index position. So go on and prepare for then next character
 | 
						|
 | 
						|
                    descend = 1;
 | 
						|
                    start = current_string = index;
 | 
						|
                    index = alphabet->daughter[index];
 | 
						|
                  }
 | 
						|
                  else
 | 
						|
                  {
 | 
						|
                    // (current_string,current_char) was not found in the alphabet
 | 
						|
                    // so write current_string to the Gif stream
 | 
						|
                    GIF_set_code(GIF_file, &GIF, GIF_buffer, current_string);
 | 
						|
 | 
						|
                    if(alphabet->free < 4096) {
 | 
						|
                      // link current_string and the new one
 | 
						|
                      if (descend)
 | 
						|
                        alphabet->daughter[start] = alphabet->free;
 | 
						|
                      else
 | 
						|
                        alphabet->sister[start] = alphabet->free;
 | 
						|
 | 
						|
                      // add (current_string,current_char) to the alphabet
 | 
						|
                      alphabet->prefix[alphabet->free] = current_string;
 | 
						|
                      alphabet->suffix[alphabet->free] = current_char;
 | 
						|
                      alphabet->free++;
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (alphabet->free >= 4096)
 | 
						|
                    {
 | 
						|
                      // clear alphabet
 | 
						|
                      GIF_set_code(GIF_file, &GIF, GIF_buffer, clear);    // 256 for 8bpp
 | 
						|
                      alphabet->free=clear+2;  // 258 for 8bpp
 | 
						|
                      GIF.nb_bits = IDB.Nb_bits_pixel + 1;  // 9 for 8bpp
 | 
						|
                      alphabet->max = clear+clear-1;    // 511 for 8bpp
 | 
						|
                      for (start=0;start<4096;start++)
 | 
						|
                      {
 | 
						|
                        alphabet->daughter[start] = GIF_INVALID_CODE;
 | 
						|
                        alphabet->sister[start] = GIF_INVALID_CODE;
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                    else if (alphabet->free > (alphabet->max + 1))
 | 
						|
                    {
 | 
						|
                      // On augmente le nb de bits
 | 
						|
 | 
						|
                      GIF.nb_bits++;
 | 
						|
                      alphabet->max = (1<<GIF.nb_bits)-1;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // initialize current_string as the string "current_char"
 | 
						|
                    index = alphabet->daughter[current_char];
 | 
						|
                    start = current_string = current_char;
 | 
						|
                    descend = 1;
 | 
						|
                  }
 | 
						|
                }
 | 
						|
 | 
						|
                if (!File_error)
 | 
						|
                {
 | 
						|
                  // Write the last code (before EOF)
 | 
						|
                  GIF_set_code(GIF_file, &GIF, GIF_buffer, current_string);
 | 
						|
 | 
						|
                  // we need to update alphabet->free / GIF.nb_bits here because
 | 
						|
                  // the decoder will update them after each code,
 | 
						|
                  // so in very rare cases there might be a problem if we
 | 
						|
                  // don't do it.
 | 
						|
                  // see http://pulkomandy.tk/projects/GrafX2/ticket/125
 | 
						|
                  if(alphabet->free < 4096)
 | 
						|
                  {
 | 
						|
                    alphabet->free++;
 | 
						|
                    if ((alphabet->free > alphabet->max+1) && (GIF.nb_bits < 12))
 | 
						|
                    {
 | 
						|
                      GIF.nb_bits++;
 | 
						|
                      alphabet->max = (1 << GIF.nb_bits) - 1;
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
 | 
						|
                  GIF_set_code(GIF_file, &GIF, GIF_buffer, eof);  // 257 for 8bpp    // Code de End d'image
 | 
						|
                  if (GIF.remainder_bits!=0)
 | 
						|
                  {
 | 
						|
                    // Write last byte (this is an incomplete byte)
 | 
						|
                    GIF_buffer[++GIF.remainder_byte]=GIF.last_byte;
 | 
						|
                    GIF.last_byte=0;
 | 
						|
                    GIF.remainder_bits=0;
 | 
						|
                  }
 | 
						|
                  GIF_empty_buffer(GIF_file, &GIF, GIF_buffer); // On envoie les dernières données du buffer GIF dans le buffer KM
 | 
						|
 | 
						|
                  // On écrit un \0
 | 
						|
                  if (! Write_byte(GIF_file,'\x00'))
 | 
						|
                    File_error=1;
 | 
						|
                }
 | 
						|
 | 
						|
              } // On a pu écrire l'IDB
 | 
						|
              else
 | 
						|
                File_error=1;
 | 
						|
            }
 | 
						|
            else
 | 
						|
              File_error=1;
 | 
						|
          }
 | 
						|
 | 
						|
          // After writing all layers
 | 
						|
          if (!File_error)
 | 
						|
          {
 | 
						|
            /// - If requested, write a specific extension for storing
 | 
						|
            /// original file path.
 | 
						|
            /// This is used by the backup system.
 | 
						|
            /// The format is :
 | 
						|
            /// <pre>
 | 
						|
            ///   0x21       Extension Label
 | 
						|
            ///   0xFF       Application Extension Label
 | 
						|
            ///   0x0B       Block Size
 | 
						|
            ///   "GFX2PATH" "GFX2PATH" Application Identifier (8 bytes)
 | 
						|
            ///   "\0\0\0"   Application Authentication Code (3 bytes)
 | 
						|
            ///   0xll       Sub-block Data Size : path size (including null)
 | 
						|
            ///   "..path.." path (null-terminated)
 | 
						|
            ///   0xll       Sub-block Data Size : filename size (including null)
 | 
						|
            ///   "..file.." file name (null-terminated)
 | 
						|
            ///   0x00       Block terminator </pre>
 | 
						|
            if (context->Original_file_name != NULL
 | 
						|
             && context->Original_file_directory != NULL)
 | 
						|
            {
 | 
						|
              long name_size = 1+strlen(context->Original_file_name);
 | 
						|
              long dir_size = 1+strlen(context->Original_file_directory);
 | 
						|
              if (name_size<256 && dir_size<256)
 | 
						|
              {
 | 
						|
                if (! Write_bytes(GIF_file,"\x21\xFF\x0BGFX2PATH\x00\x00\x00", 14)
 | 
						|
                || ! Write_byte(GIF_file,dir_size)
 | 
						|
                || ! Write_bytes(GIF_file, context->Original_file_directory, dir_size)
 | 
						|
                || ! Write_byte(GIF_file,name_size)
 | 
						|
                || ! Write_bytes(GIF_file, context->Original_file_name, name_size)
 | 
						|
                || ! Write_byte(GIF_file,0))
 | 
						|
                  File_error=1;
 | 
						|
              }
 | 
						|
            }
 | 
						|
 | 
						|
            // On écrit un GIF TERMINATOR, exigé par SVGA et SEA.
 | 
						|
            if (! Write_byte(GIF_file,'\x3B'))
 | 
						|
              File_error=1;
 | 
						|
          }
 | 
						|
 | 
						|
        } // On a pu écrire la palette
 | 
						|
        else
 | 
						|
          File_error=1;
 | 
						|
 | 
						|
      } // On a pu écrire le LSDB
 | 
						|
      else
 | 
						|
        File_error=1;
 | 
						|
 | 
						|
      // Libération de la mémoire utilisée par les tables
 | 
						|
      free(alphabet);
 | 
						|
 | 
						|
    } // On a pu écrire la signature du fichier
 | 
						|
    else
 | 
						|
      File_error=1;
 | 
						|
 | 
						|
    fclose(GIF_file);
 | 
						|
    if (File_error)
 | 
						|
      Remove_file(context);
 | 
						|
 | 
						|
  } // On a pu ouvrir le fichier en écriture
 | 
						|
  else
 | 
						|
    File_error=1;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/** @} */
 |