/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/
#include "dumpToGif.h"

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include <visu_tools.h>

#include <glib.h>
/* #include <unistd.h> */
#include <time.h>

/**
 * SECTION:dumpToGif
 * @short_description: add an export capability into GIF files.
 *
 * <para>This provides a write routine to export V_Sim views into GIF
 * files.</para>
 *
 * <para>Most of the routines used there have been modified by
 * L. Billard (1997 - 2001) from the original ones taken from the
 * ImageMagick package of cristy@dupont.com.</para>
 *
 * <para>The goal of this is to reduce the colour span into 256 to be
 * able to output as GIF file.</para>
 *
 * <note>
 * <para>Copyright 1994 E. I. du Pont de Nemours & Company</para>
 * <para>Permission to use, copy, modify, distribute, and sell this
 * software and its documentation for any purpose is hereby granted
 * without fee, provided that the above copyright notice appear in all
 * copies and that both that copyright notice and this permission
 * notice appear in supporting documentation, and that the name of
 * E. I. du Pont de Nemours & Company not be used in advertising or
 * publicity pertaining to distribution of the software without
 * specific, written prior permission.  E. I. du Pont de Nemours &
 * Company makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express
 * or implied warranty.</para>
 * <para>E. I. du Pont de Nemours & Company disclaims all warranties
 * with regard to this software, including all implied warranties of
 * merchantability and fitness, in no event shall E. I. du Pont de
 * Nemours & Company be liable for any special, indirect or
 * consequential damages or any damages whatsoever resulting from loss
 * of use, data or profits, whether in an action of contract,
 * negligence or other tortious action, arising out of or in
 * connection with the use or performance of this software.</para>
 * </note>
 */

static unsigned char *image;

#define _XOPEN_SOURCE_EXTENDED

static Image *img;

static FILE *file;

#define False  0
#define True  1
#define Max(x,y)  (((x) > (y)) ? (x) : (y))
#define Min(x,y)  (((x) < (y)) ? (x) : (y))


#define MaxMapSize  65535
#define MaxRGB  255


#define color_number  number_colors
#define MaxNodes  266817
#define MaxTreeDepth  8  /* Log2(MaxRGB) */
#define NodesInAList  2048

static gpointer waitData;
static ToolVoidDataFunc waitFunc;


typedef struct _Node {
  struct _Node *parent, *child[8];
  unsigned char id, level, children, mid_red, mid_green, mid_blue;
  unsigned long number_colors, number_unique, 
           total_red, total_green, total_blue;
} Node;

typedef struct _Nodes {
  Node nodes[NodesInAList];
  struct _Nodes *next;
} Nodes;

typedef struct _Cube{
  Node *root;
  ColorPacket color, *colormap;
  guint depth;
  unsigned long colors, pruning_threshold, next_pruning_threshold,
         distance, squares[MaxRGB+MaxRGB+1];
  guint shift[MaxTreeDepth+1], nodes, free_nodes, color_number;
  Node *next_node;
  Nodes *node_queue;
} Cube;

static Cube cube;

static guint tree_depth = 8;

static guint Assignment();
static guint Classification(GError **error);
static void ClosestColor(register Node *node);
static void Map(register Node *node);
static guint DitherImage();
static guint InitializeCube(guint number_pixels, GError **error);
static Node *InitializeNode(
  guint id,guint level,Node *parent,
  guint mid_red,guint mid_green,guint mid_blue);
static void PruneChild(register Node *node);
static void PruneLevel(register Node *node);
static void Reduce(register Node *node);
static void Reduction(guint number_colors);
  
static guint LZWEncodeImage(guint data_size);
static void LSBFirstWriteShort(guint value);

static gboolean writeViewInGifFormat(ToolFileFormat *format, const char* filename,
				      VisuGlNodeScene *scene, guint width, guint height,
				      GError **error, ToolVoidDataFunc functionWait,
                                      gpointer data);


/******************************************************************************/
/******************************************************************************/

void dumpToGif_setImage(Image *data)
{
  img = data;
}

/******************************************************************************/

guint Assignment() {


  img->colormap=g_malloc(cube.colors*sizeof(ColorPacket));
  cube.colormap=img->colormap;
  cube.colors=0;
  Map(cube.root);
  img->colors=(guint) cube.colors;
  if (DitherImage()) return 1;
  return 0;
}

/******************************************************************************/

guint Classification(GError **error) {

  register guint i;
  register Node *node;
  register  ColorPacket *p;
  register guint bisect, id, level;

  p=img->pixels;
  for (i=0; i < img->packets; i++) {
    if (cube.nodes > MaxNodes) {
        /* Prune one level if the color tree is too large. */
        PruneLevel(cube.root);
        cube.depth--;
    }
    /* Start at the root and descend the color cube tree. */
    node=cube.root;
    for (level=1; level <= cube.depth; level++) {
      id=(p->red > node->mid_red ? 1 : 0) |
         (p->green > node->mid_green ? 1 : 0) << 1 |
         (p->blue > node->mid_blue ? 1 : 0) << 2;
      if (node->child[id] == (Node *) NULL) {
          /* Set colors of new node to contain pixel. */
          node->children|=1 << id;
          bisect=(guint) (1 << (MaxTreeDepth-level)) >> 1;
          node->child[id]=InitializeNode(id,level,node,
            node->mid_red+(id & 1 ? bisect : -bisect),
            node->mid_green+(id & 2 ? bisect : -bisect),
            node->mid_blue+(id & 4 ? bisect : -bisect));
          if (node->child[id] == (Node *) NULL)
	    {
	      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_ENCODE,
				   _("Unable to quantize image, "
				     "initialisation failed for node child %d."), id);
              return 1;
	    }
          if (level == cube.depth) cube.colors++;
      }
      /*
        Record the number of colors represented by this node.  
        Shift by level in the color description tree.
      */
      node=node->child[id];
      node->number_colors+=1 << cube.shift[level];
    }
    /*
      Increment unique color count and sum RGB values for this leaf for later
      derivation of the mean cube color.
    */
    node->number_unique+=1;
    node->total_red+=p->red;
    node->total_green+=p->green;
    node->total_blue+=p->blue;
    p++;
  }
  return 0;
}
 
/******************************************************************************/

void ClosestColor(register Node *node) {

  register guint id;

  /* Traverse any children. */
  if (node->children != 0)
    for (id=0; id < 8; id++)
      if (node->children & (1 << id))
        ClosestColor(node->child[id]);
  if (node->number_unique != 0) {
      register ColorPacket *color;
      register guint blue_distance, green_distance, red_distance;
      register unsigned long distance;

      /* Determine if this color is "closest". */
      color=cube.colormap+node->color_number;
      red_distance=(int) color->red-(int) cube.color.red+MaxRGB;
      green_distance=(int) color->green-(int) cube.color.green+MaxRGB;
      blue_distance=(int) color->blue-(int) cube.color.blue+MaxRGB;
      distance=cube.squares[red_distance]+
               cube.squares[green_distance]+
               cube.squares[blue_distance];
      if (distance < cube.distance) {
          cube.distance=distance;
          cube.color_number=(unsigned short) node->color_number;
      }
  }
}
 
/******************************************************************************/

void Map(register Node *node) {
  register guint id;

  /* Traverse any children*/
  if (node->children != 0)
    for (id=0; id < 8; id++)
      if (node->children & (1 << id))
        Map(node->child[id]);
  if (node->number_unique > 0) {
      /* Map entry is defined by the mean color in this cube. */
      cube.colormap[cube.colors].red=(unsigned char)
        ((node->total_red+(node->number_unique >> 1))/node->number_unique);
      cube.colormap[cube.colors].green=(unsigned char)
        ((node->total_green+(node->number_unique >> 1))/node->number_unique);
      cube.colormap[cube.colors].blue=(unsigned char)
        ((node->total_blue+(node->number_unique >> 1))/node->number_unique);
      node->color_number=cube.colors++;
  }
}
 
/******************************************************************************/

guint DitherImage() {

#define MaxError  16

  typedef struct {
    int red, green, blue;
  } ErrorPacket;
  ErrorPacket *error;
  int *cache;
  register int blue_error, green_error, red_error, step;
  register Node *node;
  register ColorPacket *q;
  register ErrorPacket *cs, *ns;
  register unsigned char *range_limit;
  register guint id;
  unsigned char blue, green, *range_table, red;
  guint i, x, y;
  unsigned short index;

  
  cache=g_malloc((1 << 18)*sizeof(int));
  error=g_malloc(((img->columns+2) << 1)*sizeof(ErrorPacket));
  range_table=g_malloc(3*(MaxRGB+1)*sizeof(unsigned char));
  
  for (i=0; i < (1 << 18); i++) cache[i]=(-1);
  for (i=0; i < ((img->columns+2) << 1); i++) {
    error[i].red=0;
    error[i].green=0;
    error[i].blue=0;
  }
  for (i=0; i <= MaxRGB; i++) {
    range_table[i]=0;
    range_table[i+(MaxRGB+1)]=(unsigned char) i;
    range_table[i+(MaxRGB+1)*2]=MaxRGB;
  }
  range_limit=range_table+(MaxRGB+1);
  
  for (y=0; y < img->rows; y++) {
    q=img->pixels+img->columns*y;
    cs=error+1;
    ns=error+(img->columns+2)+1;
    step=1;
    if (y & 0x01) {
        /* Distribute error right-to-left for odd scanlines. */
        q+=(img->columns-1);
        cs=error+(img->columns+2)+(img->columns-1)+1;
        ns=error+(img->columns-1)+1;
        step=(-1);
    }
    for (x=0; x < img->columns; x++) {
      red_error=(cs->red+8)/16;
      if (red_error > MaxError) red_error=MaxError;
      else if (red_error < -MaxError) red_error=(-MaxError);
      green_error=(cs->green+8)/16;
      if (green_error > MaxError) green_error=MaxError;
      else if (green_error < -MaxError) green_error=(-MaxError);
      blue_error=(cs->blue+8)/16;
      if (blue_error > MaxError) blue_error=MaxError;
      else if (blue_error < -MaxError) blue_error=(-MaxError);
      red=range_limit[q->red+red_error];
      green=range_limit[q->green+green_error];
      blue=range_limit[q->blue+blue_error];
      i=(red >> 2) << 12 | (green >> 2) << 6 | blue >> 2;
      if (cache[i] < 0) {
          /* Identify the deepest node containing the pixel's color. */
          node=cube.root;
          for ( ; ; ) {
            id=(red > node->mid_red ? 1 : 0) |
               (green > node->mid_green ? 1 : 0) << 1 |
               (blue > node->mid_blue ? 1 : 0) << 2;
            if ((node->children & (1 << id)) == 0) break;
            node=node->child[id];
          }
          /* Find closest color among siblings and their children. */
          cube.color.red=red;
          cube.color.green=green;
          cube.color.blue=blue;
          cube.distance=(unsigned long) (~0);
          ClosestColor(node->parent);
          cache[i]=cube.color_number;
      }
      index=(unsigned short) cache[i];
      red_error=(int) red-(int) cube.colormap[index].red;
      green_error=(int) green-(int) cube.colormap[index].green;
      blue_error=(int) blue-(int) cube.colormap[index].blue;
      q->index=index;
      q+=step;
      /* Propagate the error in these proportions:
                Q     7/16
          3/16  5/16  1/16
      */
      cs->red=0;
      cs->green=0;
      cs->blue=0;
      cs+=step;
      cs->red+=7*red_error;
      cs->green+=7*green_error;
      cs->blue+=7*blue_error;
      ns-=step;
      ns->red+=3*red_error;
      ns->green+=3*green_error;
      ns->blue+=3*blue_error;
      ns+=step;
      ns->red+=5*red_error;
      ns->green+=5*green_error;
      ns->blue+=5*blue_error;
      ns+=step;
      ns->red+=red_error;
      ns->green+=green_error;
      ns->blue+=blue_error;
    }
  }
  
  g_free(range_table);
  g_free(error);
  g_free(cache);
  return 0;
}

/******************************************************************************/

guint InitializeCube(guint number_pixels, GError **error) {
  
  char c;
  register int i;
  guint bits, level, max_shift;

  cube.node_queue=(Nodes *) NULL;
  cube.nodes=0;
  cube.free_nodes=0;
  cube.depth=Min(tree_depth,8);
  /* Initialize the shift values. */
  c=1;
  for (bits=0; c != (char) 0; bits++) c<<=1;
  for (max_shift=sizeof(guint)*bits; number_pixels != 0; max_shift--)
       number_pixels>>=1;
  for (level=0; level <= cube.depth; level++) {
    cube.shift[level]=max_shift;
    if (max_shift != 0) max_shift--;
  }
  /* Initialize root node. */
  cube.root=InitializeNode(0,0,(Node *) NULL,
     (MaxRGB+1) >> 1,(MaxRGB+1) >> 1, (MaxRGB+1) >> 1);
  if (cube.root == (Node *) NULL)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_ENCODE,
			   _("Unable to quantize image, initialisation failed."));
      return 1;
    }
  cube.root->parent=cube.root;
  cube.root->number_colors=(unsigned long) (~0);
  cube.colors=0;
  /* Initialize the square values. */
  for (i=(-MaxRGB); i <= MaxRGB; i++)
    cube.squares[i+MaxRGB]=i*i;
    
  return 0;
}

/******************************************************************************/

Node *InitializeNode(
  guint id,guint level,Node *parent,
  guint mid_red,guint mid_green,guint mid_blue) {
  
  register int i;

  register Node *node;

  if (cube.free_nodes == 0) {
      register Nodes *nodes;

      /* Allocate a new nodes of nodes. */
      nodes=(Nodes *) malloc(sizeof(Nodes));
      if (nodes == (Nodes *) NULL)
        return((Node *) NULL);
      nodes->next=cube.node_queue;
      cube.node_queue=nodes;
      cube.next_node=nodes->nodes;
      cube.free_nodes=NodesInAList;
  }
  cube.nodes++;
  cube.free_nodes--;
  node=cube.next_node++;
  node->parent=parent;
  for (i=0; i < 8; i++)
    node->child[i]=(Node *) NULL;
  node->id=id;
  node->level=level;
  node->children=0;
  node->mid_red=mid_red;
  node->mid_green=mid_green;
  node->mid_blue=mid_blue;
  node->number_colors=0;
  node->number_unique=0;
  node->total_red=0;
  node->total_green=0;
  node->total_blue=0;
  return(node);
}

/******************************************************************************/

void PruneChild(register Node *node) {
  register Node *parent;

  /* Merge color statistics into parent. */
  parent=node->parent;
  parent->children&=~(1 << node->id);
  parent->number_unique+=node->number_unique;
  parent->total_red+=node->total_red;
  parent->total_green+=node->total_green;
  parent->total_blue+=node->total_blue;
  cube.nodes--;
}

/******************************************************************************/

void PruneLevel(register Node *node) {
  register int id;

  /* Traverse any children. */
  if (node->children != 0)
    for (id=0; id < 8; id++)
      if (node->children & (1 << id))
        PruneLevel(node->child[id]);
  if (node->level == cube.depth)
    PruneChild(node);
}

/******************************************************************************/

void Reduce(register Node *node) {
  register guint id;

  /* Traverse any children. */
  if (node->children != 0)
    for (id=0; id < 8; id++)
      if (node->children & (1 << id))
        Reduce(node->child[id]);
        
  /* Node is a colormap entry if it has unique colors. */
  if (node->number_unique > 0) cube.colors++;
  
  /* Find minimum pruning threshold. */
  if (node->number_colors < cube.next_pruning_threshold)
    cube.next_pruning_threshold=node->number_colors;
  if (node->number_colors <= cube.pruning_threshold)
    PruneChild(node); /* Node has a sub-threshold color count */
}

/******************************************************************************/

void Reduction(guint number_colors)
{
  float i;

  i = 0.;
  cube.next_pruning_threshold=1;
  while (cube.colors > number_colors) {
    cube.pruning_threshold=cube.next_pruning_threshold;
    cube.next_pruning_threshold=cube.root->number_colors-1;
    cube.colors=0;
    i += 0.025;
    if (waitFunc && (int)(i * 100.) % 100 == 0 && i < 50.)
      waitFunc(waitData);
    Reduce(cube.root);
  }
  while (waitFunc && i<50.)
    {
      i = i + 1.;
      waitFunc(waitData);
    }
}

/******************************************************************************/

guint dumpToGif_quantizeImage(guint number_colors, GError **error,
				     ToolVoidDataFunc functionWait, gpointer data)
{
  Nodes *nodes;

  waitFunc = functionWait;
  waitData = data;

  /* Reduce the number of colors in the continuous tone image. */
  if (number_colors > MaxMapSize) number_colors=MaxMapSize;
  if(InitializeCube(img->columns*img->rows, error)) return 1;
  
  if(Classification(error)) return 1;
  Reduction(number_colors);
  if(Assignment(error)) return 1;

  do {
    nodes=cube.node_queue->next;
    (void)free(cube.node_queue);
    cube.node_queue=nodes;
  }
  while (cube.node_queue != (Nodes *) NULL);
  
  return 0;
  
}

/******************************************************************************/
/******************************************************************************/

/* Function LZWEncodeImage compresses an image via LZW-coding. */
guint LZWEncodeImage(guint data_size) {
#define MaxCode(number_bits)  ((1 << (number_bits))-1)
#define MaxHashTable  5003
#define MaxLZWBits  12
#define MaxLZWTable  (1 << MaxLZWBits)
#define LZWOutputCode(code) \
{ \
  if (bits > 0) datum|=((long) code << bits); \
  else datum=(long) code; \
  bits+=number_bits; \
  while (bits >= 8) { \
    packet[byte_count++]=(unsigned char) (datum & 0xff); \
    if (byte_count >= 254) { \
        (void) fputc(byte_count,file); \
        (void) fwrite((char *) packet,1,byte_count,file); \
        byte_count=0; \
    } \
    datum>>=8; \
    bits-=8; \
  } \
  if (free_code > max_code) { \
      number_bits++; \
      if (number_bits == MaxLZWBits)  max_code=MaxLZWTable; \
      else max_code=MaxCode(number_bits); \
  } \
}

  int bits, byte_count, next_pixel, number_bits;
  long datum;
  register unsigned i;
  register int displacement, j;
  register ColorPacket *p;
  short clear_code, end_of_information_code;
  short free_code, *hash_code, *hash_prefix, index, max_code, waiting_code;
  unsigned char *packet, *hash_suffix;

  

  packet=g_malloc(256*sizeof(unsigned char));
  hash_code=g_malloc(MaxHashTable*sizeof(short));
  hash_prefix=g_malloc(MaxHashTable*sizeof(short));
  hash_suffix=g_malloc(MaxHashTable*sizeof(unsigned char));
  
  number_bits=data_size;
  max_code=MaxCode(number_bits);
  clear_code=((short) 1 << (data_size-1));
  end_of_information_code=clear_code+1;
  free_code=clear_code+2;
  byte_count=0;
  datum=0;
  bits=0;
  for (i=0; i < MaxHashTable; i++) hash_code[i]=0;
  LZWOutputCode(clear_code);
  
  p=img->pixels;
  waiting_code=p->index;
  for (i=1; i < (img->columns*img->rows); i++) {
    
    if (waitFunc && i % (img->columns*img->rows / 50) == 0)
      waitFunc(waitData);

    p++;
    index=p->index & 0xff;
    j=(int) ((int) index << (MaxLZWBits-8))+waiting_code;
    if (j >= MaxHashTable) j-=MaxHashTable;
    if (hash_code[j] > 0) {
        if ((hash_prefix[j] == waiting_code) && (hash_suffix[j] == index))  {
            waiting_code=hash_code[j];
            continue;
        }
        if (j == 0) displacement=1;
        else displacement=MaxHashTable-j;
        next_pixel=False;
        for ( ; ; ) {
          j-=displacement;
          if (j < 0) j+=MaxHashTable;
          if (hash_code[j] == 0)  break;
          if ((hash_prefix[j] == waiting_code) && (hash_suffix[j] == index)) {
              waiting_code=hash_code[j];
              next_pixel=True;
              break;
          }
        }
        if (next_pixel == True) continue;
    }
    LZWOutputCode(waiting_code);
    if (free_code < MaxLZWTable) {
        hash_code[j]=free_code++;
        hash_prefix[j]=waiting_code;
        hash_suffix[j]=(unsigned char)index;
    }
    else {
        for (j=0; j < MaxHashTable; j++)  hash_code[j]=0;
        free_code=clear_code+2;
        LZWOutputCode(clear_code);
        number_bits=data_size;
        max_code=MaxCode(number_bits);
    }
    waiting_code=index;
  }
  
  
  LZWOutputCode(waiting_code);
  LZWOutputCode(end_of_information_code);
  if (bits > 0) {
      /* Add a character to current packet. */
      packet[byte_count++]=(unsigned char) (datum & 0xff);
      if (byte_count >= 254)  {
          (void) fputc(byte_count,file);
          (void) fwrite((char *) packet,1,byte_count,file);
          byte_count=0;
      }
  }
  
  if (byte_count > 0) {
      (void) fputc(byte_count,file);
      (void) fwrite((char *) packet,1,byte_count,file);
  }
  
  g_free(hash_suffix);
  g_free(hash_prefix);
  g_free(hash_code);
  g_free(packet);
  
  if (i < img->packets) return(False);
  return(True);
  
}


/******************************************************************************/

/* LSBFirstWriteShort writes a long value as a 16 bit quantity in
   least-significant byte first order.
*/
void LSBFirstWriteShort(guint value) {

  unsigned char buffer[2];

  buffer[0]=(unsigned char) (value);
  buffer[1]=(unsigned char) ((value) >> 8);
  (void) fwrite((char *) buffer,1,2,file);
}


/******************************************************************************/

void dumpToGif_syncImage(void) {

  register guint i;
  register ColorPacket *p;
  register unsigned short index;

  p=img->pixels;
  for (i=0; i < img->packets; i++) {
    index=p->index;
    p->red=img->colormap[index].red;
    p->green=img->colormap[index].green;
    p->blue=img->colormap[index].blue;
    p++;
  }
}

/******************************************************************************/

static void write_comment_comm(unsigned char comm[]) {

   size_t size;   
   unsigned char c[256];
   
   /* Extension Introducer */
   c[0] = 0x21;
   (void)fwrite(c, sizeof(unsigned char), 1, file);
   
   /* Comment Label */
   c[0] = 0xFE;
   (void)fwrite(c, sizeof(unsigned char), 1, file);
   
   /* Data Size ( entre 1 et 255 ) */
   size = strlen((char *)comm);
   c[0] = (unsigned char)size;
   (void)fwrite(c, sizeof(unsigned char), 1, file);
   
   /* Data */
   (void)fwrite(comm, sizeof(unsigned char), size, file);
   
   /* Block Terminator */
   c[0] = '\0';
   (void)fwrite(c, sizeof(unsigned char), 1, file);
   
}

/******************************************************************************/

VisuDump* dumpToGif_init()
{
  VisuDump *gif;
  const gchar *typeGIF[] = {"*.gif", (char*)0};
  #define descrGIF _("Gif (256 colors) file")

  gif = VISU_DUMP(visu_dump_scene_new(descrGIF, typeGIF, writeViewInGifFormat, FALSE));

  waitData = (gpointer)0;
  waitFunc = (ToolVoidDataFunc)0;

  return gif;
}


static gboolean writeViewInGifFormat(ToolFileFormat *format _U_, const char* filename,
				      VisuGlNodeScene *scene, guint width, guint height,
				      GError **error, ToolVoidDataFunc functionWait,
                                      gpointer data)
{
  register guint i;
  register ColorPacket *q;
  register unsigned char *p;
  unsigned char bits_per_pixel, c;
  guint status;
  GArray *imageData;

  g_return_val_if_fail(error && !*error, FALSE);

  imageData = visu_gl_ext_set_getPixmapData(VISU_GL_EXT_SET(scene),
                                            width, height, FALSE);
  if (!imageData)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_OPENGL,
                           _("Can't dump OpenGL area to data.\n"));
      return FALSE;
    }

  waitData   = data;
  waitFunc   = functionWait;
  image      = (unsigned char*)imageData->data;

  DBG_fprintf(stderr, "Dump Gif : begin export in %dx%d...\n", width, height);

  file = fopen(filename, "wb");
  if(!file)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_FILE,
			   _("Cannot open file (to write in)."));
      return FALSE;
    }

  img=g_malloc(sizeof(Image));
  img->colormap = (ColorPacket*)0;

  img->columns = width;
  img->rows = height;
  img->packets=img->columns*img->rows;
  img->pixels=g_malloc(img->packets*sizeof(ColorPacket));
  q=img->pixels;
  p=image;
  for (i=0; i < img->packets; i++) {
    q->red=(*p++);
    q->green=(*p++);
    q->blue=(*p++);
    q->index=0;
    q++;
  }
  
  if(dumpToGif_quantizeImage(256, error, waitFunc, data))
    {
      g_free(img->pixels);
      if (img->colormap)
	g_free(img->colormap);
      g_free(img);
      return FALSE;
    }
  dumpToGif_syncImage();
   
  for (bits_per_pixel=1; bits_per_pixel < 8; bits_per_pixel++)
    if ((1 << bits_per_pixel) >= (int)img->colors) break;
  
  
  (void) fwrite("GIF89a",1,6,file);
  LSBFirstWriteShort(img->columns);
  LSBFirstWriteShort(img->rows);
  c=0x80;  /* global colormap */
  c|=(8-1) << 4;  /* color resolution */
  c|=(bits_per_pixel-1);   /* size of global colormap */
  (void) fputc((int)(char) c,file);
  (void) fputc(0x0,file);  /* background color */
  (void) fputc(0x0,file);  /* reserved */
  
  
  for (i=0; i < img->colors; i++) {
    (void) fputc((int)(char) img->colormap[i].red,file);
    (void) fputc((int)(char) img->colormap[i].green,file);
    (void) fputc((int)(char) img->colormap[i].blue,file);
  }
  for ( ; i < (guint)(1 << bits_per_pixel) ; i++) {
    (void) fputc(0x0,file);
    (void) fputc(0x0,file);
    (void) fputc(0x0,file);
  }
  /* écriture du commentaire */
  write_comment_comm((unsigned char *)"Image créée par le programme V_Sim\n"
		     "Auteur : L. BILLARD");
      
  (void) fputc(',',file);  /* image separator */
  
  
  LSBFirstWriteShort(0);
  LSBFirstWriteShort(0);
  LSBFirstWriteShort(img->columns);
  LSBFirstWriteShort(img->rows);
  (void) fputc(0x0,file);
  c=Max(bits_per_pixel,2);
  (void) fputc((int)(char) c,file);
  status=LZWEncodeImage(Max(bits_per_pixel,2)+1);
  if (status == False)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_ENCODE,
			   _("Fail to compress the GIF file."));
      g_free(img->pixels);
      g_free(img->colormap);
      g_free(img);
      return FALSE;
  }
  (void) fputc(0x0,file);
  (void) fputc(';',file); /* terminator */
   
  (void) fclose(file);
   
  g_free(img->pixels);
  g_free(img->colormap);
  g_free(img);

  g_array_free(imageData, TRUE);
  
  return TRUE;
}
