/* vim:expandtab:ts=2 sw=2:
*/
/*  Grafx2 - The Ultimate 256-color bitmap paint program

    Copyright 2018 Thomas Bernard
    Copyright 2011 Pawel Góralski
    Copyright 2009 Petter Lindquist
    Copyright 2008 Yves Rizoud
    Copyright 2008 Franck Charlet
    Copyright 2007-2011 Adrien Destugues
    Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud)

    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 miscfileformats.c
/// Formats that aren't fully saving, either because of palette restrictions or other things

#include <limits.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
#include <stdio.h>
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#endif

#include "engine.h"
#include "errors.h"
#include "global.h"
#include "io.h"
#include "libraw2crtc.h"
#include "loadsave.h"
#include "misc.h"
#include "screen.h"
#include "struct.h"
#include "windows.h"
#include "oldies.h"
#include "pages.h"
#include "keycodes.h"
#include "input.h"
#include "help.h"
#include "fileformats.h"

extern char Program_version[]; // generated in pversion.c
extern const char SVN_revision[]; // generated in version.c

//////////////////////////////////// PAL ////////////////////////////////////
//

// -- Test wether a file is in PAL format --------------------------------
void Test_PAL(T_IO_Context * context, FILE * file)
{
  char buffer[32];
  long file_size;

  (void)context;
  File_error = 1;

  file_size = File_length_file(file);
  // First check for GrafX2 legacy palette format. The simplest one, 768 bytes
  // of RGB data. It is a raw dump of the T_Palette structure. There is no
  // header at all, so we check for the file size.
  if (file_size == sizeof(T_Palette))
    File_error = 0;
  else if (file_size > 8)
  {
    // Bigger (or smaller ?) files may be in other formats. These have an
    // header, so look for it.
    if (!Read_bytes(file, buffer, 8))
      return;
    if (strncmp(buffer,"JASC-PAL",8) == 0)
    {
      // JASC file format, used by Paint Shop Pro and GIMP. This is also the
      // one used for saving, as it brings greater interoperability.
      File_error = 0;
    }
    else if(strncmp(buffer,"RIFF", 4) == 0)
    {
      // Microsoft RIFF file
      // This is a data container (similar to IFF). We only check the first
      // chunk header, and give up if that's not a palette.
      fseek(file, 8, SEEK_SET);
      if (!Read_bytes(file, buffer, 8))
        return;
      if (strncmp(buffer, "PAL data", 8) == 0)
      {
        File_error = 0;
      }
    }
  }
}

/// Test for GPL (Gimp Palette) file format
void Test_GPL(T_IO_Context * context, FILE * file)
{
  char buffer[16];
  long file_size;

  (void)context;
  File_error = 1;

  file_size = File_length_file(file);
  if (file_size > 33) {
    // minimum header length == 33
    // "GIMP Palette" == 12
    if (!Read_bytes(file, buffer, 12))
      return;
    if (strncmp(buffer,"GIMP Palette",12) == 0)
      File_error = 0;
  }
}

/// skip the padding before a space-padded field.
static int skip_padding(FILE *file, int max_chars)
{
  byte b;
  int chars_read = 0;

  do {
    if (chars_read == max_chars)
      return chars_read; // eof
    if (!Read_byte(file, &b))
      return chars_read;
    chars_read++;
  } while (b == ' ');

  fseek(file, -1, SEEK_CUR);
  return chars_read;
}

/// Load GPL (Gimp Palette) file format
void Load_GPL(T_IO_Context * context)
{
  FILE *file;
  char buffer[256];

  File_error = 1;

  file = Open_file_read(context);
  if (file == NULL)
    return;

  if (!Read_byte_line(file, buffer, sizeof(buffer)))
    return;

  if (memcmp(buffer,"GIMP Palette",12) == 0)
  {
    int i, r, g, b, columns;
    size_t len;
    // Name: xxxxx
    if (!Read_byte_line(file, buffer, sizeof(buffer)))
      return;
    len = strlen(buffer);
    while (len > 0)
    {
      len--;
      if (buffer[len] == '\r' || buffer[len] == '\n')
        buffer[len] = '\0';
    }
    GFX2_Log(GFX2_DEBUG, "GPL %s\n", buffer);
    if (0 == memcmp(buffer, "Name: ", 6))
      snprintf(context->Comment, sizeof(context->Comment), "GPL: %s", buffer + 6);

    // Columns: 16
    if (fscanf(file, "Columns: %d", &columns) != 1)
      return;
    Read_byte_line(file, buffer, sizeof(buffer));
    /// @todo set grafx2 columns setting to match.
    // #<newline>
    if (!Read_byte_line(file, buffer, sizeof(buffer)))
      return;

    for (i = 0; i < 256; i++)
    {
      skip_padding(file, 32);
      if (fscanf(file, "%d", &r) != 1)
        break;
      skip_padding(file, 32);
      if (fscanf(file, "%d", &g) != 1)
        break;
      skip_padding(file, 32);
      if (fscanf(file, "%d\t", &b) != 1)
        break;
      if (!Read_byte_line(file, buffer, sizeof(buffer)))
        break;
      len = strlen(buffer);
      while (len > 1)
      {
        len--;
        if (buffer[len] == '\r' || buffer[len] == '\n')
          buffer[len] = '\0';
      }
      /// @todo analyze color names to build shade table

      GFX2_Log(GFX2_DEBUG, "GPL: %3d: RGB(%3d,%3d,%3d) %s\n", i, r,g,b, buffer);
      context->Palette[i].R = r;
      context->Palette[i].G = g;
      context->Palette[i].B = b;
    }
    if (i > 0)  // at least one color was read
      File_error = 0;
  }
  else
    File_error = 2;

  // close the file
  fclose(file);
}


/// Save GPL (Gimp Palette) file format
void
Save_GPL (T_IO_Context * context)
{
  // Gimp is a unix program, so use Unix file endings (LF aka '\n')
  FILE *file;

  file = Open_file_write(context);

  if (file != NULL )
  {
    int i;

    File_error = 0;
    fprintf (file, "GIMP Palette\n");
    fprintf (file, "Name: %s\n", context->File_name);
    // TODO: use actual columns value
    fprintf (file, "Columns: %d\n#\n", 16);

    for (i = 0; i < 256 && File_error==0; i++)
    {
      // TODO: build names from shade table data
      if (fprintf(file,"%d %d %d\tUntitled\n",context->Palette[i].R, context->Palette[i].G, context->Palette[i].B) <= 0)
        File_error=1;
    }
    fclose(file);

    if (File_error)
      Remove_file(context);
  }
  else
  {
    // unable to open output file, nothing saved.
    File_error=1;
  }
}



// -- Lire un fichier au format PAL -----------------------------------------
void Load_PAL(T_IO_Context * context)
{
  FILE *file;              // Fichier du fichier

  File_error=0;

  // Ouverture du fichier
  if ((file=Open_file_read(context)))
  {
    long file_size = File_length_file(file);
    // Le fichier ne peut être au format PAL que si sa taille vaut 768 octets
    if (file_size == sizeof(T_Palette))
    {
      T_Palette palette_64;
      // Pre_load(context, ?); // Pas possible... pas d'image...

      // Lecture du fichier dans context->Palette
      if (Read_bytes(file, palette_64, sizeof(T_Palette)))
      {
        Palette_64_to_256(palette_64);
        memcpy(context->Palette, palette_64, sizeof(T_Palette));
      }
      else
        File_error = 2;
    } else {
      char buffer[16];
      if (!Read_bytes(file, buffer, 8))
      {
        File_error = 2;
        fclose(file);
        return;
      }
      buffer[8] = '\0';
      if (strncmp(buffer,"JASC-PAL",8) == 0)
      {
        int i, n, r, g, b;
        i = fscanf(file, "%d",&n);
        if(i != 1 || n != 100)
        {
          File_error = 2;
          fclose(file);
          return;
        }
        // Read color count
        if (fscanf(file, "%d",&n) == 1)
        {
          for (i = 0; i < n; i++)
          {
            if (fscanf(file, "%d %d %d",&r, &g, &b) == 3)
            {
              context->Palette[i].R = r;
              context->Palette[i].G = g;
              context->Palette[i].B = b;
            }
            else
              File_error = 2;
          }
        }
        else
          File_error = 2;
      }
      else if(strncmp(buffer, "RIFF", 4) == 0)
      {
        // Microsoft RIFF format.
        fseek(file, 8, SEEK_SET);
        Read_bytes(file, buffer, 8);
        if (strncmp(buffer, "PAL data", 8) == 0)
        {
          word color_count;
          word i = 0;

          fseek(file, 22, SEEK_SET);
          if (!Read_word_le(file, &color_count))
            File_error = 2;
          else
            for(i = 0; i < color_count && File_error == 0; i++)
            {
              byte colors[4];
              if (!Read_bytes(file, colors, 4))
                File_error = 2;
              context->Palette[i].R = colors[0];
              context->Palette[i].G = colors[1];
              context->Palette[i].B = colors[2];
            }
        } else File_error = 2;
      } else
        File_error = 2;
    }

    fclose(file);
  }
  else
    // Si on n'a pas réussi à ouvrir le fichier, alors il y a eu une erreur
    File_error=1;
}


// -- Sauver un fichier au format PAL ---------------------------------------
void Save_PAL(T_IO_Context * context)
{
  // JASC-PAL is a DOS/Windows format, so use CRLF line endings "\r\n"
  FILE *file;

  File_error=0;

  // Open output file
  if ((file=Open_file_write(context)) != NULL)
  {
    int i;

    setvbuf(file, NULL, _IOFBF, 64*1024);

    if (fputs("JASC-PAL\r\n0100\r\n256\r\n", file)==EOF)
      File_error=1;
    for (i = 0; i < 256 && File_error==0; i++)
    {
      if (fprintf(file,"%d %d %d\r\n",context->Palette[i].R, context->Palette[i].G, context->Palette[i].B) <= 0)
        File_error=1;
    }

    fclose(file);

    if (File_error)
      Remove_file(context);
  }
  else
  {
    // unable to open output file, nothing saved.
    File_error=1;
  }
}


//////////////////////////////////// PKM ////////////////////////////////////
typedef struct
{
  char Ident[3];    // String "PKM" }
  byte Method;      // Compression method
                    //   0 = per-line compression (c)KM
                    //   others = unknown at the moment
  byte Recog1;      // Recognition byte 1
  byte Recog2;      // Recognition byte 2
  word Width;       // Image width
  word Height;      // Image height
  T_Palette Palette;// RGB Palette 256*3, on a 1-64 scale for each component
  word Jump;        // Size of the jump between header and image:
                    //   Used to insert a comment
} T_PKM_Header;

// -- Tester si un fichier est au format PKM --------------------------------
void Test_PKM(T_IO_Context * context, FILE * file)
{
  T_PKM_Header header;

  (void)context;
  File_error=1;

  // Lecture du header du fichier
  if (Read_bytes(file,&header.Ident,3) &&
      Read_byte(file,&header.Method) &&
      Read_byte(file,&header.Recog1) &&
      Read_byte(file,&header.Recog2) &&
      Read_word_le(file,&header.Width) &&
      Read_word_le(file,&header.Height) &&
      Read_bytes(file,&header.Palette,sizeof(T_Palette)) &&
      Read_word_le(file,&header.Jump))
  {
    // On regarde s'il y a la signature PKM suivie de la méthode 0.
    // La constante "PKM" étant un chaîne, elle se termine toujours par 0.
    // Donc pas la peine de s'emm...er à regarder si la méthode est à 0.
    if ( (!memcmp(&header,"PKM",4)) && header.Width && header.Height)
      File_error=0;
  }
}


// -- Lire un fichier au format PKM -----------------------------------------
void Load_PKM(T_IO_Context * context)
{
  FILE *file;             // Fichier du fichier
  T_PKM_Header header;
  byte  color;
  byte  temp_byte;
  word  len;
  word  index;
  dword Compteur_de_pixels;
  dword Compteur_de_donnees_packees;
  dword image_size;
  dword Taille_pack;
  long  file_size;

  File_error=0;

  if ((file=Open_file_read(context)))
  {
    file_size=File_length_file(file);

    if (Read_bytes(file,&header.Ident,3) &&
        Read_byte(file,&header.Method) &&
        Read_byte(file,&header.Recog1) &&
        Read_byte(file,&header.Recog2) &&
        Read_word_le(file,&header.Width) &&
        Read_word_le(file,&header.Height) &&
        Read_bytes(file,&header.Palette,sizeof(T_Palette)) &&
        Read_word_le(file,&header.Jump))
    {
      context->Comment[0]='\0'; // On efface le commentaire
      if (header.Jump)
      {
        index=0;
        while ( (index<header.Jump) && (!File_error) )
        {
          if (Read_byte(file,&temp_byte))
          {
            index+=2; // On rajoute le "Field-id" et "le Field-size" pas encore lu
            switch (temp_byte)
            {
              case 0 : // Commentaire
                if (Read_byte(file,&temp_byte))
                {
                  if (temp_byte>COMMENT_SIZE)
                  {
                    color=temp_byte;              // On se sert de color comme
                    temp_byte=COMMENT_SIZE;   // variable temporaire
                    color-=COMMENT_SIZE;
                  }
                  else
                    color=0;

                  if (Read_bytes(file,context->Comment,temp_byte))
                  {
                    index+=temp_byte;
                    context->Comment[temp_byte]='\0';
                    if (color)
                      if (fseek(file,color,SEEK_CUR))
                        File_error=2;
                  }
                  else
                    File_error=2;
                }
                else
                  File_error=2;
                break;

              case 1 : // Dimensions de l'écran d'origine
                if (Read_byte(file,&temp_byte))
                {
                  if (temp_byte==4)
                  {
                    index+=4;
                    if ( ! Read_word_le(file,(word *) &Original_screen_X)
                      || !Read_word_le(file,(word *) &Original_screen_Y) )
                      File_error=2;
                  }
                  else
                    File_error=2;
                }
                else
                  File_error=2;
                break;

              case 2 : // color de transparence
                if (Read_byte(file,&temp_byte))
                {
                  if (temp_byte==1)
                  {
                    index++;
                    if (! Read_byte(file,&Back_color))
                      File_error=2;
                  }
                  else
                    File_error=2;
                }
                else
                  File_error=2;
                break;

              default:
                if (Read_byte(file,&temp_byte))
                {
                  index+=temp_byte;
                  if (fseek(file,temp_byte,SEEK_CUR))
                    File_error=2;
                }
                else
                  File_error=2;
            }
          }
          else
            File_error=2;
        }
        if ( (!File_error) && (index!=header.Jump) )
          File_error=2;
      }

      /*Init_lecture();*/

      if (!File_error)
      {
        Pre_load(context, header.Width,header.Height,file_size,FORMAT_PKM,PIXEL_SIMPLE,0);
        if (File_error==0)
        {

          context->Width=header.Width;
          context->Height=header.Height;
          image_size=(dword)(context->Width*context->Height);
          // Palette lue en 64
          memcpy(context->Palette,header.Palette,sizeof(T_Palette));
          Palette_64_to_256(context->Palette);

          Compteur_de_donnees_packees=0;
          Compteur_de_pixels=0;
          // Header size is 780
          Taille_pack=(file_size)-780-header.Jump;

          // Boucle de décompression:
          while ( (Compteur_de_pixels<image_size) && (Compteur_de_donnees_packees<Taille_pack) && (!File_error) )
          {
            if(Read_byte(file, &temp_byte)!=1)
            {
              File_error=2;
              break;
            }

            // Si ce n'est pas un octet de reconnaissance, c'est un pixel brut
            if ( (temp_byte!=header.Recog1) && (temp_byte!=header.Recog2) )
            {
              Set_pixel(context, Compteur_de_pixels % context->Width,
                                  Compteur_de_pixels / context->Width,
                                  temp_byte);
              Compteur_de_donnees_packees++;
              Compteur_de_pixels++;
            }
            else // Sinon, On regarde si on va décompacter un...
            { // ... nombre de pixels tenant sur un byte
                if (temp_byte==header.Recog1)
                {
                  if(Read_byte(file, &color)!=1)
                {
                    File_error=2;
                    break;
                }
                if(Read_byte(file, &temp_byte)!=1)
                {
                    File_error=2;
                    break;
                }
                for (index=0; index<temp_byte; index++)
                  Set_pixel(context, (Compteur_de_pixels+index) % context->Width,
                                      (Compteur_de_pixels+index) / context->Width,
                                      color);
                Compteur_de_pixels+=temp_byte;
                Compteur_de_donnees_packees+=3;
              }
              else // ... nombre de pixels tenant sur un word
              {
                if(Read_byte(file, &color)!=1)
                {
                    File_error=2;
                    break;
        }
                Read_word_be(file, &len);
                for (index=0; index<len; index++)
                  Set_pixel(context, (Compteur_de_pixels+index) % context->Width,
                                      (Compteur_de_pixels+index) / context->Width,
                                      color);
                Compteur_de_pixels+=len;
                Compteur_de_donnees_packees+=4;
              }
            }
          }
        }
      }
      /*Close_lecture();*/
    }
    else // Lecture header impossible: Error ne modifiant pas l'image
      File_error=1;

    fclose(file);
  }
  else // Ouv. fichier impossible: Error ne modifiant pas l'image
    File_error=1;
}


// -- Sauver un fichier au format PKM ---------------------------------------

  // Trouver quels sont les octets de reconnaissance
  void Find_recog(byte * recog1, byte * recog2)
  {
    dword Find_recon[256]; // Table d'utilisation de couleurs
    byte  best;   // Meilleure couleur pour recon (recon1 puis recon2)
    dword NBest;  // Nombre d'occurences de cette couleur
    word  index;


    // On commence par compter l'utilisation de chaque couleurs
    Count_used_colors(Find_recon);

    // Ensuite recog1 devient celle la moins utilisée de celles-ci
    *recog1=0;
    best=1;
    NBest=INT_MAX; // Une même couleur ne pourra jamais être utilisée 1M de fois.
    for (index=1;index<=255;index++)
      if (Find_recon[index]<NBest)
      {
        best=index;
        NBest=Find_recon[index];
      }
    *recog1=best;

    // Enfin recog2 devient la 2ème moins utilisée
    *recog2=0;
    best=0;
    NBest=INT_MAX;
    for (index=0;index<=255;index++)
      if ( (Find_recon[index]<NBest) && (index!=*recog1) )
      {
        best=index;
        NBest=Find_recon[index];
      }
    *recog2=best;
  }


void Save_PKM(T_IO_Context * context)
{
  FILE *file;
  T_PKM_Header header;
  dword Compteur_de_pixels;
  dword image_size;
  word  repetitions;
  byte  last_color;
  byte  pixel_value;
  size_t comment_size;



  // Construction du header
  memcpy(header.Ident,"PKM",3);
  header.Method=0;
  Find_recog(&header.Recog1,&header.Recog2);
  header.Width=context->Width;
  header.Height=context->Height;
  memcpy(header.Palette,context->Palette,sizeof(T_Palette));
  Palette_256_to_64(header.Palette);

  // Calcul de la taille du Post-header
  header.Jump=9; // 6 pour les dimensions de l'ecran + 3 pour la back-color
  comment_size=strlen(context->Comment);
  if (comment_size > 255) comment_size = 255;
  if (comment_size)
    header.Jump+=(word)comment_size+2;


  File_error=0;

  // Ouverture du fichier
  if ((file=Open_file_write(context)))
  {
    setvbuf(file, NULL, _IOFBF, 64*1024);

    // Ecriture du header
    if (Write_bytes(file,&header.Ident,3) &&
        Write_byte(file,header.Method) &&
        Write_byte(file,header.Recog1) &&
        Write_byte(file,header.Recog2) &&
        Write_word_le(file,header.Width) &&
        Write_word_le(file,header.Height) &&
        Write_bytes(file,&header.Palette,sizeof(T_Palette)) &&
        Write_word_le(file,header.Jump))
    {

      // Ecriture du commentaire
      // (Compteur_de_pixels est utilisé ici comme simple index de comptage)
      if (comment_size > 0)
      {
        Write_one_byte(file,0);
        Write_one_byte(file,(byte)comment_size);
        for (Compteur_de_pixels=0; Compteur_de_pixels<comment_size; Compteur_de_pixels++)
          Write_one_byte(file,context->Comment[Compteur_de_pixels]);
      }
      // Ecriture des dimensions de l'écran
      Write_one_byte(file,1);
      Write_one_byte(file,4);
      Write_one_byte(file,Screen_width&0xFF);
      Write_one_byte(file,Screen_width>>8);
      Write_one_byte(file,Screen_height&0xFF);
      Write_one_byte(file,Screen_height>>8);
      // Ecriture de la back-color
      Write_one_byte(file,2);
      Write_one_byte(file,1);
      Write_one_byte(file,Back_color);

      // Routine de compression PKM de l'image
      image_size=(dword)(context->Width*context->Height);
      Compteur_de_pixels=0;
      pixel_value=Get_pixel(context, 0,0);

      while ( (Compteur_de_pixels<image_size) && (!File_error) )
      {
        Compteur_de_pixels++;
        repetitions=1;
        last_color=pixel_value;
        if(Compteur_de_pixels<image_size)
        {
          pixel_value=Get_pixel(context, Compteur_de_pixels % context->Width,Compteur_de_pixels / context->Width);
        }
        while ( (pixel_value==last_color)
             && (Compteur_de_pixels<image_size)
             && (repetitions<65535) )
        {
          Compteur_de_pixels++;
          repetitions++;
          if(Compteur_de_pixels>=image_size) break;
          pixel_value=Get_pixel(context, Compteur_de_pixels % context->Width,Compteur_de_pixels / context->Width);
        }

        if ( (last_color!=header.Recog1) && (last_color!=header.Recog2) )
        {
          if (repetitions==1)
            Write_one_byte(file,last_color);
          else
          if (repetitions==2)
          {
            Write_one_byte(file,last_color);
            Write_one_byte(file,last_color);
          }
          else
          if ( (repetitions>2) && (repetitions<256) )
          { // RECON1/couleur/nombre
            Write_one_byte(file,header.Recog1);
            Write_one_byte(file,last_color);
            Write_one_byte(file,repetitions&0xFF);
          }
          else
          if (repetitions>=256)
          { // RECON2/couleur/hi(nombre)/lo(nombre)
            Write_one_byte(file,header.Recog2);
            Write_one_byte(file,last_color);
            Write_one_byte(file,repetitions>>8);
            Write_one_byte(file,repetitions&0xFF);
          }
        }
        else
        {
          if (repetitions<256)
          {
            Write_one_byte(file,header.Recog1);
            Write_one_byte(file,last_color);
            Write_one_byte(file,repetitions&0xFF);
          }
          else
          {
            Write_one_byte(file,header.Recog2);
            Write_one_byte(file,last_color);
            Write_one_byte(file,repetitions>>8);
            Write_one_byte(file,repetitions&0xFF);
          }
        }
      }
    }
    else
      File_error=1;
    fclose(file);
  }
  else
  {
    File_error=1;
    fclose(file);
  }
  //   S'il y a eu une erreur de sauvegarde, on ne va tout de même pas laisser
  // ce fichier pourri traîner... Ca fait pas propre.
  if (File_error)
    Remove_file(context);
}


//////////////////////////////////// CEL ////////////////////////////////////
typedef struct
{
  word Width;              // width de l'image
  word Height;             // height de l'image
} T_CEL_Header1;

typedef struct
{
  byte Signature[4];           // Signature du format
  byte Kind;               // Type de fichier ($10=PALette $20=BitMaP)
  byte Nb_bits;             // Nombre de bits
  word Filler1;            // ???
  word Width;            // width de l'image
  word Height;            // height de l'image
  word X_offset;         // Offset en X de l'image
  word Y_offset;         // Offset en Y de l'image
  byte Filler2[16];        // ???
} T_CEL_Header2;

// -- Tester si un fichier est au format CEL --------------------------------

void Test_CEL(T_IO_Context * context, FILE * file)
{
  int  size;
  T_CEL_Header1 header1;
  T_CEL_Header2 header2;
  int file_size;

  (void)context;
  File_error=0;

  file_size = File_length_file(file);
  if (Read_word_le(file,&header1.Width) &&
      Read_word_le(file,&header1.Height) )
  {
      //   Vu que ce header n'a pas de signature, il va falloir tester la
      // cohérence de la dimension de l'image avec celle du fichier.

      size=file_size-4;
      if ( (!size) || ( (((header1.Width+1)>>1)*header1.Height)!=size ) )
      {
        // Tentative de reconnaissance de la signature des nouveaux fichiers

        fseek(file,0,SEEK_SET);
        if (Read_bytes(file,&header2.Signature,4) &&
            !memcmp(header2.Signature,"KiSS",4) &&
            Read_byte(file,&header2.Kind) &&
            (header2.Kind==0x20) &&
            Read_byte(file,&header2.Nb_bits) &&
            Read_word_le(file,&header2.Filler1) &&
            Read_word_le(file,&header2.Width) &&
            Read_word_le(file,&header2.Height) &&
            Read_word_le(file,&header2.X_offset) &&
            Read_word_le(file,&header2.Y_offset) &&
            Read_bytes(file,&header2.Filler2,16))
        {
          // ok
        }
        else
          File_error=1;
      }
      else
        File_error=1;
  }
  else
  {
    File_error=1;
  }
}


// -- Lire un fichier au format CEL -----------------------------------------

void Load_CEL(T_IO_Context * context)
{
  FILE *file;
  T_CEL_Header1 header1;
  T_CEL_Header2 header2;
  short x_pos;
  short y_pos;
  byte  last_byte=0;
  long  file_size;
  const long int header_size = 4;

  File_error=0;
  if ((file=Open_file_read(context)))
  {
    if (Read_word_le(file,&(header1.Width))
    &&  Read_word_le(file,&(header1.Height)))
    {
      file_size=File_length_file(file);
      if ( (file_size>header_size)
        && ( (((header1.Width+1)>>1)*header1.Height)==(file_size-header_size) ) )
      {
        // Chargement d'un fichier CEL sans signature (vieux fichiers)
        context->Width=header1.Width;
        context->Height=header1.Height;
        Original_screen_X=context->Width;
        Original_screen_Y=context->Height;
        Pre_load(context, context->Width,context->Height,file_size,FORMAT_CEL,PIXEL_SIMPLE,0);
        if (File_error==0)
        {
          // Chargement de l'image
          /*Init_lecture();*/
          for (y_pos=0;((y_pos<context->Height) && (!File_error));y_pos++)
            for (x_pos=0;((x_pos<context->Width) && (!File_error));x_pos++)
              if ((x_pos & 1)==0)
              {
                if(Read_byte(file,&last_byte)!=1) File_error = 2;
                Set_pixel(context, x_pos,y_pos,(last_byte >> 4));
              }
              else
                Set_pixel(context, x_pos,y_pos,(last_byte & 15));
          /*Close_lecture();*/
        }
      }
      else
      {
        // On réessaye avec le nouveau format

        fseek(file,0,SEEK_SET);
        if (Read_bytes(file,header2.Signature,4)
        && Read_byte(file,&(header2.Kind))
        && Read_byte(file,&(header2.Nb_bits))
        && Read_word_le(file,&(header2.Filler1))
        && Read_word_le(file,&(header2.Width))
        && Read_word_le(file,&(header2.Height))
        && Read_word_le(file,&(header2.X_offset))
        && Read_word_le(file,&(header2.Y_offset))
        && Read_bytes(file,header2.Filler2,16)
        )
        {
          // Chargement d'un fichier CEL avec signature (nouveaux fichiers)

          context->Width=header2.Width+header2.X_offset;
          context->Height=header2.Height+header2.Y_offset;
          Original_screen_X=context->Width;
          Original_screen_Y=context->Height;
          Pre_load(context, context->Width,context->Height,file_size,FORMAT_CEL,PIXEL_SIMPLE,0);
          if (File_error==0)
          {
            // Chargement de l'image
            /*Init_lecture();*/

            if (!File_error)
            {
              // Effacement du décalage
              for (y_pos=0;y_pos<header2.Y_offset;y_pos++)
                for (x_pos=0;x_pos<context->Width;x_pos++)
                  Set_pixel(context, x_pos,y_pos,0);
              for (y_pos=header2.Y_offset;y_pos<context->Height;y_pos++)
                for (x_pos=0;x_pos<header2.X_offset;x_pos++)
                  Set_pixel(context, x_pos,y_pos,0);

              switch(header2.Nb_bits)
              {
                case 4:
                  for (y_pos=0;((y_pos<header2.Height) && (!File_error));y_pos++)
                    for (x_pos=0;((x_pos<header2.Width) && (!File_error));x_pos++)
                      if ((x_pos & 1)==0)
                      {
                        if(Read_byte(file,&last_byte)!=1) File_error=2;
                        Set_pixel(context, x_pos+header2.X_offset,y_pos+header2.Y_offset,(last_byte >> 4));
                      }
                      else
                        Set_pixel(context, x_pos+header2.X_offset,y_pos+header2.Y_offset,(last_byte & 15));
                  break;

                case 8:
                  for (y_pos=0;((y_pos<header2.Height) && (!File_error));y_pos++)
                    for (x_pos=0;((x_pos<header2.Width) && (!File_error));x_pos++)
                    {
                      byte byte_read;
                      if(Read_byte(file,&byte_read)!=1) File_error = 2;
                      Set_pixel(context, x_pos+header2.X_offset,y_pos+header2.Y_offset,byte_read);
                      }
                  break;

                default:
                  File_error=1;
              }
            }
            /*Close_lecture();*/
          }
        }
        else
          File_error=1;
      }
      fclose(file);
    }
    else
      File_error=1;
  }
  else
    File_error=1;
}


// -- Ecrire un fichier au format CEL ---------------------------------------

void Save_CEL(T_IO_Context * context)
{
  FILE *file;
  T_CEL_Header1 header1;
  T_CEL_Header2 header2;
  short x_pos;
  short y_pos;
  byte  last_byte=0;
  dword color_usage[256]; // Table d'utilisation de couleurs


  // On commence par compter l'utilisation de chaque couleurs
  Count_used_colors(color_usage);

  File_error=0;
  if ((file=Open_file_write(context)))
  {
    setvbuf(file, NULL, _IOFBF, 64*1024);

    // On regarde si des couleurs >16 sont utilisées dans l'image
    for (x_pos=16;((x_pos<256) && (!color_usage[x_pos]));x_pos++);

    if (x_pos==256)
    {
      // Cas d'une image 16 couleurs (écriture à l'ancien format)

      header1.Width =context->Width;
      header1.Height=context->Height;

      if (Write_word_le(file,header1.Width)
      && Write_word_le(file,header1.Height)
      )
      {
        // Sauvegarde de l'image
        for (y_pos=0;((y_pos<context->Height) && (!File_error));y_pos++)
        {
          for (x_pos=0;((x_pos<context->Width) && (!File_error));x_pos++)
            if ((x_pos & 1)==0)
              last_byte=(Get_pixel(context, x_pos,y_pos) << 4);
            else
            {
              last_byte=last_byte | (Get_pixel(context, x_pos,y_pos) & 15);
              Write_one_byte(file,last_byte);
            }

          if ((x_pos & 1)==1)
            Write_one_byte(file,last_byte);
        }
      }
      else
        File_error=1;
      fclose(file);
    }
    else
    {
      // Cas d'une image 256 couleurs (écriture au nouveau format)

      // Recherche du décalage
      for (y_pos=0;y_pos<context->Height;y_pos++)
      {
        for (x_pos=0;x_pos<context->Width;x_pos++)
          if (Get_pixel(context, x_pos,y_pos)!=0)
            break;
        if (Get_pixel(context, x_pos,y_pos)!=0)
          break;
      }
      header2.Y_offset=y_pos;
      for (x_pos=0;x_pos<context->Width;x_pos++)
      {
        for (y_pos=0;y_pos<context->Height;y_pos++)
          if (Get_pixel(context, x_pos,y_pos)!=0)
            break;
        if (Get_pixel(context, x_pos,y_pos)!=0)
          break;
      }
      header2.X_offset=x_pos;

      memcpy(header2.Signature,"KiSS",4); // Initialisation de la signature
      header2.Kind=0x20;              // Initialisation du type (BitMaP)
      header2.Nb_bits=8;               // Initialisation du nombre de bits
      header2.Filler1=0;              // Initialisation du filler 1 (?)
      header2.Width=context->Width-header2.X_offset; // Initialisation de la largeur
      header2.Height=context->Height-header2.Y_offset; // Initialisation de la hauteur
      for (x_pos=0;x_pos<16;x_pos++)  // Initialisation du filler 2 (?)
        header2.Filler2[x_pos]=0;

      if (Write_bytes(file,header2.Signature,4)
      && Write_byte(file,header2.Kind)
      && Write_byte(file,header2.Nb_bits)
      && Write_word_le(file,header2.Filler1)
      && Write_word_le(file,header2.Width)
      && Write_word_le(file,header2.Height)
      && Write_word_le(file,header2.X_offset)
      && Write_word_le(file,header2.Y_offset)
      && Write_bytes(file,header2.Filler2,14)
      )
      {
        // Sauvegarde de l'image
        for (y_pos=0;((y_pos<header2.Height) && (!File_error));y_pos++)
          for (x_pos=0;((x_pos<header2.Width) && (!File_error));x_pos++)
            Write_one_byte(file,Get_pixel(context, x_pos+header2.X_offset,y_pos+header2.Y_offset));
      }
      else
        File_error=1;
      fclose(file);
    }

    if (File_error)
      Remove_file(context);
  }
  else
    File_error=1;
}


//////////////////////////////////// KCF ////////////////////////////////////
typedef struct
{
  struct
  {
    struct
    {
      byte Byte1;
      byte Byte2;
    } color[16];
  } Palette[10];
} T_KCF_Header;

// -- Tester si un fichier est au format KCF --------------------------------

void Test_KCF(T_IO_Context * context, FILE * file)
{
  T_KCF_Header header1;
  T_CEL_Header2 header2;
  int pal_index;
  int color_index;

  (void)context;
  File_error=0;
    if (File_length_file(file)==320)
    {
      for (pal_index=0;pal_index<10 && !File_error;pal_index++)
        for (color_index=0;color_index<16 && !File_error;color_index++)
          if (!Read_byte(file,&header1.Palette[pal_index].color[color_index].Byte1) ||
              !Read_byte(file,&header1.Palette[pal_index].color[color_index].Byte2))
            File_error=1;
      // On vérifie une propriété de la structure de palette:
      for (pal_index=0;pal_index<10;pal_index++)
        for (color_index=0;color_index<16;color_index++)
          if ((header1.Palette[pal_index].color[color_index].Byte2>>4)!=0)
            File_error=1;
    }
    else
    {
      if (Read_bytes(file,header2.Signature,4)
        && Read_byte(file,&(header2.Kind))
        && Read_byte(file,&(header2.Nb_bits))
        && Read_word_le(file,&(header2.Filler1))
        && Read_word_le(file,&(header2.Width))
        && Read_word_le(file,&(header2.Height))
        && Read_word_le(file,&(header2.X_offset))
        && Read_word_le(file,&(header2.Y_offset))
        && Read_bytes(file,header2.Filler2,14)
        )
      {
        if (memcmp(header2.Signature,"KiSS",4)==0)
        {
          if (header2.Kind!=0x10)
            File_error=1;
        }
        else
          File_error=1;
      }
      else
        File_error=1;
    }
}


// -- Lire un fichier au format KCF -----------------------------------------

void Load_KCF(T_IO_Context * context)
{
  FILE *file;
  T_KCF_Header header1;
  T_CEL_Header2 header2;
  byte bytes[3];
  int pal_index;
  int color_index;
  int index;
  long  file_size;


  File_error=0;
  if ((file=Open_file_read(context)))
  {
    file_size=File_length_file(file);
    if (file_size==320)
    {
      // Fichier KCF à l'ancien format
      for (pal_index=0;pal_index<10 && !File_error;pal_index++)
        for (color_index=0;color_index<16 && !File_error;color_index++)
          if (!Read_byte(file,&header1.Palette[pal_index].color[color_index].Byte1) ||
              !Read_byte(file,&header1.Palette[pal_index].color[color_index].Byte2))
            File_error=1;

      if (!File_error)
      {
        // Pre_load(context, ?); // Pas possible... pas d'image...

        if (Config.Clear_palette)
          memset(context->Palette,0,sizeof(T_Palette));

        // Chargement de la palette
        for (pal_index=0;pal_index<10;pal_index++)
          for (color_index=0;color_index<16;color_index++)
          {
            index=16+(pal_index*16)+color_index;
            context->Palette[index].R=((header1.Palette[pal_index].color[color_index].Byte1 >> 4) << 4);
            context->Palette[index].B=((header1.Palette[pal_index].color[color_index].Byte1 & 15) << 4);
            context->Palette[index].G=((header1.Palette[pal_index].color[color_index].Byte2 & 15) << 4);
          }

        for (index=0;index<16;index++)
        {
          context->Palette[index].R=context->Palette[index+16].R;
          context->Palette[index].G=context->Palette[index+16].G;
          context->Palette[index].B=context->Palette[index+16].B;
        }

      }
      else
        File_error=1;
    }
    else
    {
      // Fichier KCF au nouveau format

      if (Read_bytes(file,header2.Signature,4)
        && Read_byte(file,&(header2.Kind))
        && Read_byte(file,&(header2.Nb_bits))
        && Read_word_le(file,&(header2.Filler1))
        && Read_word_le(file,&(header2.Width))
        && Read_word_le(file,&(header2.Height))
        && Read_word_le(file,&(header2.X_offset))
        && Read_word_le(file,&(header2.Y_offset))
        && Read_bytes(file,header2.Filler2,14)
        )
      {
        // Pre_load(context, ?); // Pas possible... pas d'image...

        index=(header2.Nb_bits==12)?16:0;
        for (pal_index=0;pal_index<header2.Height;pal_index++)
        {
           // Pour chaque palette

           for (color_index=0;color_index<header2.Width;color_index++)
           {
             // Pour chaque couleur

             switch(header2.Nb_bits)
             {
               case 12: // RRRR BBBB | 0000 VVVV
                 Read_bytes(file,bytes,2);
                 context->Palette[index].R=(bytes[0] >> 4) << 4;
                 context->Palette[index].B=(bytes[0] & 15) << 4;
                 context->Palette[index].G=(bytes[1] & 15) << 4;
                 break;

               case 24: // RRRR RRRR | VVVV VVVV | BBBB BBBB
                 Read_bytes(file,bytes,3);
                 context->Palette[index].R=bytes[0];
                 context->Palette[index].G=bytes[1];
                 context->Palette[index].B=bytes[2];
             }

             index++;
           }
        }

        if (header2.Nb_bits==12)
          for (index=0;index<16;index++)
          {
            context->Palette[index].R=context->Palette[index+16].R;
            context->Palette[index].G=context->Palette[index+16].G;
            context->Palette[index].B=context->Palette[index+16].B;
          }

      }
      else
        File_error=1;
    }
    fclose(file);
  }
  else
    File_error=1;
}


// -- Ecrire un fichier au format KCF ---------------------------------------

void Save_KCF(T_IO_Context * context)
{
  FILE *file;
  T_KCF_Header header1;
  T_CEL_Header2 header2;
  byte bytes[3];
  int pal_index;
  int color_index;
  int index;
  dword color_usage[256]; // Table d'utilisation de couleurs

  // On commence par compter l'utilisation de chaque couleurs
  Count_used_colors(color_usage);

  File_error=0;
  if ((file=Open_file_write(context)))
  {
    setvbuf(file, NULL, _IOFBF, 64*1024);
    // Sauvegarde de la palette

    // On regarde si des couleurs >16 sont utilisées dans l'image
    for (index=16;((index<256) && (!color_usage[index]));index++);

    if (index==256)
    {
      // Cas d'une image 16 couleurs (écriture à l'ancien format)

      for (pal_index=0;pal_index<10;pal_index++)
        for (color_index=0;color_index<16;color_index++)
        {
          index=16+(pal_index*16)+color_index;
          header1.Palette[pal_index].color[color_index].Byte1=((context->Palette[index].R>>4)<<4) | (context->Palette[index].B>>4);
          header1.Palette[pal_index].color[color_index].Byte2=context->Palette[index].G>>4;
        }

      // Write all
      for (pal_index=0;pal_index<10 && !File_error;pal_index++)
        for (color_index=0;color_index<16 && !File_error;color_index++)
          if (!Write_byte(file,header1.Palette[pal_index].color[color_index].Byte1) ||
              !Write_byte(file,header1.Palette[pal_index].color[color_index].Byte2))
            File_error=1;
    }
    else
    {
      // Cas d'une image 256 couleurs (écriture au nouveau format)

      memcpy(header2.Signature,"KiSS",4); // Initialisation de la signature
      header2.Kind=0x10;              // Initialisation du type (PALette)
      header2.Nb_bits=24;              // Initialisation du nombre de bits
      header2.Filler1=0;              // Initialisation du filler 1 (?)
      header2.Width=256;            // Initialisation du nombre de couleurs
      header2.Height=1;              // Initialisation du nombre de palettes
      header2.X_offset=0;           // Initialisation du décalage X
      header2.Y_offset=0;           // Initialisation du décalage Y
      for (index=0;index<16;index++) // Initialisation du filler 2 (?)
        header2.Filler2[index]=0;

      if (!Write_bytes(file,header2.Signature,4)
      || !Write_byte(file,header2.Kind)
      || !Write_byte(file,header2.Nb_bits)
      || !Write_word_le(file,header2.Filler1)
      || !Write_word_le(file,header2.Width)
      || !Write_word_le(file,header2.Height)
      || !Write_word_le(file,header2.X_offset)
      || !Write_word_le(file,header2.Y_offset)
      || !Write_bytes(file,header2.Filler2,14)
      )
        File_error=1;

      for (index=0;(index<256) && (!File_error);index++)
      {
        bytes[0]=context->Palette[index].R;
        bytes[1]=context->Palette[index].G;
        bytes[2]=context->Palette[index].B;
        if (! Write_bytes(file,bytes,3))
          File_error=1;
      }
    }

    fclose(file);

    if (File_error)
      Remove_file(context);
  }
  else
    File_error=1;
}


/**
 * @defgroup atarist Atari ST picture formats
 * @ingroup loadsaveformats
 *
 * Support for Atari ST picture formats. The Atari ST has
 * 3 video modes :
 * - low res : 320x200 16 colors
 * - med res : 640x200 4 colors
 * - high res : 640x400 monochrome
 *
 * Supported formats :
 * - PI1 : Degas
 * - PC1 : Degas elite compressed
 * - NEO : Neochrome
 *
 * @{
 */
//////////////////////////////////// PI1 ////////////////////////////////////

//// DECODAGE d'une partie d'IMAGE ////

void PI1_8b_to_16p(const byte * src,byte * dest)
{
  int  i;           // index du pixel à calculer
  word byte_mask;      // Masque de decodage
  word w0,w1,w2,w3; // Les 4 words bien ordonnés de la source

  byte_mask=0x8000;
  w0=(((word)src[0])<<8) | src[1];
  w1=(((word)src[2])<<8) | src[3];
  w2=(((word)src[4])<<8) | src[5];
  w3=(((word)src[6])<<8) | src[7];
  for (i=0;i<16;i++)
  {
    // Pour décoder le pixel n°i, il faut traiter les 4 words sur leur bit
    // correspondant à celui du masque

    dest[i]=((w0 & byte_mask)?0x01:0x00) |
           ((w1 & byte_mask)?0x02:0x00) |
           ((w2 & byte_mask)?0x04:0x00) |
           ((w3 & byte_mask)?0x08:0x00);
    byte_mask>>=1;
  }
}

void PI2_8b_to_16p(const byte * src,byte * dest)
{
  int  i;           // index du pixel à calculer
  word mask;      // Masque de decodage
  word w0,w1;

  w0=(((word)src[0])<<8) | src[1];
  w1=(((word)src[2])<<8) | src[3];
  mask=0x8000;
  for (i = 0; i < 16; i++)
  {
    dest[i] = ((w0 & mask) ? 0x01 : 0) | ((w1 & mask) ? 0x02 : 0);
    mask >>= 1;
  }
}

//// CODAGE d'une partie d'IMAGE ////

void PI1_16p_to_8b(byte * src,byte * dest)
{
  int  i;           // index du pixel à calculer
  word byte_mask;      // Masque de codage
  word w0,w1,w2,w3; // Les 4 words bien ordonnés de la destination

  byte_mask=0x8000;
  w0=w1=w2=w3=0;
  for (i=0;i<16;i++)
  {
    // Pour coder le pixel n°i, il faut modifier les 4 words sur leur bit
    // correspondant à celui du masque

    w0|=(src[i] & 0x01)?byte_mask:0x00;
    w1|=(src[i] & 0x02)?byte_mask:0x00;
    w2|=(src[i] & 0x04)?byte_mask:0x00;
    w3|=(src[i] & 0x08)?byte_mask:0x00;
    byte_mask>>=1;
  }
  dest[0]=w0 >> 8;
  dest[1]=w0 & 0x00FF;
  dest[2]=w1 >> 8;
  dest[3]=w1 & 0x00FF;
  dest[4]=w2 >> 8;
  dest[5]=w2 & 0x00FF;
  dest[6]=w3 >> 8;
  dest[7]=w3 & 0x00FF;
}

//// DECODAGE de la PALETTE ////

static void PI1_decode_palette(const byte * src, T_Components * palette)
{
  int i;  // Numéro de la couleur traitée
  word w; // Word contenant le code

  // Schéma d'un word =
  //
  //    Low        High
  // VVVV RRRR | 0000 BBBB
  // 0321 0321 |      0321

  for (i=0;i<16;i++)
  {
    w = (word)src[0] << 8 | (word)src[1];
    src += 2;

    palette[i].R = (((w & 0x0700)>>7) | ((w & 0x0800) >> 11)) * 0x11 ;
    palette[i].G = (((w & 0x0070)>>3) | ((w & 0x0080) >> 7)) * 0x11 ;
    palette[i].B = (((w & 0x0007)<<1) | ((w & 0x0008) >> 3)) * 0x11 ;
  }
}

//// CODAGE de la PALETTE ////

void PI1_code_palette(const T_Components * palette, byte * dest)
{
  int i;  // Numéro de la couleur traitée
  word w; // Word contenant le code

  // Schéma d'un word =
  //
  // Low        High
  // VVVV RRRR | 0000 BBBB
  // 0321 0321 |      0321

  for (i=0;i<16;i++)
  {
    w  = ((word)(palette[i].R & 0xe0) << 3) | ((word)(palette[i].R & 0x10) << 7);
    w |= ((word)(palette[i].G & 0xe0) >> 1) | ((word)(palette[i].G & 0x10) << 3);
    w |= ((word)(palette[i].B & 0xe0) >> 5) | ((word)(palette[i].B & 0x10) >> 1);

    *dest++ = (w >> 8);
    *dest++ = (w & 0xff);
  }
}

/// Load color cycling data from a PI1 or PC1 image (Degas Elite format)
static void PI1_load_ranges(T_IO_Context * context, const byte * buffer, int size)
{
  int range;

  if (buffer==NULL || size<32)
    return;

  for (range=0; range < 4; range ++)
  {
    word min_col, max_col, direction, delay;

    min_col   = (buffer[size - 32 + range*2 +  0] << 8) | buffer[size - 32 + range*2 +  1];
    max_col   = (buffer[size - 32 + range*2 +  8] << 8) | buffer[size - 32 + range*2 +  9];
    direction = (buffer[size - 32 + range*2 + 16] << 8) | buffer[size - 32 + range*2 + 17];
    delay     = (buffer[size - 32 + range*2 + 24] << 8) | buffer[size - 32 + range*2 + 25];

    if (max_col < min_col)
      SWAP_WORDS(min_col,max_col)

    GFX2_Log(GFX2_DEBUG, "Degas Color cycling : [#%d:#%d] direction=%d delay=%d\n", min_col, max_col, direction, delay);
    // Sanity checks
    if (min_col < 256 && max_col < 256 && direction < 3 && (direction == 1 || delay < 128))
    {
      int speed = 1;
      if (delay < 128)
        speed = 210/(128-delay);
      // Grafx2's slider has a limit of COLOR_CYCLING_SPEED_MAX
      if (speed > COLOR_CYCLING_SPEED_MAX)
        speed = COLOR_CYCLING_SPEED_MAX;
      context->Cycle_range[context->Color_cycles].Start=min_col;
      context->Cycle_range[context->Color_cycles].End=max_col;
      context->Cycle_range[context->Color_cycles].Inverse= (direction==0);
      context->Cycle_range[context->Color_cycles].Speed=direction == 1 ? 0 : speed;
      context->Color_cycles++;
    }
  }
}

/// Saves color ranges from a PI1 or PC1 image (Degas Elite format)
void PI1_save_ranges(T_IO_Context * context, byte * buffer, int size)
{
  // empty by default
  memset(buffer+size - 32, 0, 32);
  if (context->Color_cycles)
  {
    int i; // index in context->Cycle_range[] : < context->Color_cycles
    int saved_range; // index in resulting buffer : < 4

    for (i=0, saved_range=0; i<context->Color_cycles && saved_range<4; i++)
    {
      if (context->Cycle_range[i].Start < 16 && context->Cycle_range[i].End < 16)
      {
        int speed;
        if (context->Cycle_range[i].Speed == 0)
          speed = 0;
        else if (context->Cycle_range[i].Speed == 1)
          // has to "round" manually to closest valid number for this format
          speed = 1;
        else
          speed = 128 - 210 / context->Cycle_range[i].Speed;

        buffer[size - 32 + saved_range*2 +  1] = context->Cycle_range[i].Start;
        buffer[size - 32 + saved_range*2 +  9] = context->Cycle_range[i].End;
        buffer[size - 32 + saved_range*2 + 17] = (context->Cycle_range[i].Speed == 0) ? 1 : (context->Cycle_range[i].Inverse ? 0 : 2);
        buffer[size - 32 + saved_range*2 + 25] = speed;

        saved_range ++;
      }
    }
  }
}

/// Test for Degas file format
void Test_PI1(T_IO_Context * context, FILE * file)
{
  unsigned long size;              // Taille du fichier
  word resolution;                 // Résolution de l'image

  (void)context;
  File_error=1;

  if (!Read_word_be(file,&resolution))
    return;

  size = File_length_file(file);
  if ((size==32034) || (size==32066)) // size check
  {
    if (resolution < 3)
      File_error=0;
  }
}


/// Load Degas file format
void Load_PI1(T_IO_Context * context)
{
  enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
  word resolution;
  word width, height;
  FILE *file;
  word x_pos,y_pos;
  byte buffer[160];
  byte * ptr;
  byte pixels[320];
  byte bpp;

  File_error = 1;
  file = Open_file_read(context);
  if (file == NULL)
    return;

  if (!Read_word_be(file, &resolution))
    return;
  GFX2_Log(GFX2_DEBUG, "Degas UnCompressed. Resolution = %04x\n", resolution);
  // Read palette
  if (!Read_bytes(file, buffer, 32))
  {
    fclose(file);
    return;
  }
  if (Config.Clear_palette)
    memset(context->Palette,0,sizeof(T_Palette));
  PI1_decode_palette(buffer, context->Palette);

  switch (resolution)
  {
    case 0:  // Low Res
      width = 320;
      height = 200;
      bpp = 4;
      break;
    case 1:  // Med Res
      width = 640;
      height = 200;
      bpp = 2;
      ratio = PIXEL_TALL;
      break;
    case 2:  // High Res
      width = 640;
      height = 400;
      bpp = 1;
      break;
    default:
      fclose(file);
      return;
  }
  Pre_load(context, width, height, File_length_file(file),FORMAT_PI1,ratio, bpp);

  for (y_pos=0;y_pos<height;y_pos++)
  {
    if (!Read_bytes(file, buffer, (resolution == 2) ? 80 : 160))
    {
      fclose(file);
      return;
    }
    ptr = buffer;
    for (x_pos=0; x_pos < width;)
    {
      int i;
      switch (resolution)
      {
        case 0:
          PI1_8b_to_16p(ptr, pixels);
          ptr += 8;
          break;
        case 1:
          PI2_8b_to_16p(ptr, pixels);
          ptr += 4;
          break;
        case 2:
          for (i = 0; i < 8; i++)
            pixels[i] = (ptr[0] & (0x80 >> i)) ? 1 : 0;
          for (; i < 16; i++)
            pixels[i] = (ptr[1] & (0x80 >> (i - 8))) ? 1 : 0;
          ptr += 2;
      }
      for (i = 0; i < 16; i++)
        Set_pixel(context, x_pos++, y_pos, pixels[i]);
    }
  }
  // load color cycling information
  if (Read_bytes(file, buffer, 32))
  {
    PI1_load_ranges(context, buffer, 32);
  }
  fclose(file);
  File_error = 0;
}


// -- Sauver un fichier au format PI1 ---------------------------------------
void Save_PI1(T_IO_Context * context)
{
  FILE *file;
  short x_pos,y_pos;
  byte * buffer;
  byte * ptr;
  byte pixels[320];

  File_error=0;
  // Ouverture du fichier
  if ((file=Open_file_write(context)))
  {
    setvbuf(file, NULL, _IOFBF, 64*1024);

    // allocation d'un buffer mémoire
    buffer=(byte *)malloc(32034);
    // Codage de la résolution
    buffer[0]=0x00;
    buffer[1]=0x00;
    // Codage de la palette
    PI1_code_palette(context->Palette, buffer+2);
    // Codage de l'image
    ptr=buffer+34;
    for (y_pos=0;y_pos<200;y_pos++)
    {
      // Codage de la ligne
      memset(pixels,0,320);
      if (y_pos<context->Height)
      {
        for (x_pos=0;(x_pos<320) && (x_pos<context->Width);x_pos++)
          pixels[x_pos]=Get_pixel(context, x_pos,y_pos);
      }

      for (x_pos=0;x_pos<(320>>4);x_pos++)
      {
        PI1_16p_to_8b(pixels+(x_pos<<4),ptr);
        ptr+=8;
      }
    }

    if (Write_bytes(file,buffer,32034))
    {
      if (context->Color_cycles)
      {
        PI1_save_ranges(context, buffer, 32);
        if (!Write_bytes(file,buffer,32))
          File_error=1;
      }
      fclose(file);
    }
    else // Error d'écriture (disque plein ou protégé)
    {
      fclose(file);
      Remove_file(context);
      File_error=1;
    }
    // Libération du buffer mémoire
    free(buffer);
    buffer = NULL;
  }
  else
  {
    fclose(file);
    Remove_file(context);
    File_error=1;
  }
}


//////////////////////////////////// PC1 ////////////////////////////////////

/// uncompress degas elite compressed stream (PACKBITS)
/// @return 1 for success
/// @return 0 for failure
static int PC1_uncompress_packbits_file(FILE *f, byte * dest)
{
  int id = 0;
  unsigned count;
  byte code, value;

  while (id < 32000)
  {
    if (!Read_byte(f, &code))
      return 0;

    if (code & 0x80)
    {
      /// Code is negative :
      /// Repeat (1-code) times next byte
      count = 257 - code;
      if (id + count > 32000)
        return 0;
      if (!Read_byte(f, &value))
        return 0;
      while (count-- > 0)
        dest[id++] = value;
    }
    else
    {
      /// Code is positive :
      /// Copy (code+1) bytes
      count = code + 1;
      if (id + count > 32000)
        return 0;
      if (!Read_bytes(f, dest + id, count))
        return 0;
      id += count;
    }
  }
  return 1;
}

//// COMPRESSION d'un buffer selon la méthode PACKBITS ////

static void PC1_compress_packbits(byte * src,byte * dest,int source_size,int * dest_size)
{

  *dest_size = 0;
  while (source_size > 0)
  {
    int is = 0; // index dans la source
    int id = 0; // index dans la destination
    int ir; // index de   la répétition
    int n;  // Taille des séquences
    int repet; // "Il y a répétition"

    while(is<40)
    {
      // On recherche le 1er endroit où il y a répétition d'au moins 3 valeurs
      // identiques

      repet=0;
      for (ir=is;ir<40-2;ir++)
      {
        if ((src[ir]==src[ir+1]) && (src[ir+1]==src[ir+2]))
        {
          repet=1;
          break;
        }
      }

      // On code la partie sans répétitions
      if (!repet || ir!=is)
      {
        n=(ir-is)+1;
        dest[id++]=n-1;
        for (;n>0;n--)
          dest[id++]=src[is++];
      }

      // On code la partie sans répétitions
      if (repet)
      {
        // On compte la quantité de fois qu'il faut répéter la valeur
        for (ir+=3;ir<40;ir++)
        {
          if (src[ir]!=src[is])
            break;
        }
        n=(ir-is);
        dest[id++]=257-n;
        dest[id++]=src[is];
        is=ir;
      }
    }
    // On renseigne la taille du buffer compressé
    *dest_size+=id;
    // Move for next 40-byte block
    src += 40;
    dest += id;
    source_size -= 40;
  }
}

//// DECODAGE d'une partie d'IMAGE ////

// Transformation de 4 plans de bits en 1 ligne de pixels

static void PC1_4bp_to_1line(byte * src0,byte * src1,byte * src2,byte * src3,byte * dest)
{
  int  i,j;         // Compteurs
  int  ip;          // index du pixel à calculer
  byte byte_mask;      // Masque de decodage
  byte b0,b1,b2,b3; // Les 4 octets des plans bits sources

  ip=0;
  // Pour chacun des 40 octets des plans de bits
  for (i=0;i<40;i++)
  {
    b0=src0[i];
    b1=src1[i];
    b2=src2[i];
    b3=src3[i];
    // Pour chacun des 8 bits des octets
    byte_mask=0x80;
    for (j=0;j<8;j++)
    {
      dest[ip++]=((b0 & byte_mask)?0x01:0x00) |
                ((b1 & byte_mask)?0x02:0x00) |
                ((b2 & byte_mask)?0x04:0x00) |
                ((b3 & byte_mask)?0x08:0x00);
      byte_mask>>=1;
    }
  }
}

//// CODAGE d'une partie d'IMAGE ////

// Transformation d'1 ligne de pixels en 4 plans de bits

static void PC1_1line_to_4bp(byte * src,byte * dst0,byte * dst1,byte * dst2,byte * dst3)
{
  int  i,j;         // Compteurs
  int  ip;          // index du pixel à calculer
  byte byte_mask;      // Masque de decodage
  byte b0,b1,b2,b3; // Les 4 octets des plans bits sources

  ip=0;
  // Pour chacun des 40 octets des plans de bits
  for (i=0;i<40;i++)
  {
    // Pour chacun des 8 bits des octets
    byte_mask=0x80;
    b0=b1=b2=b3=0;
    for (j=0;j<8;j++)
    {
      b0|=(src[ip] & 0x01)?byte_mask:0x00;
      b1|=(src[ip] & 0x02)?byte_mask:0x00;
      b2|=(src[ip] & 0x04)?byte_mask:0x00;
      b3|=(src[ip] & 0x08)?byte_mask:0x00;
      ip++;
      byte_mask>>=1;
    }
    dst0[i]=b0;
    dst1[i]=b1;
    dst2[i]=b2;
    dst3[i]=b3;
  }
}


/// Test for Degas Elite Compressed format
void Test_PC1(T_IO_Context * context, FILE * file)
{
  int  size;              // Taille du fichier
  word resolution;        // Résolution de l'image

  (void)context;
  File_error=1;

  size = File_length_file(file);
  if (!Read_word_be(file,&resolution))
    return;

  if ((size <= 32066) && (resolution & 0x8000))
  {
    if ((resolution & 0x7fff) < 3)
      File_error=0;
  }
}


/// Load Degas Elite compressed files
void Load_PC1(T_IO_Context * context)
{
  enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
  unsigned long size;
  word width, height;
  byte bpp;
  FILE *file;
  word x_pos,y_pos;
  byte buffer[32];
  byte * bufferdecomp;
  byte * ptr;
  byte pixels[640];
  word resolution;

  File_error = 1;
  file = Open_file_read(context);
  if (file == NULL)
    return;
  size = File_length_file(file);

  if (!Read_word_be(file, &resolution))
    return;
  GFX2_Log(GFX2_DEBUG, "Degas Elite Compressed. Resolution = %04x\n", resolution);
  // Read palette
  if (!Read_bytes(file, buffer, 32))
  {
    fclose(file);
    return;
  }
  if (Config.Clear_palette)
    memset(context->Palette,0,sizeof(T_Palette));
  PI1_decode_palette(buffer, context->Palette);

  switch (resolution)
  {
    case 0x8000:  // Low Res
      width = 320;
      height = 200;
      bpp = 4;
      break;
    case 0x8001:  // Med Res
      width = 640;
      height = 200;
      bpp = 2;
      ratio = PIXEL_TALL;
      break;
    case 0x8002:  // High Res
      width = 640;
      height = 400;
      bpp = 1;
      break;
    default:
      fclose(file);
      return;
  }

  bufferdecomp=(byte *)malloc(32000);
  if (bufferdecomp == NULL)
  {
    fclose(file);
    return;
  }

  // Initialisation de la preview
  Pre_load(context, width, height, size, FORMAT_PC1, ratio, bpp);

  //PC1_uncompress_packbits(buffercomp, bufferdecomp);
  if (!PC1_uncompress_packbits_file(file, bufferdecomp))
  {
    GFX2_Log(GFX2_INFO, "PC1_uncompress_packbits_file() failed\n");
    free(bufferdecomp);
    fclose(file);
    return;
  }

  // Décodage de l'image
  ptr=bufferdecomp;
  for (y_pos = 0; y_pos < height; y_pos++)
  {
    // Décodage de la scanline
    switch (resolution)
    {
      case 0x8000:  // Low Res
        PC1_4bp_to_1line(ptr,ptr+40,ptr+80,ptr+120,pixels);
        ptr+=160;
        break;
      case 0x8001:  // Med Res
        x_pos = 0;
        while (x_pos < width)
        {
          int i;
          for (i = 7; i >= 0; i--, x_pos++)
            pixels[x_pos] =  ((ptr[0] >> i) & 1)
                            | (((ptr[80] >> i) << 1) & 2);
          ptr++;
        }
        ptr += 80;
        break;
      case 0x8002:  // High Res
        x_pos = 0;
        while (x_pos < width)
        {
          int i;
          for (i = 7; i >= 0; i--)
            pixels[x_pos++] = (*ptr >> i) & 1;
          ptr++;
        }
    }
    for (x_pos=0;x_pos<width;x_pos++)
      Set_pixel(context, x_pos, y_pos, pixels[x_pos]);
  }
  // Try to load color cycling information
  GFX2_Log(GFX2_DEBUG, "remaining bytes = %ld\n", size - ftell(file));
  if (Read_bytes(file, buffer, 32))
  {
    PI1_load_ranges(context, buffer, 32);
  }
  free(bufferdecomp);
  fclose(file);
  File_error = 0;
}


// -- Sauver un fichier au format PC1 ---------------------------------------
void Save_PC1(T_IO_Context * context)
{
  FILE *file;
  int   size;
  short x_pos,y_pos;
  byte * buffercomp;
  byte * bufferdecomp;
  byte * ptr;
  byte pixels[320];

  File_error=0;
  // Ouverture du fichier
  if ((file=Open_file_write(context)))
  {
    setvbuf(file, NULL, _IOFBF, 64*1024);

    // Allocation des buffers mémoire
    bufferdecomp=(byte *)malloc(32000);
    buffercomp  =(byte *)malloc(64066);
    // Codage de la résolution
    buffercomp[0]=0x80;
    buffercomp[1]=0x00;
    // Codage de la palette
    PI1_code_palette(context->Palette, buffercomp+2);
    // Codage de l'image
    ptr=bufferdecomp;
    for (y_pos=0;y_pos<200;y_pos++)
    {
      // Codage de la ligne
      memset(pixels,0,320);
      if (y_pos<context->Height)
      {
        for (x_pos=0;(x_pos<320) && (x_pos<context->Width);x_pos++)
          pixels[x_pos]=Get_pixel(context, x_pos,y_pos);
      }

      // Encodage de la scanline
      PC1_1line_to_4bp(pixels,ptr,ptr+40,ptr+80,ptr+120);
      ptr+=160;
    }

    // Compression du buffer
    PC1_compress_packbits(bufferdecomp,buffercomp+34,32000,&size);
    size += 34;
    size += 32;
    PI1_save_ranges(context, buffercomp,size);

    if (Write_bytes(file,buffercomp,size))
    {
      fclose(file);
    }
    else // Error d'écriture (disque plein ou protégé)
    {
      fclose(file);
      Remove_file(context);
      File_error=1;
    }
    // Libération des buffers mémoire
    free(bufferdecomp);
    free(buffercomp);
    buffercomp = bufferdecomp = NULL;
  }
  else
  {
    fclose(file);
    Remove_file(context);
    File_error=1;
  }
}


//////////////////////////////////// NEO ////////////////////////////////////
/**
NeoChrome Format :
<pre>
1 word          flag word [always 0]
1 word          resolution [0 = low res, 1 = medium res, 2 = high res]
16 words        palette
12 bytes        filename [usually "        .   "]
1 word          color animation limits.  High bit (bit 15) set if color
                animation data is valid.  Low byte contains color animation
                limits (4 most significant bits are left/lower limit,
                4 least significant bits are right/upper limit).
1 word          color animation speed and direction.  High bit (bit 15) set
                if animation is on.  Low order byte is # vblanks per step.
                If negative, scroll is left (decreasing).  Number of vblanks
                between cycles is |x| - 1
1 word          # of color steps (as defined in previous word) to display
                picture before going to the next.  (For use in slide shows)
1 word          image X offset [unused, always 0]
1 word          image Y offset [unused, always 0]
1 word          image width [unused, always 320]
1 word          image height [unused, always 200]
33 words        reserved for future expansion
32000 bytes     pixel data
</pre>

Dali           *.SD0 (ST low resolution)
               *.SD1 (ST medium resolution)
               *.SD2 (ST high resolution)
        
Files do not seem to have any resolution or bit plane info stored in them. The file
extension seems to be the only way to determine the contents.
        
1 long         file id? [always 0]
16 words       palette
92 bytes       reserved? [usually 0]
*/
void Test_NEO(T_IO_Context * context, FILE * file)
{
  word flag;
  word resolution;   // Atari ST resolution

  (void)context;
  File_error=1;

  if (File_length_file(file) != 32128)
    return;

  if (!Read_word_be(file,&flag))
    return;
  // Flag word : always 0
  if (flag != 0)
    return;

  if (!Read_word_be(file,&resolution))
    return;
  // 0 = STlow, 1 = STmed, 2 = SThigh
  if (resolution==0 || resolution==1 || resolution==2)
    File_error = 0;
}


/// Load Neochrome file format
void Load_NEO(T_IO_Context * context)
{
  enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
  word flag;
  word resolution;   // Atari ST resolution
  word width, height;
  byte bpp;
  word color_cycling_range, color_cycling_delay;
  word display_time;
  word image_width, image_height, image_X_pos, image_Y_pos;
  FILE *file;
  word x_pos,y_pos;
  byte * ptr;
  byte buffer[160];
  byte pixels[16];

  File_error = 1;
  file = Open_file_read(context);
  if (file == NULL)
    return;

  if (!Read_word_be(file,&flag))
    goto error;
  // Flag word : always 0
  if (flag != 0)
    goto error;

  if (!Read_word_be(file,&resolution))
    goto error;

  switch (resolution)
  {
  case 0:
    width = 320;
    height = 200;
    bpp = 4;
    break;
  case 1:
    width = 640;
    height = 200;
    bpp = 2;
    ratio = PIXEL_TALL;
    break;
  case 2:
    width = 640;
    height = 400;
    bpp = 1;
    break;
  default:
    goto error;
  }

  Pre_load(context, width, height, File_length_file(file), FORMAT_NEO, ratio, bpp);

  if (!Read_bytes(file,buffer,32))
    goto error;

  // Initialisation de la palette
  if (Config.Clear_palette)
    memset(context->Palette, 0, sizeof(T_Palette));
  PI1_decode_palette(buffer, context->Palette);

  if (!Read_bytes(file, buffer, 12))
    goto error;
  buffer[12] = '\0';
  GFX2_Log(GFX2_DEBUG, "NEO resolution %u name=\"%s\"\n", resolution, (char *)buffer);
  if (!Read_word_be(file, &color_cycling_range)
     || !Read_word_be(file, &color_cycling_delay)
     || !Read_word_be(file, &display_time))
    goto error;
  GFX2_Log(GFX2_DEBUG, "  Color cycling : %04x %04x. Time to show %u\n", color_cycling_range, color_cycling_delay, display_time);
  if (color_cycling_range & 0x8000)
  {
    context->Cycle_range[context->Color_cycles].Start = (color_cycling_range & 0x00f0) >> 4;
    context->Cycle_range[context->Color_cycles].End = (color_cycling_range & 0x000f);
    if (color_cycling_delay & 0x8000)
    {
      // color cycling on
      color_cycling_delay &= 0xff;
      if (color_cycling_delay & 0x0080)
      {
        context->Cycle_range[context->Color_cycles].Inverse = 1;
        color_cycling_delay = 256 - color_cycling_delay;
      }
      else
        context->Cycle_range[context->Color_cycles].Inverse = 0;
      // Speed resolution is 0.2856Hz
      // NEO color_cycling_delay is in 50Hz VBL
      // Speed = (50/delay) / 0.2856 = 175 / delay
      if (color_cycling_delay != 0)
        context->Cycle_range[context->Color_cycles].Speed = 175 / color_cycling_delay;
      else
        context->Cycle_range[context->Color_cycles].Speed = COLOR_CYCLING_SPEED_MAX; // fastest
      if (context->Cycle_range[context->Color_cycles].Speed > COLOR_CYCLING_SPEED_MAX)
        context->Cycle_range[context->Color_cycles].Speed = COLOR_CYCLING_SPEED_MAX;
    }
    else
      context->Cycle_range[context->Color_cycles].Speed = 0;  // cycling off
    context->Color_cycles++;
  }

  if (!Read_word_be(file, &image_X_pos) || !Read_word_be(file, &image_Y_pos)
      || !Read_word_be(file, &image_width) || !Read_word_be(file, &image_height))
    goto error;
  GFX2_Log(GFX2_DEBUG, "  pos (%u,%u) size %ux%u\n", image_X_pos, image_Y_pos, image_width, image_height);
  if (!Read_bytes(file, buffer, 128-4-32-12-6-8))
    goto error;
  GFX2_LogHexDump(GFX2_DEBUG, "NEO ", buffer, 0, 128-4-32-12-6-8);

  // Chargement/décompression de l'image
  for (y_pos=0;y_pos<height;y_pos++)
  {
    if (!Read_bytes(file, buffer, (resolution==2) ? 80 : 160))
      goto error;
    
    ptr = buffer;
    for (x_pos = 0; x_pos < width; )
    {
      int i;
      switch (resolution)
      {
        case 0:
          PI1_8b_to_16p(ptr, pixels);
          ptr += 8;
          break;
        case 1:
          PI2_8b_to_16p(ptr, pixels);
          ptr += 4;
          break;
        case 2:
          for (i = 0; i < 8; i++)
            pixels[i] = (ptr[0] & (0x80 >> i)) ? 1 : 0;
          for (; i < 16; i++)
            pixels[i] = (ptr[1] & (0x80 >> (i - 8))) ? 1 : 0;
          ptr += 2;
          break;
        default:
          goto error;
      }
      for (i = 0; i < 16; i++)
        Set_pixel(context, x_pos++, y_pos, pixels[i]);
    }
  }
  File_error = 0; // everything was ok

error:
  fclose(file);
}

/// Save in NeoChrome format
void Save_NEO(T_IO_Context * context)
{
  word resolution = 0;
  FILE *file = NULL;
  short x_pos,y_pos;
  word color_cycling_range = 0, color_cycling_delay = 0;
  word display_time = 0;
  word image_width = 320, image_height = 200;
  byte buffer[32];
  byte pixels[320];
  char * ext;
  int i, j;

  File_error = 1;
  file = Open_file_write(context);
  if (file == NULL)
    return;

  // flags and resolution
  if (!Write_word_be(file, 0) || !Write_word_be(file, resolution))
    goto error;

  // palette
  PI1_code_palette(context->Palette, buffer);
  if (!Write_bytes(file, buffer, 16*2))
    goto error;

  // file name
  i = 0;
  j = 0;
  ext = strrchr(context->File_name, '.');
  while (j < 8 && ext != (context->File_name + i))
  {
    byte c = context->File_name[i++];
    if (c == 0)
      break;
    if (c >= 'a' && c <= 'z')
      c -= 32;
    if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_'))
      buffer[j++] = c;
  }
  while (j < 8)
    buffer[j++] = ' ';
  buffer[j++] = '.';
  if (ext != NULL)
  {
    i = 0;
    while (j < 12)
    {
      byte c = ext[i++];
      if (c == 0)
        break;
      if (c >= 'a' && c <= 'z')
        c -= 32;
      if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_'))
        buffer[j++] = c;
    }
  }
  while (j < 12)
    buffer[j++] = ' ';

  if (!Write_bytes(file, buffer, 12))
    goto error;

  // Save the 1st valid Color cycling range
  for (i = 0; i < context->Color_cycles; i++)
  {
    if (context->Cycle_range[i].Start < 16 && context->Cycle_range[i].End < 16)
    {
      color_cycling_range = 0x8000 | (context->Cycle_range[i].Start << 4) | context->Cycle_range[i].End;
      if (context->Cycle_range[i].Speed > 0)
      {
        color_cycling_delay = 175 / context->Cycle_range[i].Speed;
        if (color_cycling_delay > 0 && context->Cycle_range[i].Inverse)
          color_cycling_delay = 256 - color_cycling_delay;
        color_cycling_delay |= 0x8000;
      }
      break;
    }
  }
  if (!Write_word_be(file, color_cycling_range) || !Write_word_be(file, color_cycling_delay) || !Write_word_be(file, display_time))
    goto error;

  // Save image position and size
  if (!Write_word_be(file, 0) || !Write_word_be(file, 0)
      || !Write_word_be(file, image_width) || !Write_word_be(file, image_height))
    goto error;

  // Fill with 128 bytes header with 0's
  // a few files have the string "NEO!" at offset 124 (0x7C)
  for (i = ftell(file); i < 128; i++)
  {
    if (!Write_byte(file, 0))
      goto error;
  }

  // image coding
  for (y_pos=0;y_pos<200;y_pos++)
  {
    // Codage de la ligne
    memset(pixels, 0, 320);
    if (y_pos < context->Height)
    {
      for (x_pos = 0; (x_pos < 320) && (x_pos < context->Width); x_pos++)
        pixels[x_pos] = Get_pixel(context, x_pos, y_pos);
    }

    for (x_pos=0; x_pos < 320; x_pos += 16)
    {
      PI1_16p_to_8b(pixels + x_pos, buffer);
      if (!Write_bytes(file, buffer, 8))
        goto error;
    }
  }

  fclose(file);
  File_error = 0;
  return;

error:
  if (file != NULL)
    fclose(file);
  Remove_file(context);
}

/* @} */

//////////////////////////////////// C64 ////////////////////////////////////

/** C64 file formats
 */
enum c64_format
{
  F_invalid = -1,
  F_hires = 0,    ///< 320x200
  F_multi = 1,    ///< 160x200
  F_bitmap = 2,   ///< 320x200 monochrome
  F_fli = 3       ///< FLI (Flexible Line Interpretation)
};

/** C64 file formats names
 */
static const char *c64_format_names[] = {
  "Hires",
  "Multicolor",
  "Bitmap",
  "FLI"
};

static long C64_unpack_doodle(byte ** file_buffer, long file_size);

/**
 * Test for a C64 picture file
 *
 * Checks the file size and the load address
 *
 * References :
 * - http://unusedino.de/ec64/technical/formats/bitmap.html
 * - http://codebase64.org/doku.php?id=base:c64_grafix_files_specs_list_v0.03
 * - https://sourceforge.net/p/view64/code/HEAD/tree/trunk/libview64.c#l3737
 */
void Test_C64(T_IO_Context * context, FILE * file)
{
  unsigned long file_size;
  word load_addr;
  byte header[14];

  (void)context;
  File_error = 1;
  file_size = File_length_file(file);
  if (file_size < 16 || file_size > 48*1024)
    return; // File too short or too long, exit now
  // First test for formats without load address
  switch (file_size)
  {
    // case 1000: // screen or color
    case 8000: // raw bitmap
    case 9000: // bitmap + ScreenRAM
    case 10001: // multicolor
    case 17472: // FLI (BlackMail)
      File_error = 0;
      return;
    default: // then we don't know for now.
      if (!Read_word_le(file, &load_addr))
        return;
  }
  GFX2_Log(GFX2_DEBUG, "Test_C64() file_size=%ld LoadAddr=$%04X\n", file_size, load_addr);
  if (!Read_bytes(file, header, sizeof(header)))
    return;
  if (memcmp(header, "DRAZPAINT", 9) == 0)
  {
    GFX2_Log(GFX2_DEBUG, "Test_C64() header=%.13s RLE code = $%02X\n", header, header[13]);
    File_error = 0;
    return;
  }
  // check last 2 bytes
  if (fseek(file, -2, SEEK_END) < 0)
    return;
  if (!Read_bytes(file, header, 2))
    return;
  if (load_addr == 0x4000 && header[0] == 0xC2 && header[1] == 0x00) // Amica Paint EOF mark
  {
    File_error = 0;
    return;
  }
  switch (file_size)
  {
    // case 1002: // (screen or color) + loadaddr
    case 8002: // raw bitmap with loadaddr
    case 9002: // bitmap + ScreenRAM + loadaddr
      // $4000 => InterPaint Hi-Res (.iph)
    case 9003: // bitmap + ScreenRAM + loadaddr (+ border ?)
    case 9009: // bitmap + ScreenRAM + loadaddr
      // $2000 => Art Studio
    case 9218:
      // $5C00 => Doodle
    case 9332:
      // $3F8E => Paint Magic (.pmg) 'JEDI' at offset $0010 and $2010
    case 10003: // multicolor + loadaddr
      // $4000 => InterPaint multicolor
      // $6000 => Koala Painter
    case 10004:
      // $4000 => Face Paint (.fpt)
    case 10006:
      // $6000 => Run Paint (.rpm)
    case 10018:
      // $2000 => Advanced Art Studio
    case 10022:
      // $18DC => Micro Illustrator (uncompressed)
    case 10050:
      // $1800 => Picasso64
    case 10218:
      // $3C00 => Image System (.ism)
    case 10219:
      // $7800 => Saracen Paint (.sar)
      File_error = 0;
      break;
    case 10242:
      // $4000 => Artist 64 (.a64)
      // $A000 => Blazing paddles (.pi)
      // $5C00 => Rainbow Painter (.rp)
      if (load_addr != 0x4000 && load_addr != 0xa000 && load_addr != 0x5c00)
      {
        File_error = 1;
        return;
      }
      File_error = 0;
      break;
    case 17218:
    case 17409:
      // $3c00 => FLI-designer v1.1
      // ? $3ff0 => FLI designer 2 ?
    case 17410:
     // $3c00 => FLI MATIC
    case 17474: // FLI (BlackMail) + loadaddr
      // $3b00 => FLI Graph 2
    case 17665:
      // $3b00 => FLI editor
    case 17666:
      // $3b00 => FLI Graph
    case 10277: // multicolor CDU-Paint + loadaddr
      // $7EEF
      File_error = 0;
      break;
    default: // then we don't know for now.
      if (load_addr == 0x6000 || load_addr == 0x5c00)
      {
        long unpacked_size;
        byte * buffer = malloc(file_size);
        if (buffer == NULL)
          return;
        fseek(file, SEEK_SET, 0);
        if (!Read_bytes(file, buffer, file_size))
          return;
        unpacked_size = C64_unpack_doodle(&buffer, file_size);
        free(buffer);
        switch (unpacked_size)
        {
          case 9024:  // Doodle hi color
          case 9216:
          case 10001: // Koala painter 2
          case 10070:
            File_error = 0;
        }
      }
  }
}

/**
 * Load C64 hires (320x200)
 *
 * @param context	the IO context
 * @param bitmap the bitmap RAM (8000 bytes)
 * @param screen_ram the screen RAM (1000 bytes)
 */
static void Load_C64_hires(T_IO_Context *context, byte *bitmap, byte *screen_ram)
{
  int cx,cy,x,y,c[4],pixel,color;

  for(cy=0; cy<25; cy++)
  {
    for(cx=0; cx<40; cx++)
    {
      if(screen_ram != NULL)
      {
        c[0]=screen_ram[cy*40+cx]&15;
        c[1]=screen_ram[cy*40+cx]>>4;
      }
      else
      { /// If screen_ram is NULL, uses default C64 basic colors
        c[0] = 6;
        c[1] = 14;
      }
      for(y=0; y<8; y++)
      {
        pixel=bitmap[cy*320+cx*8+y];
        for(x=0; x<8; x++)
        {
          color=c[pixel&(1<<(7-x))?1:0];
          Set_pixel(context, cx*8+x,cy*8+y,color);
        }
      }
    }
  }
}

/**
 * Load C64 multicolor (160x200)
 *
 * @param context	the IO context
 * @param bitmap the bitmap RAM (8000 bytes)
 * @param screen_ram the screen RAM (1000 bytes)
 * @param color_ram the color RAM (1000 bytes)
 * @param background the background color
 */
static void Load_C64_multi(T_IO_Context *context, byte *bitmap, byte *screen_ram, byte *color_ram, byte background)
{
    int cx,cy,x,y,c[4],pixel,color;
    c[0]=background&15;
    for(cy=0; cy<25; cy++)
    {
        for(cx=0; cx<40; cx++)
        {
            c[1]=screen_ram[cy*40+cx]>>4;
            c[2]=screen_ram[cy*40+cx]&15;
            c[3]=color_ram[cy*40+cx]&15;

            for(y=0; y<8; y++)
            {
                pixel=bitmap[cy*320+cx*8+y];
                for(x=0; x<4; x++)
                {
                    color=c[(pixel&3)];
                    pixel>>=2;
                    Set_pixel(context, cx*4+(3-x),cy*8+y,color);
                }
            }
        }
    }
}

/**
 * Loads a C64 FLI (Flexible Line Interpretation) picture.
 * Sets 4 layers :
 *  - Layer 0 : filled with background colors (1 per line)
 *  - Layer 1 : "Color RAM" 4x8 blocks
 *  - Layer 2 : pixels (From Screen RAMs + Bitmap)
 *  - Layer 3 : Transparency layer filled with color 16
 *
 * @param context the IO context
 * @param bitmap 8000 bytes buffer
 * @param screen_ram 8 x 1024 bytes buffers
 * @param color_ram 1000 byte buffer
 * @param background 200 byte buffer
 */
void Load_C64_fli(T_IO_Context *context, byte *bitmap, byte *screen_ram, byte *color_ram, byte *background)
{
  // Thanks to MagerValp for complement of specifications.
  //
  // background : length: 200 (+ padding 56)
  //    These are the BG colors for lines 0-199 (top to bottom)
  //        Low nybble: the color.
  //        High nybble: garbage. ignore it.
  // color_ram  : length: 1000 (+ padding 24)
  //    Color RAM. Contains one color per 4x8 block.
  //    There are 40x25 such blocks, arranged from top left to bottom
  //    right, starting in right direction. For each block there is one byte.
  //        Low nybble: the color.
  //        High nybble: garbage. ignore it.
  // screen_ram : length: 8192
  //    Screen RAMs. The s is important.
  //    This is actually 8 blocks of 1000 bytes, each separated by a filler of
  //    24 bytes. Each byte contains data for a 4x1 pixel group, and a complete
  //    block will contain 40x25 of them. 40 is from left to right, and 25 is from
  //    top to bottom, spacing them 8 lines apart.
  //    The second block start at y=1, the third block starts at y=2, etc...
  //    Each byte contains 2 colors that *can* be used by the 4x1 pixel group:
  //        Low nybble: Color 1
  //        High nybble: Color 2
  //
  // bitmap     : length: 8000
  //    This is the final structure that refers to all others. It describes
  //    160x200 pixels linearly, from top left to bottom right, starting in
  //    right direction. For each pixel, two bits say which color is displayed
  //    (So 4 pixels are described by the same byte)
  //        00 Use the BG color of the current line (background[y])
  //        01 Use the Color 2 from the current 4x8 block of Screen RAM
  //           ((screen_ram[y/8][x/4] & 0xF0) >> 8)
  //        10 Use the Color 1 from the current 4x8 block of Screen RAM
  //           (screen_ram[y/8][x/4] & 0x0F)
  //        11 Use the color from Color RAM
  //           (color_ram[y/8][x/4] & 0x0F)
  //

  int cx,cy,x,y,c[4];

  if (context->Type == CONTEXT_MAIN_IMAGE)
  {
    // Fill layer 0 with background colors
    for(y=0; y<200; y++)
    {
      byte bg_color = 0;
      if (background != NULL)
        bg_color = background[y];
      for(x=0; x<160; x++)
        Set_pixel(context, x,y, bg_color);
    }

    // Fill layer 1 with color ram (1 color per 4x8 block)
    Set_loading_layer(context, 1);
    for(cy=0; cy<25; cy++)
    {
      for(cx=0; cx<40; cx++)
      {
        c[3]=color_ram[cy*40+cx]&15;
        for(y=0; y<8; y++)
        {
          for(x=0; x<4; x++)
          {
            Set_pixel(context, cx*4+x,cy*8+y,c[3]);
          }
        }
      }
    }
  }

  // Layer 2 are actual pixels
  Set_loading_layer(context, 2);
  for(cy=0; cy<25; cy++)
  {
    for(cx=0; cx<40; cx++)
    {
      c[3]=color_ram[cy*40+cx]&15;
      for(y=0; y<8; y++)
      {
        int pixel=bitmap[cy*320+cx*8+y];

        c[0] = 0;
        if(background != NULL)
          c[0] = background[cy*8+y]&15;
        c[1]=screen_ram[y*1024+cy*40+cx]>>4;
        c[2]=screen_ram[y*1024+cy*40+cx]&15;
        for(x=0; x<4; x++)
        {
          int color=c[(pixel&3)];
          pixel>>=2;
          Set_pixel(context, cx*4+(3-x),cy*8+y,color);
        }
      }
    }
  }
  if (context->Type == CONTEXT_MAIN_IMAGE)
  {
    // Fill layer 3 with color 16
    Set_loading_layer(context, 3);
    for(y=0; y<200; y++)
    {
      for(x=0; x<160; x++)
        Set_pixel(context, x,y,16);
    }
  }
}

/**
 * Count the length of the unpacked data
 *
 * RLE encoding is either ESCAPE CODE, COUNT, VALUE
 * or ESCAPE CODE, VALUE, COUNT
 *
 * @param buffer the packed data
 * @param input_size the packed data byte count
 * @param RLE_code the escape code
 * @param order 0 for ESCAPE, COUNT, VALUE, 1 for ESCAPE, VALUE, COUNT
 * @return the unpacked data byte count
 */
static long C64_unpack_get_length(const byte * buffer, long input_size, byte RLE_code, int order)
{
  const byte * end;
  long unpacked_size = 0;

  end = buffer + input_size;
  while(buffer < end)
  {
    if (*buffer == RLE_code)
    {
      if (order)
      { // ESCAPE, VALUE, COUNT
        buffer += 2;  // skip value
        unpacked_size += *buffer;
      }
      else
      { // ESCAPE, COUNT, VALUE
        buffer++;
        if (*buffer == 0)
          break;
        unpacked_size += *buffer++;
      }
    }
    else
      unpacked_size++;
    buffer++;
  }
  return unpacked_size;
}

/**
 * unpack RLE packed data
 *
 * RLE encoding is either ESCAPE CODE, COUNT, VALUE
 * or ESCAPE CODE, VALUE, COUNT
 *
 * @param unpacked buffer to received unpacked data
 * @param buffer the packed data
 * @param input_size the packed data byte count
 * @param RLE_code the escape code
 * @param order 0 for ESCAPE, COUNT, VALUE, 1 for ESCAPE, VALUE, COUNT
 */
static void C64_unpack(byte * unpacked, const byte * buffer, long input_size, byte RLE_code, int order)
{
  const byte * end;

  end = buffer + input_size;
  while(buffer < end)
  {
    if (*buffer == RLE_code)
    {
      byte count;
      byte value;
      buffer++;
      if (order)
      { // ESCAPE, VALUE, COUNT
        value = *buffer++;
        count = *buffer;
      }
      else
      { // ESCAPE, COUNT, VALUE
        count = *buffer++;
        value = *buffer;
      }
      if (count == 0)
        break;
      while (count-- > 0)
        *unpacked++ = value;
    }
    else
      *unpacked++ = *buffer;
    buffer++;
  }
}

/**
 * Unpack the Amica Paint RLE packing
 *
 * @param[in,out] file_buffer will contain the unpacked buffer on return
 * @param[in] file_size packed buffer size
 * @return the unpacked data size or -1 in case of error
 *
 * Ref:
 * - http://codebase64.org/doku.php?id=base:c64_grafix_files_specs_list_v0.03
 */
static long C64_unpack_amica(byte ** file_buffer, long file_size)
{
  long unpacked_size;
  byte * unpacked_buffer;
  const byte RLE_code = 0xC2;

  if (file_size <= 16 || file_buffer == NULL || *file_buffer == NULL)
    return -1;
  unpacked_size = C64_unpack_get_length(*file_buffer + 2, file_size - 2, RLE_code, 0);
  GFX2_Log(GFX2_DEBUG, "C64_unpack_amica() unpacked_size=%ld\n", unpacked_size);
   // 2nd pass to unpack
  unpacked_buffer = malloc(unpacked_size);
  if (unpacked_buffer == NULL)
    return -1;
  C64_unpack(unpacked_buffer, *file_buffer + 2, file_size - 2, RLE_code, 0);

  free(*file_buffer);
  *file_buffer = unpacked_buffer;
  return unpacked_size;
}

/**
 * Unpack the DRAZPAINT RLE packing
 *
 * @param[in,out] file_buffer will contain the unpacked buffer on return
 * @param[in] file_size packed buffer size
 * @return the unpacked data size or -1 in case of error
 *
 * Ref:
 * - https://www.godot64.de/german/l_draz.htm
 * - https://sourceforge.net/p/view64/code/HEAD/tree/trunk/libview64.c#l2805
 */
static long C64_unpack_draz(byte ** file_buffer, long file_size)
{
  long unpacked_size;
  byte * unpacked_buffer;
  byte RLE_code;

  if (file_size <= 16 || file_buffer == NULL || *file_buffer == NULL)
    return -1;
  RLE_code = (*file_buffer)[15];
  // First pass to know unpacked size
  unpacked_size = C64_unpack_get_length(*file_buffer + 16, file_size - 16, RLE_code, 0);
  GFX2_Log(GFX2_DEBUG, "C64_unpack_draz() \"%.13s\" RLE code=$%02X RLE data length=%ld unpacked_size=%ld\n",
           *file_buffer + 2, RLE_code, file_size - 16, unpacked_size);
   // 2nd pass to unpack
  unpacked_buffer = malloc(unpacked_size);
  if (unpacked_buffer == NULL)
    return -1;
  C64_unpack(unpacked_buffer, *file_buffer + 16, file_size - 16, RLE_code, 0);
  free(*file_buffer);
  *file_buffer = unpacked_buffer;
  return unpacked_size;
}

/**
 * Unpack doodle/koala painter 2 data
 *
 * @return the unpacked data size or -1 in case of error
 */
static long C64_unpack_doodle(byte ** file_buffer, long file_size)
{
  long unpacked_size;
  byte * unpacked_buffer;
  const byte RLE_code = 0xFE;

  if (file_size <= 16 || file_buffer == NULL || *file_buffer == NULL)
    return -1;
  // First pass to know unpacked size
  unpacked_size = C64_unpack_get_length(*file_buffer + 2, file_size - 2, RLE_code, 1);
  GFX2_Log(GFX2_DEBUG, "C64_unpack_doodle() unpacked_size=%ld\n", unpacked_size);
   // 2nd pass to unpack
  unpacked_buffer = malloc(unpacked_size);
  if (unpacked_buffer == NULL)
    return -1;
  C64_unpack(unpacked_buffer, *file_buffer + 2, file_size - 2, RLE_code, 1);
  free(*file_buffer);
  *file_buffer = unpacked_buffer;
  return unpacked_size;
}

/**
 * Load C64 pictures formats.
 *
 * Supports:
 * - Hires (with or without ScreenRAM)
 * - Multicolor (Koala or CDU-paint format)
 * - FLI
 *
 * see http://unusedino.de/ec64/technical/formats/bitmap.html
 *
 * @param context the IO context
 */
void Load_C64(T_IO_Context * context)
{
    FILE* file;
    long file_size;
    byte hasLoadAddr=0;
    word load_addr;
    enum c64_format loadFormat = F_invalid;

    byte *file_buffer;
    byte *bitmap, *screen_ram, *color_ram=NULL, *background=NULL; // Only pointers to existing data
    byte *temp_buffer = NULL;
    word width, height=200;

    file = Open_file_read(context);

    if (file)
    {
        File_error=0;
        file_size = File_length_file(file);

        // Load entire file in memory
        file_buffer=(byte *)malloc(file_size);
        if (!file_buffer)
        {
            File_error = 1;
            fclose(file);
            return;
        }
        if (!Read_bytes(file,file_buffer,file_size))
        {
            File_error = 1;
            free(file_buffer);
            fclose(file);
            return;
        }
        fclose(file);

        // get load address (valid only if hasLoadAddr = 1)
        load_addr = file_buffer[0] | (file_buffer[1] << 8);

        // Unpack if needed
        if (memcmp(file_buffer + 2, "DRAZPAINT", 9) == 0)
          file_size = C64_unpack_draz(&file_buffer, file_size);
        else if(load_addr == 0x4000 && file_buffer[file_size-2] == 0xC2 && file_buffer[file_size-1] == 0)
          file_size = C64_unpack_amica(&file_buffer, file_size);
        else if (file_size < 8000 && (load_addr == 0x6000 || load_addr == 0x5c00))
          file_size = C64_unpack_doodle(&file_buffer, file_size);

        switch (file_size)
        {
            case 8000: // raw bitmap
                hasLoadAddr=0;
                loadFormat=F_bitmap;
                bitmap=file_buffer+0; // length: 8000
                screen_ram=NULL;
                break;

            case 8002: // raw bitmap with loadaddr
                hasLoadAddr=1;
                loadFormat=F_bitmap;
                bitmap=file_buffer+2; // length: 8000
                screen_ram=NULL;
                break;

            case 9000: // bitmap + ScreenRAM
                hasLoadAddr=0;
                loadFormat=F_hires;
                bitmap=file_buffer+0; // length: 8000
                screen_ram=file_buffer+8000; // length: 1000
                break;

            case 9003: // bitmap + ScreenRAM + loadaddr (+ border ?)
            case 9002: // bitmap + ScreenRAM + loadaddr
                hasLoadAddr=1;
                loadFormat=F_hires;
                bitmap=file_buffer+2; // length: 8000
                screen_ram=file_buffer+8002; // length: 1000
                break;

            case 9009: // Art Studio (.aas)
                hasLoadAddr=1;
                loadFormat=F_hires;
                bitmap=file_buffer+2; // length: 8000
                screen_ram=file_buffer+8002; // length: 1000
                break;

            case 9024:  // Doodle (unpacked from .jj)
            case 9216:
                hasLoadAddr=0;
                loadFormat=F_hires;
                screen_ram=file_buffer; // length: 1000 (+24 padding)
                bitmap=file_buffer+1024; // length: 8000
                break;

            case 9218: // Doodle (.dd)
                hasLoadAddr=1;
                loadFormat=F_hires;
                screen_ram=file_buffer+2; // length: 1000 (+24 padding)
                bitmap=file_buffer+1024+2; // length: 8000
                break;

            case 9332:  // Paint Magic .pmg
                hasLoadAddr=1;
                loadFormat=F_multi;
                // Display routine between offset $0002 and $0073 (114 bytes)
                // duplicated between offset      $2002 and $2073
                bitmap=file_buffer+114+2;         // $0074
                background=file_buffer+8000+114+2;// $1FB4
                temp_buffer=malloc(1000);
                memset(temp_buffer, file_buffer[3+8000+114+2], 1000); // color RAM Byte
                color_ram=temp_buffer;
                //border  byte = file_buffer[4+8000+114+2];
                screen_ram=file_buffer+8192+114+2;  // $2074
                break;

            case 10001: // multicolor
            case 10070: // unpacked file.
                hasLoadAddr=0;
                loadFormat=F_multi;
                bitmap=file_buffer+0; // length: 8000
                screen_ram=file_buffer+8000; // length: 1000
                color_ram=file_buffer+9000; // length: 1000
                background=file_buffer+10000; // only 1
                break;

            case 10003: // multicolor + loadaddr
            case 10004: // extra byte is border color
            case 10006: // Run Paint
                hasLoadAddr=1;
                loadFormat=F_multi;
                bitmap=file_buffer+2; // length: 8000
                screen_ram=file_buffer+8002; // length: 1000
                color_ram=file_buffer+9002; // length: 1000
                background=file_buffer+10002; // only 1
                break;

            case 10018: // Advanced Art Studio (.ocp) + loadaddr
                hasLoadAddr=1;
                loadFormat=F_multi;
                bitmap=file_buffer+2; // length: 8000
                screen_ram=file_buffer+8000+2; // length: 1000
                color_ram=file_buffer+9016+2; // length: 1000
                // filebuffer+9000+2 is border
                background=file_buffer+9001+2; // only 1
                break;

            case 10022: // Micro Illustrator (.mil)
                hasLoadAddr=1;
                loadFormat=F_multi;
                screen_ram=file_buffer+20+2;
                color_ram=file_buffer+1000+20+2;
                bitmap=file_buffer+2*1000+20+2;
                break;

            case 10049: // unpacked DrazPaint
                hasLoadAddr=1;
                loadFormat=F_multi;
                color_ram=file_buffer; // length: 1000 + (padding 24)
                screen_ram=file_buffer+1024; // length: 1000 + (padding 24)
                bitmap=file_buffer+1024*2; // length: 8000
                background=file_buffer+8000+1024*2;
                break;

            case 10050: // Picasso64 multicolor + loadaddr
                hasLoadAddr=1;
                loadFormat=F_multi;
                color_ram=file_buffer+2; // length: 1000 + (padding 24)
                screen_ram=file_buffer+1024+2; // length: 1000 + (padding 24)
                bitmap=file_buffer+1024*2+2; // length: 8000
                background=file_buffer+1024*2+2-1; // only 1
                break;

            case 10218: // Image System
                hasLoadAddr=1;
                loadFormat=F_multi;
                color_ram=file_buffer+2; // Length: 1000 (+ padding 24)
                bitmap=file_buffer+1024+2; // Length: 8000 (+padding 192)
                screen_ram=file_buffer+8192+1024+2;  // Length: 1000 (no padding)
                background=file_buffer+8192+1024+2-1; // only 1
                break;

            case 10219: // Saracen Paint (.sar)
                hasLoadAddr=1;
                loadFormat=F_multi;
                screen_ram=file_buffer+2;  // Length: 1000 (+ padding24)
                background=file_buffer+1008+2; // offset 0x3F0 (only 1 byte)
                bitmap=file_buffer+1024+2; // Length: 8000 (+padding 192)
                color_ram=file_buffer+8192+1024+2; // Length: 1000 (+ padding 24)
                break;

            case 10242: // Artist 64/Blazing Paddles/Rainbow Painter multicolor + loadaddr
                hasLoadAddr=1;
                loadFormat=F_multi;
                switch(load_addr)
                {
                  default:
                  case 0x4000:  // Artist 64
                    bitmap=file_buffer+2; // length: 8000 (+padding 192)
                    screen_ram=file_buffer+8192+2; // length: 1000 + (padding 24)
                    color_ram=file_buffer+1024+8192+2; // length: 1000 + (padding 24)
                    background=file_buffer+1024*2+8192+2-1; // only 1
                    break;
                  case 0xa000:  // Blazing Paddles
                    bitmap=file_buffer+2; // length: 8000 (+padding 192)
                    screen_ram=file_buffer+8192+2; // length: 1000 + (padding 24)
                    color_ram=file_buffer+1024+8192+2; // length: 1000 + (padding 24)
                    background=file_buffer+8064+2; // only 1
                    break;
                  case 0x5c00:  // Rainbow Painter
                    screen_ram=file_buffer+2; // length: 1000 + (padding 24)
                    bitmap=file_buffer+1024+2; // length: 8000 (+padding 192)
                    color_ram=file_buffer+1024+8192+2; // length: 1000 + (padding 24)
                    background=file_buffer; // only 1
                    break;
                }
                break;

            case 10257: // unpacked Amica Paint (.ami)
                hasLoadAddr=1;
                loadFormat=F_multi;
                bitmap=file_buffer; // length 8000
                screen_ram=file_buffer+8000;  // length: 1000
                color_ram=file_buffer+1000+8000;// length:1000
                background=file_buffer+2*1000+8000;//1
                // remaining bytes (offset 10001, length 256) are a "Color Rotation Table"
                // we should decode if we learn its format...
                break;

            case 10277: // multicolor CDU-Paint + loadaddr
                hasLoadAddr=1;
                loadFormat=F_multi;
                // 273 bytes of display routine
                bitmap=file_buffer+275; // length: 8000
                screen_ram=file_buffer+8275; // length: 1000
                color_ram=file_buffer+9275; // length: 1000
                background=file_buffer+10275; // only 1
                break;

            case 17472: // FLI (BlackMail)
                hasLoadAddr=0;
                loadFormat=F_fli;
                background=file_buffer+0; // length: 200 (+ padding 56)
                color_ram=file_buffer+256; // length: 1000 (+ padding 24)
                screen_ram=file_buffer+1280; // length: 8192
                bitmap=file_buffer+9472; // length: 8000
                break;

            case 17474: // FLI (BlackMail) + loadaddr
                hasLoadAddr=1;
                loadFormat=F_fli;
                background=file_buffer+2; // length: 200 (+ padding 56)
                color_ram=file_buffer+258; // length: 1000 (+ padding 24)
                screen_ram=file_buffer+1282; // length: 8192
                bitmap=file_buffer+9474; // length: 8000
                break;

            case 17218:
            case 17409: // FLI-Designer v1.1 (+loadaddr)
            case 17410: // => FLI MATIC (background at 2+1024+8192+8000+65 ?)
              hasLoadAddr=1;
              loadFormat=F_fli;
              background=NULL;
              color_ram=file_buffer+2; // length: 1000 (+ padding 24)
              screen_ram=file_buffer+1024+2; // length: 8192
              bitmap=file_buffer+8192+1024+2; // length: 8000
              break;

            case 17666: // FLI Graph
              hasLoadAddr=1;
              loadFormat=F_fli;
              background=file_buffer+2;
              color_ram=file_buffer+256+2; // length: 1000 (+ padding 24)
              screen_ram=file_buffer+1024+256+2; // length: 8192
              bitmap=file_buffer+8192+1024+256+2; // length: 8000
              break;

            case 17665: // FLI Editor
              hasLoadAddr=1;
              loadFormat=F_fli;
              background=file_buffer+8;
              color_ram=file_buffer+256+2; // length: 1000 (+ padding 24)
              screen_ram=file_buffer+1024+256+2; // length: 8192
              bitmap=file_buffer+8192+1024+256+2; // length: 8000
              break;

            default:
                File_error = 1;
                free(file_buffer);
                return;
        }

        if (loadFormat == F_invalid)
        {
          File_error = 1;
          free(file_buffer);
          return;
        }

        if (loadFormat == F_fli || loadFormat == F_multi)
        {
          context->Ratio = PIXEL_WIDE;
          width = 160;
        }
        else
        {
          context->Ratio = PIXEL_SIMPLE;
          width = 320;
        }

        // Write detailed format in comment
        if (hasLoadAddr)
          snprintf(context->Comment,COMMENT_SIZE+1,"%s, load at $%4.4X",c64_format_names[loadFormat],load_addr);
        else
          snprintf(context->Comment,COMMENT_SIZE+1,"%s, no addr",c64_format_names[loadFormat]);

        Pre_load(context, width, height, file_size, FORMAT_C64, context->Ratio, (loadFormat == F_bitmap) ? 1 : 4); // Do this as soon as you can

        if (Config.Clear_palette)
          memset(context->Palette,0, sizeof(T_Palette));
        C64_set_palette(context->Palette);
        context->Transparent_color=16;

        switch(loadFormat)
        {
          case F_fli:
            Load_C64_fli(context,bitmap,screen_ram,color_ram,background);
            Set_image_mode(context, IMAGE_MODE_C64FLI);
            break;
          case F_multi:
            Load_C64_multi(context,bitmap,screen_ram,color_ram,
                           (background==NULL) ? 0 : *background);
            Set_image_mode(context, IMAGE_MODE_C64MULTI);
            break;
          default:
            Load_C64_hires(context,bitmap,screen_ram);
            if (loadFormat == F_hires)
              Set_image_mode(context, IMAGE_MODE_C64HIRES);
        }

        free(file_buffer);
        if (temp_buffer)
          free(temp_buffer);
    }
    else
        File_error = 1;
}

/**
 * Display the dialog for C64 save parameters
 *
 * @param[in,out] saveFormat one of the C64 mode from @ref c64_format
 * @param[in,out] saveWhat 0=All, 1=Only bitmap, 2=Only Screen RAM, 3=Only color RAM
 * @param[in,out] loadAddr actual load address or 0 for "None"
 * @return true to proceed, false to abort
 */
static int Save_C64_window(enum c64_format *saveFormat, byte *saveWhat, word *loadAddr)
{
  int button;
  unsigned int i;
  T_Dropdown_button *what, *addr;
  T_Dropdown_button *format;
  static const char * what_label[] = {
    "All",
    "Bitmap",
    "Screen",
    "Color"
  };
  static const char * address_label[] = {
    "None",
    "$2000",
    "$4000",
    "$6000",
    "$8000",
    "$A000",
    "$C000",
    "$E000"
  };
  // default addresses :
  //  - FLI Fli Graph 2 (BlackMail) => $3b00
  //  - multicolor (Koala Painter) => $6000
  //  - hires (InterPaint) => $4000

  Open_window(200,120,"C64 saving settings");
  Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1
  Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2

  Print_in_window(13,18,"Data:",MC_Dark,MC_Light);
  what = Window_set_dropdown_button(10,28,90,15,70,what_label[*saveWhat],1, 0, 1, LEFT_SIDE,0); // 3
  Window_dropdown_clear_items(what);
  for (i=0; i<sizeof(what_label)/sizeof(what_label[0]); i++)
    Window_dropdown_add_item(what,i,what_label[i]);

  Print_in_window(113,18,"Address:",MC_Dark,MC_Light);
  addr = Window_set_dropdown_button(110,28,70,15,70,address_label[*loadAddr/0x2000],1, 0, 1, LEFT_SIDE,0); // 4
  Window_dropdown_clear_items(addr);
  for (i=0; i<sizeof(address_label)/sizeof(address_label[0]); i++)
    Window_dropdown_add_item(addr,i,address_label[i]);

  Print_in_window(13,46,"Format:",MC_Dark,MC_Light);
  format = Window_set_dropdown_button(10,56,90,15,88,c64_format_names[*saveFormat],1, 0, 1, LEFT_SIDE,0); // 5
  if (*saveFormat == F_hires || *saveFormat == F_bitmap)
  {
    Window_dropdown_add_item(format, F_hires, c64_format_names[F_hires]);
    Window_dropdown_add_item(format, F_bitmap, c64_format_names[F_bitmap]);
  }
  else
  {
    Window_dropdown_add_item(format, F_multi, c64_format_names[F_multi]);
    Window_dropdown_add_item(format, F_fli, c64_format_names[F_fli]);
  }

  Update_window_area(0,0,Window_width,Window_height);
  Display_cursor();

  do
  {
    button = Window_clicked_button();
    if (Is_shortcut(Key, 0x100+BUTTON_HELP))
    {
      Key = 0;
      Window_help(BUTTON_SAVE, "COMMODORE 64 FORMATS");
    }
    else switch(button)
    {
      case 3: // Save what
        *saveWhat = Window_attribute2;
        GFX2_Log(GFX2_DEBUG, "Save_C64_Window() : what=%d (%s)\n", *saveWhat, what_label[*saveWhat]);
        break;

      case 4: // Load addr
        *loadAddr = Window_attribute2*0x2000;
        GFX2_Log(GFX2_DEBUG, "Save_C64_Window() : addr=$%04x (%d)\n",*loadAddr,Window_attribute2);
        break;

      case 5:
        *saveFormat = Window_attribute2;
        GFX2_Log(GFX2_DEBUG, "Save_C64_Window() : format=%d\n", Window_attribute2);
        break;

      case 0: break;
    }
  } while(button!=1 && button!=2);

  Close_window();
  Display_cursor();
  return button==1;
}

/// Save a C64 hires picture
///
/// c64 hires is 320x200 with only 2 colors per 8x8 block.
static int Save_C64_hires(T_IO_Context *context, byte saveWhat, word loadAddr)
{
  int i, pos = 0;
  word cx, cy, x, y;
  byte screen_ram[1000],bitmap[8000];
  FILE *file;

  for(cy=0; cy<25; cy++) // Character line, 25 lines
  {
    for(cx=0; cx<40; cx++) // Character column, 40 columns
    {
      byte fg, bg;  // foreground and background colors for the 8x8 block
      byte c[2];
      int count = 0;
      // first pass : find colors used
      for(y=0; y<8; y++)
      {
        for(x=0; x<8; x++)
        {
          byte pixel = Get_pixel(context, x+cx*8,y+cy*8);
          if(pixel>15)
          {
            Warning_message("Color above 15 used");
            // TODO hilite offending block here too?
            // or make it smarter with color allocation?
            // However, the palette is fixed to the 16 first colors
            return 1;
          }
          for (i = 0; i < count; i++)
          {
            if (c[i] == pixel)
              break;
          }
          if (i >= 2)
          {
            Warning_with_format("More than 2 colors\nin 8x8 pixel cell: (%d, %d)\nRect: (%d, %d, %d, %d)", cx, cy, cx * 8, cy * 8, cx * 8 + 7, cy * 8 + 7);
            // TODO here we should hilite the offending block
            return 1;
          }
          if (i >= count)
            c[count++] = pixel;
        }
      }

      if (count == 1)
      {
        if (c[0] == 0)  // only black
          fg = 1; // white
        else
          fg = c[0];
        bg = 0; // black
      }
      else
      {
        // set lower color index as background
        if (c[0] < c[1])
        {
          fg = c[1];
          bg = c[0];
        }
        else
        {
          fg = c[0];
          bg = c[1];
        }
      }
      screen_ram[cx+cy*40] = (fg<<4) | bg;

      // 2nd pass : store bitmap (0 = background, 1 = foreground)
      for(y=0; y<8; y++)
      {
        byte bits = 0;
        for(x=0; x<8; x++)
        {
          bits <<= 1;
          if (Get_pixel(context, x+cx*8, y+cy*8) == fg)
            bits |= 1;
        }
        bitmap[pos++] = bits;
      }
    }
  }

  file = Open_file_write(context);

  if(!file)
  {
    Warning_message("File open failed");
    File_error = 1;
    return 1;
  }

  if (loadAddr)
    Write_word_le(file,loadAddr);

  if (saveWhat==0 || saveWhat==1)
    Write_bytes(file,bitmap,8000);
  if (saveWhat==0 || saveWhat==2)
    Write_bytes(file,screen_ram,1000);

  fclose(file);
  return 0;
}


/**
 * Save a C64 FLI (Flexible Line Interpretation) picture.
 *
 * This function is able to save a one layer picture, by finding
 * itself the background colors and color RAM value to be used.
 *
 * The algorithm is :
 * - first choose the lowest value for all possible background colors for each line
 * - first the lowest value from the possible colors for color RAM
 * - encode bitmap and screen RAMs
 *
 * The algorithm can fail by picking a "wrong" background color for a line,
 * that make the choice for the color RAM value of one of the 40 blocks impossible.
 *
 * @param context the IO context
 * @param saveWhat what part of the data to save
 * @param loadAddr The load address
 */
int Save_C64_fli_monolayer(T_IO_Context *context, byte saveWhat, word loadAddr)
{
  FILE * file;
  byte bitmap[8000],screen_ram[1024*8],color_ram[1024];
  byte background[256];

  memset(bitmap, 0, sizeof(bitmap));
  memset(screen_ram, 0, sizeof(screen_ram));
  memset(color_ram, 0, sizeof(color_ram));
  memset(background, 0, sizeof(background));

  memset(color_ram, 0xff, 40*25); // no hint
  memset(background, 0xff, 200);

  if (C64_pixels_to_FLI(bitmap, screen_ram, color_ram, background, context->Target_address, context->Pitch, 0) > 0)
    return 1;

  file = Open_file_write(context);

  if(!file)
  {
    Warning_message("File open failed");
    File_error = 1;
    return 1;
  }

  if (loadAddr)
    Write_word_le(file, loadAddr);

  if (saveWhat==0)
    Write_bytes(file,background,256);    // Background colors for lines 0-199 (+ 56bytes padding)

  if (saveWhat==0 || saveWhat==3)
    Write_bytes(file,color_ram,1024); // Color RAM (1000 bytes + padding 24)

  if (saveWhat==0 || saveWhat==1)
    Write_bytes(file,screen_ram,8192);  // Screen RAMs 8 x (1000 bytes + padding 24)

  if (saveWhat==0 || saveWhat==2)
    Write_bytes(file,bitmap,8000);  // BitMap

  fclose(file);

  return 0;
}

/**
 * Save a C64 multicolor picture
 *
 * @param context the IO context
 * @param saveWhat what part of the data to save
 * @param loadAddr The load address
 */
int Save_C64_multi(T_IO_Context *context, byte saveWhat, word loadAddr)
{
    /*
    BITS     COLOR INFORMATION COMES FROM
    00     Background color #0 (screen color)
    01     Upper 4 bits of Screen RAM
    10     Lower 4 bits of Screen RAM
    11     Color RAM nybble (nybble = 1/2 byte = 4 bits)
    */

    int cx,cy,x,y,c[4]={0,0,0,0},color,lut[16],bits,pixel,pos=0;
    int cand,n,used;
    word cols, candidates = 0, invalids = 0;

    // FIXME allocating this on the stack is not a good idea. On some platforms
    // the stack has a rather small size...
    byte bitmap[8000],screen_ram[1000],color_ram[1000];

    word numcolors;
    dword cusage[256];
    byte i,background=0;
    FILE *file;

    // Detect the background color the image should be using. It's the one that's
    // used on all tiles having 4 colors.
    for(y=0;y<200;y=y+8)
    {
        for (x = 0; x<160; x=x+4)
        {
            cols = 0;

            // Compute the usage count of each color in the tile
            for (cy=0;cy<8;cy++)
            for (cx=0;cx<4;cx++)
            {
              pixel=Get_pixel(context, x+cx,y+cy);
              if(pixel>15)
              {
                Warning_message("Color above 15 used");
                // TODO hilite as in hires, you should stay to
                // the fixed 16 color palette
                return 1;
              }
              cols |= (1 << pixel);
            }

            cand = 0;
            used = 0;
            // Count the number of used colors in the tile
            for (n = 0; n<16; n++)
            {
                if (cols & (1 << n))
                    used++;
            }

            if (used>3)
            {
                GFX2_Log(GFX2_DEBUG, "(%3d,%3d) used=%d cols=%04x\n", x, y, used,(unsigned)cols);
                // This is a tile that uses the background color (and 3 others)

                // Try to guess which color is most likely the background one
                for (n = 0; n<16; n++)
                {
                    if ((cols & (1 << n)) && !((candidates | invalids) & (1 << n))) {
                        // This color is used in this tile but
                        // was not used in any other tile yet,
                        // so it could be the background one.
                        candidates |= 1 << n;
                    }

                    if ((cols & (1 << n)) == 0 ) {
                        // This color isn't used at all in this tile:
                        // Can't be the global background
                        invalids |= 1 << n;
                        candidates &= ~(1 << n);
                    }

                    if (candidates & (1 << n)) {
                        // We have a candidate, mark it as such
                        cand++;
                    }
                }

                // After checking the constraints for this tile, do we have
                // candidate background colors left ?
                if (cand==0)
                {
                    Warning_message("No possible global background color");
                    return 1;
                }
            }
        }
    }

	// Now just pick the first valid candidate
	for (n = 0; n<16; n++)
	{
		if (candidates & (1 << n)) {
			background = n;
			break;
		}
	}
  GFX2_Log(GFX2_DEBUG, "Save_C64_multi() background=%d ($%x) candidates=%x invalid=%x\n",
           (int)background, (int)background, (unsigned)candidates, (unsigned)invalids);


  // Now that we know which color is the background, we can encode the cells
  for(cy=0; cy<25; cy++)
  {
    for(cx=0; cx<40; cx++)
    {
      numcolors=Count_used_colors_area(cusage,cx*4,cy*8,4,8);
      if(numcolors>4)
      {
        Warning_with_format("More than 4 colors\nin 4x8 pixel cell: (%d, %d)\nRect: (%d, %d, %d, %d)", cx, cy, cx * 4, cy * 8, cx * 4 + 3, cy * 8 + 7);
        // TODO hilite offending block
        return 1;
      }
      color=1;
      c[0]=background;
      for(i=0; i<16; i++)
      {
        lut[i]=0;
        if(cusage[i] && (i!=background))
        {
          lut[i]=color;
          c[color]=i;
          color++;
        }
      }
      // add to screen_ram and color_ram
      screen_ram[cx+cy*40]=c[1]<<4|c[2];
      color_ram[cx+cy*40]=c[3];

      for(y=0;y<8;y++)
      {
        bits=0;
        for(x=0;x<4;x++)
        {
          pixel = Get_pixel(context, cx*4+x,cy*8+y);
          bits = (bits << 2) | lut[pixel];
        }
        bitmap[pos++]=bits;
      }
    }
  }

  file = Open_file_write(context);

  if(!file)
  {
    Warning_message("File open failed");
    File_error = 2;
    return 2;
  }

  setvbuf(file, NULL, _IOFBF, 64*1024);

  if (loadAddr)
    Write_word_le(file,loadAddr);

  if (saveWhat==0 || saveWhat==1)
    Write_bytes(file,bitmap,8000);

  if (saveWhat==0 || saveWhat==2)
    Write_bytes(file,screen_ram,1000);

  if (saveWhat==0 || saveWhat==3)
    Write_bytes(file,color_ram,1000);

  if (saveWhat==0)
    Write_byte(file,background);

  fclose(file);
  return 0;
}

/**
 * Save a C64 FLI (Flexible Line Interpretation) picture.
 *
 * This function need a 3 layer image :
 * - layer 0 is background colors
 * - layer 1 is color RAM values (4x8 blocks)
 * - layer 2 is the actual picture
 *
 * @param context the IO context
 * @param saveWhat what part of the data to save
 * @param loadAddr The load address
 */
int Save_C64_fli(T_IO_Context * context, byte saveWhat, word loadAddr)
{
  FILE *file;
  byte file_buffer[17474];

  memset(file_buffer,0,sizeof(file_buffer));

  switch(C64_FLI(context, file_buffer+9474, file_buffer+1282, file_buffer+258, file_buffer+2))
  {
    case 0: // OK
      break;
    case 1:
      Warning_message("Less than 3 layers");
      File_error=1;
      return 1;
    case 2:
      Warning_message("Picture must be 160x200");
      File_error=1;
      return 1;
    default:
      File_error=1;
      return 1;
  }

  file = Open_file_write(context);

  if(!file)
  {
    Warning_message("File open failed");
    File_error = 1;
    return 1;
  }

  if (loadAddr)
    Write_word_le(file, loadAddr);

  if (saveWhat==0)
    Write_bytes(file,file_buffer+2,256);    // Background colors for lines 0-199 (+ 56bytes padding)

  if (saveWhat==0 || saveWhat==3)
    Write_bytes(file,file_buffer+258,1024); // Color RAM (1000 bytes + padding 24)

  if (saveWhat==0 || saveWhat==1)
    Write_bytes(file,file_buffer+1282,8192);  // Screen RAMs 8 x (1000 bytes + padding 24)

  if (saveWhat==0 || saveWhat==2)
    Write_bytes(file,file_buffer+9474,8000);  // BitMap

  fclose(file);
  return 0;
}

/**
 * Save C64 picture.
 *
 * Supports :
 * - HiRes (320x200)
 * - Multicolor
 * - FLI
 *
 * @param context the IO context
 */
void Save_C64(T_IO_Context * context)
{
  enum c64_format saveFormat = F_invalid;
  static byte saveWhat=0;
  static word loadAddr=0;

  if (((context->Width!=320) && (context->Width!=160)) || context->Height!=200)
  {
    Warning_message("must be 320x200 or 160x200");
    File_error = 1;
    return;
  }

  saveFormat = (context->Width == 320) ? F_hires : F_multi;

  GFX2_Log(GFX2_DEBUG, "Save_C64() extension : %s\n", context->File_name + strlen(context->File_name) - 4);
  if (strcasecmp(context->File_name + strlen(context->File_name) - 4, ".fli") == 0)
    saveFormat = F_fli;

  if(!Save_C64_window(&saveFormat, &saveWhat,&loadAddr))
  {
    File_error = 1;
    return;
  }

  Set_saving_layer(context, 0);
  switch (saveFormat)
  {
    case F_fli:
      if (context->Nb_layers < 3)
        File_error = Save_C64_fli_monolayer(context, saveWhat, loadAddr);
      else
        File_error = Save_C64_fli(context, saveWhat, loadAddr);
      break;
    case F_multi:
      File_error = Save_C64_multi(context, saveWhat, loadAddr);
      break;
    case F_bitmap:
      saveWhat = 1; // force save bitmap
#if defined(__GNUC__) && (__GNUC__ >= 7)
      __attribute__ ((fallthrough));
#endif
    case F_hires:
    default:
      File_error = Save_C64_hires(context, saveWhat, loadAddr);
  }
}


/**
 * Test for SCR file (Amstrad CPC)
 *
 * SCR file format is from "Advanced OCP Art Studio" :
 * http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats
 *
 * .WIN "window" format is also supported.
 *
 * For now we check the presence of a valid PAL file.
 * If the PAL file is not there the pixel data may still be valid.
 * The file size depends on the screen resolution.
 * An AMSDOS header would be a good indication but in some cases it may not
 * be there.
 */
void Test_SCR(T_IO_Context * context, FILE * file)
{
  FILE * pal_file;
  unsigned long pal_size, file_size;
  byte mode, color_anim_flag;

  File_error = 1;

  file_size = File_length_file(file);
  if (file_size > 16384+128)
    return;

  // requires the PAL file
  pal_file = Open_file_read_with_alternate_ext(context, "pal");
  if (pal_file == NULL)
    return;
  /** @todo the palette data can be hidden in the 48 "empty" bytes
   * every 2048 bytes of a standard resolution SCR file.
   * So we should detect the hidden Z80 code and load them.
   * Load address of file is C000. Z80 code :<br>
   * <tt>C7D0: 3a d0 d7 cd 1c bd 21 d1 d7 46 48 cd 38 bc af 21 | :.....!..FH.8..!</tt><br>
   * <tt>C7E0: d1 d7 46 48 f5 e5 cd 32 bc e1 f1 23 3c fe 10 20 | ..FH...2...#<.. </tt><br>
   * <tt>C7F0: f1 c3 18 bb 00 00 00 00 00 00 00 00 00 00 00 00 | ................</tt><br>
   * mode and palette :<br>
   * <tt>D7D0: 00 1a 00 0c 03 0b 01 0d 17 10 02 0f 09 19 06 00 | ................</tt><br>
   * https://gitlab.com/GrafX2/grafX2/merge_requests/121#note_119964168
   */


  pal_size = File_length_file(pal_file);
  if (pal_size == 239+128)
  {
    if (!CPC_check_AMSDOS(pal_file, NULL, NULL))
    {
      fclose(pal_file);
      return;
    }
    fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header
  }
  else if (pal_size != 239)
  {
    fclose(pal_file);
    return;
  }

  if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag))
  {
    fclose(pal_file);
    return;
  }
  GFX2_Log(GFX2_DEBUG, "Test_SCR() mode=%d color animation flag %02X\n", mode, color_anim_flag);
  if (mode <= 2 && (color_anim_flag == 0 || color_anim_flag == 0xff))
    File_error = 0;
  fclose(pal_file);
}

/**
 * Load Advanced OCP Art Studio files (Amstrad CPC)
 *
 * Only standard resolution files (Mode 0 160x200, mode 1 320x200 and
 * mode 2 640x200) are supported. The .PAL file presence is required.
 * "MJH" RLE packing is supported.
 *
 * .WIN "window" format is also supported.
 *
 * @todo Ask user for screen size (or register values) in order to support
 * non standard resolutions.
 */
void Load_SCR(T_IO_Context * context)
{
    // The Amstrad CPC screen memory is mapped in a weird mode, somewhere
    // between bitmap and textmode. Basically the only way to decode this is to
    // emulate the video chip and read the bytes as needed...
    // Moreover, the hardware allows the screen to have any size from 8x1 to
    // 800x273 pixels, and there is no indication of that in the file besides
    // its size. It can also use any of the 3 screen modes. Fortunately this
    // last bit of information is stored in the palette file.
    // Oh, and BTW, the picture can be offset, and it's even usual to do it,
    // because letting 128 pixels unused at the beginning of the file make it a
    // lot easier to handle screens using more than 16K of VRam.
    // The pixel encoding change with the video mode so we have to know that
    // before attempting to load anything...
    // As if this wasn't enough, Advanced OCP Art Studio, the reference tool on
    // Amstrad, can use RLE packing when saving files, meaning we also have to
    // handle that.

    // All this mess enforces us to load (and unpack if needed) the file to a
    // temporary 32k buffer before actually decoding it.
  FILE * pal_file, * file;
  unsigned long real_file_size, file_size, amsdos_file_size = 0;
  byte mode, color_anim_flag, color_anim_delay;
  byte pal_data[236]; // 12 palettes of 16+1 colors + 16 excluded inks + 16 protected inks
  word width, height = 200;
  byte bpp;
  enum PIXEL_RATIO ratio;
  byte * pixel_data;
  word x, y;
  int i;
  byte sig[3];
  word block_length;
  word win_width, win_height;
  int is_win = 0;
  int columns = 80;

  File_error = 1;
  // requires the PAL file
  pal_file = Open_file_read_with_alternate_ext(context, "pal");
  if (pal_file == NULL)
    return;
  file_size = File_length_file(pal_file);
  if (file_size == 239+128)
  {
    if (!CPC_check_AMSDOS(pal_file, NULL, NULL))
    {
      fclose(pal_file);
      return;
    }
    fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header
  }
  if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag)
      || !Read_byte(pal_file, &color_anim_delay) || !Read_bytes(pal_file, pal_data, 236))
  {
    GFX2_Log(GFX2_WARNING, "Load_SCR() failed to load .PAL file\n");
    fclose(pal_file);
    return;
  }
  fclose(pal_file);
  GFX2_Log(GFX2_DEBUG, "Load_SCR() mode=%d color animation flag=%02X delay=%u\n",
           mode, color_anim_flag, color_anim_delay);
  switch (mode)
  {
    case 0:
      width = 160;
      bpp = 4;
      ratio = PIXEL_WIDE;
      break;
    case 1:
      width = 320;
      bpp = 2;
      ratio = PIXEL_SIMPLE;
      break;
    case 2:
      width = 640;
      bpp = 1;
      ratio = PIXEL_TALL;
      break;
    default:
      return; // unsupported
  }

  if (Config.Clear_palette)
    memset(context->Palette,0,sizeof(T_Palette));
  // Setup the palette (amstrad hardware palette)
  CPC_set_HW_palette(context->Palette + 0x40);

  // Set the palette for this picture
  for (i = 0; i < 16; i++)
    context->Palette[i] = context->Palette[pal_data[12*i]];

  file = Open_file_read(context);
  if (file == NULL)
    return;
  file_size = File_length_file(file);
  real_file_size = file_size;
  if (CPC_check_AMSDOS(file, NULL, &amsdos_file_size))
  {
    if (file_size < (amsdos_file_size + 128))
    {
      GFX2_Log(GFX2_ERROR, "Load_SCR() mismatch in file size. AMSDOS file size %lu, should be %lu\n", amsdos_file_size, file_size - 128);
      fclose(file);
      return;
    }
    else if (file_size > (amsdos_file_size + 128))
      GFX2_Log(GFX2_INFO, "Load_SCR() %lu extra bytes at end of file\n", file_size - 128 - amsdos_file_size);
    fseek(file, 128, SEEK_SET); // right after AMSDOS header
    file_size = amsdos_file_size;
  }
  else
    fseek(file, 0, SEEK_SET);

  if (file_size > 16384)  // we don't support bigger files yet
  {
    fclose(file);
    return;
  }

  if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length))
  {
    fclose(file);
    return;
  }
  fseek(file, -5, SEEK_CUR);

  pixel_data = malloc(16384);
  memset(pixel_data, 0, 16384);

  if (0 != memcmp(sig, "MJH", 3) || block_length > 16384)
  {
    // raw data
    Read_bytes(file, pixel_data, file_size);
    i = file_size;
  }
  else
  {
    // MJH packed format
    i = 0;
    do
    {
      if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length))
        break;
      if (0 != memcmp(sig, "MJH", 3))
        break;
      GFX2_Log(GFX2_DEBUG, "  %.3s block %u\n", sig, block_length);
      file_size -= 5;
      while (block_length > 0)
      {
        byte code;
        if (!Read_byte(file, &code))
          break;
        file_size--;
        if (code == 1)
        {
          byte repeat, value;
          if (!Read_byte(file, &repeat) || !Read_byte(file, &value))
            break;
          file_size -= 2;
          do
          {
            pixel_data[i++] = value;
            block_length--;
          }
          while(--repeat != 0);
        }
        else
        {
          pixel_data[i++] = code;
          block_length--;
        }
      }
      GFX2_Log(GFX2_DEBUG, "  unpacked %d bytes. remaining bytes in file=%lu\n",
               i, file_size);
    }
    while(file_size > 0 && i < 16384);
  }
  fclose(file);

  if (i > 5)
  {
    win_width = pixel_data[i-4] + (pixel_data[i-3] << 8);  // in bits
    win_height = pixel_data[i-2];
    if (((win_width + 7) >> 3) * win_height + 5 == i) // that's a WIN file !
    {
      width = win_width >> (2 - mode);
      height = win_height;
      is_win = 1;
      columns = (win_width + 7) >> 3;
      GFX2_Log(GFX2_DEBUG, ".WIN file detected len=%d (%d,%d) %dcols %02X %02X %02X %02X %02X\n",
          i, width, height, columns,
          pixel_data[i-5], pixel_data[i-4], pixel_data[i-3],
          pixel_data[i-2], pixel_data[i-1]);
    }
    else
    {
      ///@todo guess the picture size, or ask the user
      GFX2_Log(GFX2_DEBUG, ".SCR file. Data length %d\n", i);
      // i <= 16384 && i >= 16336 => Standard resolution
      if (i <= 16384 && i >= 16336)
      {
        int j;
        // Standard resolution files have the 200 lines stored in block
        // of 25 lines of 80 bytes = 2000 bytes every 2048 bytes.
        // so there are 48 bytes unused every 2048 bytes...
        for (j = 0; j < i; j += 2048)
          GFX2_LogHexDump(GFX2_DEBUG, "SCR ", pixel_data, j+2000, 48);
      }
    }
  }

  Pre_load(context, width, height, real_file_size, FORMAT_SCR, ratio, bpp);

  for (y = 0; y < height; y++)
  {
    const byte * line;

    if (is_win)
      line = pixel_data + y * columns;
    else
      line = pixel_data + ((y & 7) << 11) + ((y >> 3) * columns);
    x = 0;
    for (i = 0; i < columns; i++)
    {
      byte pixels = line[i];
      switch (mode)
      {
        case 0:
          Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2);
          Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3);
          break;
        case 1:
          do {
            // upper nibble is 4 lower color bits, lower nibble is 4 upper color bits
            Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2);
            pixels <<= 1;
          }
          while ((x & 3) != 0);
          break;
        case 2:
          do {
            Set_pixel(context, x++, y, (pixels & 0x80) >> 7);
            pixels <<= 1;
          }
          while ((x & 7) != 0);
      }
    }
  }

  free(pixel_data);

  File_error = 0;
}

/**
 * Save Amstrad SCR file
 *
 * guess mode from aspect ratio :
 * - normal pixels are mode 1
 * - wide pixels are mode 0
 * - tall pixels are mode 2
 *
 * Mode and palette are stored in a .PAL file.
 *
 * The picture color index should be 0-15,
 * The CPC Hardware palette is expected to be set (indexes 64 to 95)
 *
 * @todo Add possibility to set R9, R12, R13 values
 * @todo Add OCP packing support
 * @todo Add possibility to include AMSDOS header, with proper loading
 *       address guessed from r12/r13 values.
 */
void Save_SCR(T_IO_Context * context)
{
  int i, j;
  unsigned char* output;
  unsigned long outsize = 0;
  unsigned char r1 = 0;
  int cpc_mode;
  FILE* file;


  switch(Pixel_ratio)
  {
    case PIXEL_WIDE:
    case PIXEL_WIDE2:
      cpc_mode = 0;
      break;
    case PIXEL_TALL:
    case PIXEL_TALL2:
    case PIXEL_TALL3:
      cpc_mode = 2;
      break;
    default:
      cpc_mode = 1;
      break;
  }

  file = Open_file_write_with_alternate_ext(context, "pal");
  if (file == NULL)
    return;
  if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0))
  {
    fclose(file);
    return;
  }
  for (i = 0; i < 16; i++)
  {
    // search for the color in the HW palette (0x40-0x5F)
    byte index = 0x40;
    while ((index < 0x60) &&
        !CPC_compare_colors(context->Palette + i, context->Palette + index))
      index++;
    if (index >= 0x60)
    {
      GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i);
      index = 0x54 - i; // default
    }
    for (j = 0; j < 12; j++)  // write the same color for the 12 frames
    {
      Write_byte(file, index);
    }
  }
  // border
  for (j = 0; j < 12; j++)
  {
    Write_byte(file, 0x54); // black
  }
  // excluded inks
  for (i = 0; i < 16; i++)
  {
    Write_byte(file, 0);
  }
  // protected inks
  for (i = 0; i < 16; i++)
  {
    Write_byte(file, 0);
  }
  fclose(file);

  output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0);
  GFX2_Log(GFX2_DEBUG, "Save_SCR() output=%p outsize=%lu r1=$%02X\n", output, outsize, r1);

  if (output == NULL)
    return;

  file = Open_file_write(context);
  if (file == NULL)
    File_error = 1;
  else
  {
    File_error = 0;
    if (!Write_bytes(file, output, outsize))
      File_error = 1;
    fclose(file);
  }
  free (output);
}

/**
 * Test for CM5 - Amstrad CPC "Mode 5" picture
 *
 * This is a format designed by SyX.
 * There is one .GFX file in the usual amstrad format
 * and a .CM5 file with the palette, which varies over time.
 *
 * CM5 file is 2049 bytes, GFX is 18432 bytes.
 *
 * @todo check CM5 contains only valid values [0x40-0x5f]
 */
void Test_CM5(T_IO_Context * context, FILE * file)
{
  // check cm5 file size == 2049 bytes
  FILE *file_gfx;
  long file_size;

  File_error = 1;

  file_size = File_length_file(file);
  if (file_size != 2049)
    return;

  // check existence of a .GFX file with the same name
  file_gfx = Open_file_read_with_alternate_ext(context, "gfx");
  if (file_gfx == NULL)
    return;
  file_size = File_length_file(file_gfx);
  fclose(file_gfx);
  if (file_size != 18432)
    return;

  File_error = 0;
}


/**
 * Load Amstrad CPC "Mode 5" picture
 *
 * Only support 288x256 resolution as the Mode 5 Viewer app only handles this
 * single resoltion.
 */
void Load_CM5(T_IO_Context* context)
{
  // Ensure "8bit" constraint mode is switched on
  // Set palette to the CPC hardware colors
  // Load the palette data to the 4 colorlayers
  FILE *file;
  byte value = 0;
  int mod=0;
  short line = 0;
  int tx, ty;
  // for preview :
  byte ink0;
  byte ink1[256];
  byte ink2[256];
  byte ink3[256*6];

  if (!(file = Open_file_read(context)))
  {
      File_error = 1;
      return;
  }

  Pre_load(context, 48*6, 256, 2049, FORMAT_CM5, PIXEL_SIMPLE, 0);

  if (Config.Clear_palette)
  {
    memset(context->Palette,0,sizeof(T_Palette));
    // setup colors 0,1,2,3 to see something in the thumbnail preview of layer 5
    context->Palette[1].R = 60;
    context->Palette[2].B = 60;
    context->Palette[3].G = 60;
  }

  // Setup the palette (amstrad hardware palette)
  CPC_set_HW_palette(context->Palette + 0x40);

  First_color_in_palette = 64;

  if (!Read_byte(file, &ink0))
    File_error = 2;

  // This forces the creation of 5 layers total :
  // Needed because the "pixel" functions will seek layer 4
  Set_loading_layer(context, 4);
  // Now select layer 1 again
  Set_loading_layer(context, 0);

  if (context->Type == CONTEXT_MAIN_IMAGE)
  {
    Set_image_mode(context, IMAGE_MODE_MODE5);

    // Fill layer with color we just read (Layer 1 - INK 0)
    for(ty=0; ty<context->Height; ty++)
      for(tx=0; tx<context->Width; tx++)
        Set_pixel(context, tx, ty, ink0);
  }

  while(Read_byte(file, &value))
  {
    switch(mod)
    {
      case 0:
        // This is color for layer 2 - INK 1
        Set_loading_layer(context, 1);
        for(tx=0; tx<context->Width; tx++)
          Set_pixel(context, tx, line, value);
        ink1[line] = value;
        break;
      case 1:
        // This is color for layer 3 - INK 2
        Set_loading_layer(context, 2);
        for(tx=0; tx<context->Width; tx++)
          Set_pixel(context, tx, line, value);
        ink2[line] = value;
        break;
      default:
        // This is color for a block in layer 4 - INK 3
        Set_loading_layer(context, 3);
        for(tx=(mod-2)*48; tx<(mod-1)*48; tx++)
          Set_pixel(context, tx, line, value);
        ink3[line*6+(mod-2)] = value;
        break;
    }
    mod++;
    if (mod > 7)
    {
      mod = 0;
      line++;
    }
  }

  fclose(file);

  // Load the pixeldata to the 5th layer
  file = Open_file_read_with_alternate_ext(context, "gfx");
  if (file == NULL)
  {
    File_error = 1;
    return;
  }
  Set_loading_layer(context, 4);

  if (context->Type == CONTEXT_PREVIEW)
    for (ty = 0; ty < 256; ty++)
      for (tx = 0; tx < 48*6; )
      {
        Read_byte(file, &value);
        for (mod = 0; mod < 4; mod++, tx++, value <<= 1)
        {
          switch(3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2)))  // INK
          {
            case 0:
              Set_pixel(context, tx, ty, ink0);
              break;
            case 1:
              Set_pixel(context, tx, ty, ink1[ty]);
              break;
            case 2:
              Set_pixel(context, tx, ty, ink2[ty]);
              break;
            default:
              Set_pixel(context, tx, ty, ink3[ty*6+(tx/48)]);
          }
        }
      }
  else
    for (ty = 0; ty < 256; ty++)
      for (tx = 0; tx < 48*6; )
      {
        Read_byte(file, &value);
        Set_pixel(context, tx++, ty, 3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2)));
        Set_pixel(context, tx++, ty, 3 ^ (((value&0x40) >> 6) | ((value&0x4)>>1)));
        Set_pixel(context, tx++, ty, 3 ^ (((value&0x20) >> 5) | ((value&0x2)>>0)));
        Set_pixel(context, tx++, ty, 3 ^ (((value&0x10) >> 4) | ((value&0x1)<<1)));
      }

  fclose(file);

}


void Save_CM5(T_IO_Context* context)
{
  FILE* file;
  int tx, ty;

  // TODO: Check picture has 5 layers
  // TODO: Check the constraints on the layers
  // Layer 1 : 1 color Only
  // Layer 2 and 3 : 1 color/line
  // Layer 4 : 1 color / 48x1 block
  // TODO: handle filesize

  if (!(file = Open_file_write(context)))
  {
    File_error = 1;
    return;
  }
  setvbuf(file, NULL, _IOFBF, 64*1024);

  // Write layer 0
  Set_saving_layer(context, 0);
  Write_byte(file, Get_pixel(context, 0, 0));
  for(ty = 0; ty < 256; ty++)
  {
    Set_saving_layer(context, 1);
    Write_byte(file, Get_pixel(context, 0, ty));
    Set_saving_layer(context, 2);
    Write_byte(file, Get_pixel(context, 0, ty));
    Set_saving_layer(context, 3);
    for(tx = 0; tx < 6; tx++)
    {
      Write_byte(file, Get_pixel(context, tx*48, ty));
    }
  }

  fclose(file);

  // Now the pixeldata
  if (!(file = Open_file_write_with_alternate_ext(context, "gfx")))
  {
    File_error = 2;
    return;
  }
  setvbuf(file, NULL, _IOFBF, 64*1024);

  Set_saving_layer(context, 4);

  for (ty = 0; ty < 256; ty++)
  {
    for (tx = 0; tx < 48*6; tx+=4)
    {
      byte code = 0;
      byte pixel;

      pixel = 3-Get_pixel(context, tx+3, ty);
      code |= (pixel&2)>>1 | ((pixel & 1)<<4);
      pixel = 3-Get_pixel(context, tx+2, ty);
      code |= ((pixel&2)<<0) | ((pixel & 1)<<5);
      pixel = 3-Get_pixel(context, tx+1, ty);
      code |= ((pixel&2)<<1) | ((pixel & 1)<<6);
      pixel = 3-Get_pixel(context, tx, ty);
      code |= ((pixel&2)<<2) | ((pixel & 1)<<7);
      Write_byte(file, code);
    }
  }

  fclose(file);
  File_error = 0;

}


/* Amstrad CPC 'PPH' for Perfect Pix.
// This is a format designed by Rhino.
// There are 3 modes:
// - Mode 'R': 1:1 pixels, 16 colors from the CPC 27 color palette.
//   (this is implemented on CPC as two pictures with wide pixels, the "odd" one
//   being shifted half a pixel to the right), and flipping)
// - Mode 'B0': wide pixels, up to 126 out of 378 colors.
//   (this is implemented as two pictures with wide pixels, sharing the same 16
//   color palette, and flipping)
// - Mode 'B1': 1:1 pixels, 1 fixed color, up to 34 palettes of 9 colors
//   (actually 4 colors + flipping)
//
// - The standard CPC formats can also be encapsulated into a PPH file.
//
// http://www.pouet.net/prod.php?which=67770#c766959
*/
void Test_PPH(T_IO_Context * context, FILE * file)
{
  FILE *file_oddeve;
  byte buffer[6];
  unsigned long file_size;
  unsigned int w, h;
  unsigned int expected;

  File_error = 1;

  // First check file size is large enough to hold the header
  file_size = File_length_file(file);
  if (file_size < 11) {
    File_error = 1;
    return;
  }

  // File is large enough for the header, now check if the data makes some sense
  if (!Read_bytes(file, buffer, 6))
    return;
  if (buffer[0] > 5) {
    // Unknown mode
    File_error = 2;
    return;
  }

  w = buffer[1] | (buffer[2] << 8);
  if (w < 2 || w > 384) {
    // Invalid width
    File_error = 3;
    return;
  }

  h = buffer[3] | (buffer[4] << 8);
  if (h < 1 || h > 272) {
    // Invalid height
    File_error = 4;
    return;
  }

  if (buffer[5] < 1 || buffer[5] > 28)
  {
    // Invalid palettes count
    File_error = 5;
    return;
  }
  expected = 6; // Size of header
  switch(buffer[0])
  {
    case 0:
    case 3:
    case 4:
      // Palette size should be 16 bytes, only 1 palette.
      if (buffer[5] != 1) {
        File_error = 7;
        return;
      }
      expected += 16;
      break;

    case 1:
    case 5:
      expected += buffer[5] * 5 - 1;
      break;

    case 2:
      // Palette size should be 2 bytes
      if (buffer[5] != 1) {
        File_error = 7;
        return;
      }
      expected += 2;
      break;
  }

  if (file_size != expected)
  {
    File_error = 6;
    return;
  }

  // check existence of .ODD/.EVE files with the same name
  // and the right size
  expected = w * h / 4;
  file_oddeve = Open_file_read_with_alternate_ext(context, "odd");
  if (file_oddeve == NULL)
    return;
  file_size = File_length_file(file_oddeve);
  fclose (file_oddeve);
  if (file_size != expected)
  {
    File_error = 8;
    return;
  }
  file_oddeve = Open_file_read_with_alternate_ext(context, "eve");
  if (file_oddeve == NULL)
    return;
  file_size = File_length_file(file_oddeve);
  fclose(file_oddeve);
  if (file_size != expected)
  {
    File_error = 8;
    return;
  }
  File_error = 0;
}


static uint8_t pph_blend(uint8_t a, uint8_t b)
{
	uint32_t h,l;
	if (a > b) { h = a; l = b; }
	else       { h = b; l = a; }

	return (23 * h + 9 * l) / 32;
}


void Load_PPH(T_IO_Context* context)
{
  FILE *file;
  FILE *feven;

  // Read in the header
  uint8_t mode;
  uint16_t width;
  uint16_t height;
  uint8_t npal;
  int i,j;
  uint8_t a,b,c,d;
  int file_size;
  uint8_t pl[16];

  static const T_Components CPCPAL[27] =
  {
      { 0x00, 0x02, 0x01 }, { 0x00, 0x02, 0x6B }, { 0x0C, 0x02, 0xF4 },
      { 0x6C, 0x02, 0x01 }, { 0x69, 0x02, 0x68 }, { 0x6C, 0x02, 0xF2 },
      { 0xF3, 0x05, 0x06 }, { 0xF0, 0x02, 0x68 }, { 0xF3, 0x02, 0xF4 },
      { 0x02, 0x78, 0x01 }, { 0x00, 0x78, 0x68 }, { 0x0C, 0x7B, 0xF4 },
      { 0x6E, 0x7B, 0x01 }, { 0x6E, 0x7D, 0x6B }, { 0x6E, 0x7B, 0xF6 },
      { 0xF3, 0x7D, 0x0D }, { 0xF3, 0x7D, 0x6B }, { 0xFA, 0x80, 0xF9 },
      { 0x02, 0xF0, 0x01 }, { 0x00, 0xF3, 0x6B }, { 0x0F, 0xF3, 0xF2 },
      { 0x71, 0xF5, 0x04 }, { 0x71, 0xF3, 0x6B }, { 0x71, 0xF3, 0xF4 },
      { 0xF3, 0xF3, 0x0D }, { 0xF3, 0xF3, 0x6D }, { 0xFF, 0xF3, 0xF9 }
  };

  if (!(file = Open_file_read(context)))
  {
      File_error = 1;
      return;
  }

  file_size=File_length_file(file);

  Read_byte(file, &mode);
  Read_word_le(file, &width);
  Read_word_le(file, &height);
  Read_byte(file, &npal);

  if (npal > 16)
      npal = 16;

  // Switch to the proper aspect ratio
  switch (mode)
  {
      case 0:
      case 4:
        context->Ratio = PIXEL_WIDE;
        width /= 2;
        break;

      case 2:
        context->Ratio = PIXEL_TALL;
        break;

      case 1:
      case 5:
      case 3:
        context->Ratio = PIXEL_SIMPLE;
        break;
  }

  Pre_load(context, width, height, file_size, FORMAT_PPH, context->Ratio, 0);

  context->Width = width;
  context->Height = height;

  // First of all, detect the mode
  // 0, 1, 2 > Load as with SCR files?
  // R(3)    > Load as single layer, square pixels, 16 colors
  // B0(4)   > Load as single layer, wide pixels, expand palette with colorcycling
  // B1(5)   > Load as ???
  //           Maybe special mode similar to mode5, with 2 layers + auto-flicker?

  switch (mode)
  {
      case 0:
      case 3: // R
          // 16-color palette
          for (i = 0; i < 16; i++)
          {
              uint8_t color;
              Read_byte(file, &color);
              context->Palette[i] = CPCPAL[color];
          }
          break;

      case 1:
      case 5: // B1
      {
          // Single or multiple 4-color palettes
          uint8_t base[4];
          for (j = 0; j < npal; j++)
          {
            for (i = 0; i < 4; i++)
            {
              Read_byte(file,&base[i]);
            }
            for (i = 0; i < 16; i++)
            {
              context->Palette[i + 16*j].R = pph_blend(
                  CPCPAL[base[i & 3]].R, CPCPAL[base[i >> 2]].R);
              context->Palette[i + 16*j].G = pph_blend(
                  CPCPAL[base[i & 3]].G, CPCPAL[base[i >> 2]].G);
              context->Palette[i + 16*j].B = pph_blend(
                  CPCPAL[base[i & 3]].B, CPCPAL[base[i >> 2]].B);
            }
            // TODO this byte marks where this palette stops being used and the
            // next starts. We must handle this!
            Read_byte(file,&pl[j]);
          }
          pl[npal - 1] = 255;
          break;
      }

      case 2:
          // Single 2-color palette
          break;

      case 4: // B0
      {
          // Single 16-color palette + flipping, need to expand palette and
          // setup colorcycling ranges.
          uint8_t base[16];
          for (i = 0; i < 16; i++)
          {
              Read_byte(file,&base[i]);
          }

          for (i = 0; i < 256; i++)
          {
              context->Palette[i].R = pph_blend(
                  CPCPAL[base[i & 15]].R, CPCPAL[base[i >> 4]].R);
              context->Palette[i].G = pph_blend(
                  CPCPAL[base[i & 15]].G, CPCPAL[base[i >> 4]].G);
              context->Palette[i].B = pph_blend(
                  CPCPAL[base[i & 15]].B, CPCPAL[base[i >> 4]].B);
          }
      }
      break;
  }

  fclose(file);

  // Load the picture data
  // There are two pages, each storing bytes in the CPC vram format but lines in
  // linear order.
  file = Open_file_read_with_alternate_ext(context, "odd");
  if (file == NULL)
  {
    File_error = 3;
    return;
  }
  feven = Open_file_read_with_alternate_ext(context, "eve");
  if (feven == NULL)
  {
    File_error = 4;
    fclose(file);
    return;
  }

  c = 0;
  d = 0;

  for (j = 0; j < height; j++)
  {
      for (i = 0; i < width;)
      {
          uint8_t even, odd;
          Read_byte(feven, &even);
          Read_byte(file, &odd);

          switch (mode)
          {
              case 4:
                a = ((even & 0x02) << 2) | ((even & 0x08) >> 2)
                  | ((even & 0x20) >> 3) | ((even & 0x80) >> 7);
                a <<= 4;
                a |= ((odd & 0x02) << 2) | (( odd & 0x08) >> 2)
                  | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7);

                b = ((even & 0x01) << 3) | ((even & 0x04) >> 1)
                  | ((even & 0x10) >> 2) | ((even & 0x40) >> 6);
                b <<= 4;
                b |= ((odd & 0x01) << 3) | (( odd & 0x04) >> 1)
                  | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6);

                Set_pixel(context, i++, j, a);
                Set_pixel(context, i++, j, b);
                break;

              case 3:
                a = ((even & 0x02) << 2) | ((even & 0x08) >> 2)
                  | ((even & 0x20) >> 3) | ((even & 0x80) >> 7);
                b = (( odd & 0x02) << 2) | (( odd & 0x08) >> 2)
                  | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7);
                c = ((even & 0x01) << 3) | ((even & 0x04) >> 1)
                  | ((even & 0x10) >> 2) | ((even & 0x40) >> 6);
                d = (( odd & 0x01) << 3) | (( odd & 0x04) >> 1)
                  | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6);
                Set_pixel(context, i++, j, j & 1 ? b : a);
                Set_pixel(context, i++, j, j & 1 ? a : b);
                Set_pixel(context, i++, j, j & 1 ? d : c);
                Set_pixel(context, i++, j, j & 1 ? c : d);
                break;

              case 5:
                if (d >= pl[c])
                {
                    d = 0;
                    c++;
                }
                a = ((even & 0x80) >> 6) | ((even & 0x08) >> 3);
                b = (( odd & 0x80) >> 6) | (( odd & 0x08) >> 3);
                Set_pixel(context, i++, j, a + (b << 2) + c * 16);
                a = ((even & 0x40) >> 5) | ((even & 0x04) >> 2);
                b = (( odd & 0x40) >> 5) | (( odd & 0x04) >> 2);
                Set_pixel(context, i++, j, a + (b << 2) + c * 16);
                a = ((even & 0x20) >> 4) | ((even & 0x02) >> 1);
                b = (( odd & 0x20) >> 4) | (( odd & 0x02) >> 1);
                Set_pixel(context, i++, j, a + (b << 2) + c * 16);
                a = ((even & 0x10) >> 3) | ((even & 0x01) >> 0);
                b = (( odd & 0x10) >> 3) | (( odd & 0x01) >> 0);
                Set_pixel(context, i++, j, a + (b << 2) + c * 16);

                break;

              default:
                File_error = 2;
                return;
          }

      }
      d++;
  }
  fclose(file);
  fclose(feven);

  File_error = 0;
}

void Save_PPH(T_IO_Context* context)
{
  (void)context; // unused
    // TODO

    // Detect mode
    // Wide pixels => B0 (4)
    // Square pixels:
    // - 16 colors used => R
    // - more colors used => B1 (if <16 colors per line)

    // Check palette
    // B0: use diagonal: 0, 17, 34, ... (assume the other are mixes)
    // R: use 16 used colors (or 16 first?)
    // B1: find the 16 colors used in a line? Or assume they are in-order already?
}


/////////////////////////////////// FLI/FLC /////////////////////////////////
typedef struct {
  dword size;          /* Size of FLIC including this header */
  word  type;          /* File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ... */
  word  frames;        /* Number of frames in first segment */
  word  width;         /* FLIC width in pixels */
  word  height;        /* FLIC height in pixels */
  word  depth;         /* Bits per pixel (usually 8) */
  word  flags;         /* Set to zero or to three */
  dword speed;         /* Delay between frames */
  word  reserved1;     /* Set to zero */
  dword created;       /* Date of FLIC creation (FLC only) */
  dword creator;       /* Serial number or compiler id (FLC only) */
  dword updated;       /* Date of FLIC update (FLC only) */
  dword updater;       /* Serial number (FLC only), see creator */
  word  aspect_dx;     /* Width of square rectangle (FLC only) */
  word  aspect_dy;     /* Height of square rectangle (FLC only) */
  word  ext_flags;     /* EGI: flags for specific EGI extensions */
  word  keyframes;     /* EGI: key-image frequency */
  word  totalframes;   /* EGI: total number of frames (segments) */
  dword req_memory;    /* EGI: maximum chunk size (uncompressed) */
  word  max_regions;   /* EGI: max. number of regions in a CHK_REGION chunk */
  word  transp_num;    /* EGI: number of transparent levels */
  byte  reserved2[24]; /* Set to zero */
  dword oframe1;       /* Offset to frame 1 (FLC only) */
  dword oframe2;       /* Offset to frame 2 (FLC only) */
  byte  reserved3[40]; /* Set to zero */
} T_FLIC_Header;

static void Load_FLI_Header(FILE * file, T_FLIC_Header * header)
{
  if (!(Read_dword_le(file,&header->size)
      && Read_word_le(file,&header->type)
      && Read_word_le(file,&header->frames)
      && Read_word_le(file,&header->width)
      && Read_word_le(file,&header->height)
      && Read_word_le(file,&header->depth)
      && Read_word_le(file,&header->flags)
      && Read_dword_le(file,&header->speed)
      && Read_word_le(file,&header->reserved1)
      && Read_dword_le(file,&header->created)
      && Read_dword_le(file,&header->creator)
      && Read_dword_le(file,&header->updated)
      && Read_dword_le(file,&header->updater)
      && Read_word_le(file,&header->aspect_dx)
      && Read_word_le(file,&header->aspect_dy)
      && Read_word_le(file,&header->ext_flags)
      && Read_word_le(file,&header->keyframes)
      && Read_word_le(file,&header->totalframes)
      && Read_dword_le(file,&header->req_memory)
      && Read_word_le(file,&header->max_regions)
      && Read_word_le(file,&header->transp_num)
      && Read_bytes(file,header->reserved2,24)
      && Read_dword_le(file,&header->oframe1)
      && Read_dword_le(file,&header->oframe2)
      && Read_bytes(file,header->reserved2,40) ))
  {
    File_error=1;
  }
}

/**
 * Test for the Autodesk Animator FLI/FLC format.
 *
 * Not to be confused with Commodore 64 FLI.
 */
void Test_FLI(T_IO_Context * context, FILE * file)
{
  T_FLIC_Header header;
  (void)context;

  File_error=0;
  Load_FLI_Header(file, &header);
  if (File_error != 0) return;

  switch (header.type)
  {
    case 0xAF11:  // standard FLI
    case 0xAF12:  // FLC (8bpp)
#if 0
    case 0xAF30:  // Huffman or BWT compression
    case 0xAF31:  // frame shift compression
    case 0xAF44:  // bpp != 8
#endif
      File_error=0;
      break;
    default:
      File_error=1;
  }
}

/**
 * Load file in the Autodesk Animator FLI/FLC format.
 *
 * Not to be confused with Commodore 64 FLI.
 */
void Load_FLI(T_IO_Context * context)
{
  FILE * file;
  unsigned long file_size;
  T_FLIC_Header header;
  dword chunk_size;
  word chunk_type;
  word sub_chunk_count, sub_chunk_index;
  dword sub_chunk_size;
  word sub_chunk_type;
  word frame_delay, frame_width, frame_height;
  int current_frame = 0;

  file = Open_file_read(context);
  if (file == NULL)
  {
    File_error=1;
    return;
  }
  File_error=0;
  file_size = File_length_file(file);
  Load_FLI_Header(file, &header);
  if (File_error != 0)
  {
    fclose(file);
    return;
  }
  if (header.size == 12)
  {
    // special "magic carpet" format
    header.depth = 8;
    header.speed = 66; // about 15fps
    fseek(file, 12, SEEK_SET);
  }
  else if (file_size != header.size)
    Warning("Load_FLI(): file size mismatch in header");

  if (header.speed == 0)
  {
    if (header.type == 0xAF11) // FLI
      header.speed = 1;   // 1/70th seconds
    else
      header.speed = 10;  // 10ms
  }

  while (File_error == 0
     && Read_dword_le(file,&chunk_size) && Read_word_le(file,&chunk_type))
  {
    chunk_size -= 6;
    switch (chunk_type)
    {
      case 0xf1fa:  // FRAME
        Read_word_le(file, &sub_chunk_count);
        Read_word_le(file, &frame_delay);
        fseek(file, 2, SEEK_CUR);
        Read_word_le(file, &frame_width);
        Read_word_le(file, &frame_height);
        if (frame_width == 0)
          frame_width = header.width;
        if (frame_height == 0)
          frame_height = header.height;
        if (frame_delay == 0)
          frame_delay = header.speed;
        chunk_size -= 10;

        if (current_frame == 0)
        {
          Pre_load(context, header.width,header.height,file_size,FORMAT_FLI,PIXEL_SIMPLE,header.depth);
          Set_image_mode(context, IMAGE_MODE_ANIMATION);
          if (Config.Clear_palette)
            memset(context->Palette,0,sizeof(T_Palette));
        }
        else
        {
          Set_loading_layer(context, current_frame);
          if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
          {
            // Copy the content of previous frame
            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);
          }
        }
        if (header.type == 0xAF11) // FLI
          Set_frame_duration(context, (frame_delay * 100) / 7); // 1/70th sec
        else
          Set_frame_duration(context, frame_delay); // msec
        current_frame++;

        for (sub_chunk_index = 0; sub_chunk_index < sub_chunk_count; sub_chunk_index++)
        {
          if (!(Read_dword_le(file,&sub_chunk_size) && Read_word_le(file,&sub_chunk_type)))
            File_error = 1;
          else
          {
            chunk_size -= sub_chunk_size;
            sub_chunk_size -= 6;
            if (sub_chunk_type == 0x04 || sub_chunk_type == 0x0b)   // color map
            {
              word packet_count;
              int i = 0;
              sub_chunk_size -= 2;
              if (!Read_word_le(file, &packet_count))
                File_error = 1;
              else
                while (packet_count-- > 0 && File_error == 0)
                {
                  byte skip, count;
                  if (!(Read_byte(file, &skip) && Read_byte(file, &count)))
                    File_error = 1;
                  else
                  {
                    sub_chunk_size -= 2;
                    i += skip;  // count 0 means 256
                    do
                    {
                      byte r, g, b;
                      if (!(Read_byte(file, &r) && Read_byte(file, &g) && Read_byte(file, &b)))
                      {
                        File_error = 1;
                        break;
                      }
                      if (sub_chunk_type == 0x0b || header.size == 12) // 6bit per color
                      {
                        r = (r << 2) | (r >> 4);
                        g = (g << 2) | (g >> 4);
                        b = (b << 2) | (b >> 4);
                      }
                      context->Palette[i].R = r;
                      context->Palette[i].G = g;
                      context->Palette[i].B = b;
                      i++;
                      sub_chunk_size -= 3;
                    } while (--count != 0);
                  }
                }
            }
            else if (sub_chunk_type == 0x0f)  // full frame RLE
            {
              word x, y;
              for (y = 0; y < frame_height && File_error == 0; y++)
              {
                byte count, data;
                Read_byte(file, &count); // packet count, but dont rely on it
                sub_chunk_size--;
                for (x = 0; x < frame_width; )
                {
                  if (!Read_byte(file, &count))
                  {
                    File_error = 1;
                    break;
                  }
                  sub_chunk_size--;
                  if ((count & 0x80) == 0)
                  {
                    if (!Read_byte(file, &data))  // repeat data count times
                    {
                      File_error = 1;
                      break;
                    }
                    sub_chunk_size--;
                    while (count-- > 0 && x < frame_width)
                      Set_pixel(context, x++, y, data);
                  }
                  else
                    while (count++ != 0 && x < frame_width)  // copy count bytes
                    {
                      if (!Read_byte(file, &data))
                      {
                        File_error = 1;
                        break;
                      }
                      Set_pixel(context, x++, y, data);
                      sub_chunk_size--;
                    }
                }
              }
              if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE)
              { // load only 1st frame in preview
                fclose(file);
                return;
              }
            }
            else if (sub_chunk_type == 0x0c)  // delta image, RLE
            {
              word x, y, line_count;

              Read_word_le(file, &y);
              Read_word_le(file, &line_count);
              sub_chunk_size -= 4;
              while (sub_chunk_size > 0 && line_count > 0 && File_error == 0)
              {
                byte packet_count;

                x = 0;
                if (!Read_byte(file, &packet_count))
                  File_error = 1;
                else
                {
                  sub_chunk_size--;
                  while (packet_count-- > 0 && File_error == 0)
                  {
                    byte skip, count, data;
                    if (!(Read_byte(file, &skip) && Read_byte(file, &count)))
                      File_error = 1;
                    else
                    {
                      sub_chunk_size -= 2;
                      x += skip;
                      if (count & 0x80)
                      {
                        Read_byte(file, &data);
                        sub_chunk_size--;
                        while (count++ != 0)
                          Set_pixel(context, x++, y, data);
                      }
                      else
                        while (count-- > 0)
                        {
                          Read_byte(file, &data);
                          sub_chunk_size--;
                          Set_pixel(context, x++, y, data);
                        }
                    }
                  }
                }
                y++;
                line_count--;
              }
            }
            else if (sub_chunk_type == 0x07)  // FLC delta image
            {
              word opcode, y, line_count;

              y = 0;
              Read_word_le(file, &line_count);
              sub_chunk_size -= 2;
              while (line_count > 0)
              {
                Read_word_le(file, &opcode);
                sub_chunk_size -= 2;
                if ((opcode & 0xc000) == 0x0000) // packet count
                {
                  word x = 0;
                  while (opcode-- > 0)
                  {
                    byte skip, count, data1, data2;
                    if (!(Read_byte(file, &skip) && Read_byte(file, &count)))
                      File_error = 1;
                    else
                    {
                      sub_chunk_size -= 2;
                      x += skip;
                      if (count & 0x80)
                      {
                        Read_byte(file, &data1);
                        Read_byte(file, &data2);
                        sub_chunk_size -= 2;
                        while (count++ != 0)
                        {
                          Set_pixel(context, x++, y, data1);
                          Set_pixel(context, x++, y, data2);
                        }
                      }
                      else
                        while (count-- > 0)
                        {
                          Read_byte(file, &data1);
                          Set_pixel(context, x++, y, data1);
                          Read_byte(file, &data2);
                          Set_pixel(context, x++, y, data2);
                          sub_chunk_size -= 2;
                        }
                    }
                  }
                  y++;
                  line_count--;
                }
                else if ((opcode & 0xc000) == 0xc000)  // line skip
                {
                  y -= opcode;
                }
                else if ((opcode & 0xc000) == 0x8000)  // last byte
                {
                  Set_pixel(context, frame_width - 1, y, opcode & 0xff);
                }
                else
                {
                  Warning("Unsupported opcode");
                  File_error = 2;
                  break;
                }
              }
            }
            if (sub_chunk_size > 0)
            {
              fseek(file, sub_chunk_size, SEEK_CUR);
            }
          }
        }
        break;
      default:  // skip
        Warning("Load_FLI(): unrecognized chunk");
    }
    if (chunk_size > 0 && header.size != 12)
    {
      fseek(file, chunk_size, SEEK_CUR);
    }
  }
  fclose(file);
}

/////////////////////////////// Thomson Files ///////////////////////////////

/**
 * Test for Thomson file
 */
void Test_MOTO(T_IO_Context * context, FILE * file)
{
  long file_size;

  file_size = File_length_file(file);

  File_error = 1;
  if (file_size <= 10)
    return;
  switch (MOTO_Check_binary_file(file))
  {
    case 0: // Not Thomson binary format
      switch (file_size)
      {
        // Files in RAW formats (from TGA2teo)
        case 8004:  // 2 colors palette
        case 8008:  // 4 colors palette
        case 8032:  // 16 colors palette
          {
            char filename[MAX_PATH_CHARACTERS];
            char path[MAX_PATH_CHARACTERS];
            char * ext;

            // Check there are both FORME and COULEUR files
            strncpy(filename, context->File_name, sizeof(filename));
            filename[sizeof(filename)-1] = '\0';
            ext = strrchr(filename, '.');
            if (ext == NULL || ext == filename)
              return;
            if ((ext[-1] | 32) == 'c')
              ext[-1] = (ext[-1] & 32) | 'P';
            else if ((ext[-1] | 32) == 'p')
              ext[-1] = (ext[-1] & 32) | 'C';
            else
              return;
            Get_full_filename(path, filename, context->File_directory);
            if (File_exists(path))
              File_error = 0;
          }
          return;
        default:
          break;
      }
      break;
    case 2: // MAP file (SAVEP/LOADP)
    case 3: // TO autoloading picture
    case 4: // MO autoloading picture
      File_error = 0;
      return;
  }
}

/**
 * Load a picture for Thomson TO8/TO8D/TO9/TO9+/MO6
 *
 * One of the supported format is the one produced by TGA2Teo :
 * - Picture data is splitted into 2 files, one for each VRAM bank :
 *   - The first VRAM bank is called "forme" (shape).
 *     In 40col mode it stores pixels.
 *   - The second VRAM bank is called "couleur" (color).
 *     In 40col mode it store color indexes for foreground and background.
 * - File extension is .BIN, character before extension is "P" for the first
 *   file, and "C" for the second.
 * - The color palette is stored in both files after the data.
 *
 * The mode is detected thanks to the number of color in the palette :
 * - 2 colors is 80col (640x200)
 * - 4 colors is bitmap4 (320x200 4 colors)
 * - 16 colors is either bitmap16 (160x200 16colors)
 *   or 40col (320x200 16 colors with 2 unique colors in each 8x1 pixels
 *   block).
 *
 * As it is not possible to disriminate bitmap16 and 40col, opening the "P"
 * file sets bitmap16, opening the "C" file sets 40col.
 *
 * This function also supports .MAP files (with optional TO-SNAP extension)
 * and our own "autoloading" BIN files.
 * See http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO for
 * a detailled description.
 */
void Load_MOTO(T_IO_Context * context)
{
  // FORME / COULEUR
  FILE * file;
  byte * vram_forme = NULL;
  byte * vram_couleur = NULL;
  long file_size;
  int file_type;
  int bx, x, y, i;
  byte bpp = 4;
  byte code;
  word length, address;
  int transpose = 1;  // transpose the upper bits of the color plane bytes
                      // FFFFBBBB becomes bfFFFBBB (for TO7 compatibility)
  enum MOTO_Graphic_Mode mode = MOTO_MODE_40col;
  enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
  int width = 320, height = 200, columns = 40;

  File_error = 1;
  file = Open_file_read(context);
  if (file == NULL)
    return;
  file_size = File_length_file(file);
  // Load default palette
  if (Config.Clear_palette)
    memset(context->Palette,0,sizeof(T_Palette));
  MOTO_set_TO7_palette(context->Palette);

  file_type = MOTO_Check_binary_file(file);
  if (fseek(file, 0, SEEK_SET) < 0)
  {
    fclose(file);
    return;
  }

  if (file_type == 2) // MAP file
  {
    // http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13
    byte map_mode, col_count, line_count;
    byte * vram_current;
    int end_marks;

    if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address)))
    {
      fclose(file);
      return;
    }
    if (length < 5 || !(Read_byte(file,&map_mode) && Read_byte(file,&col_count) && Read_byte(file,&line_count)))
    {
      fclose(file);
      return;
    }
    length -= 3;
    columns = col_count + 1;
    height = 8 * (line_count + 1);
    switch(map_mode)
    {
      default:
      case 0: // bitmap4 or 40col
        width = 8 * columns;
        mode = MOTO_MODE_40col; // default to 40col
        bpp = 4;
        break;
      case 0x40:  // bitmap16
        columns >>= 1;
        width = 4 * columns;
        mode = MOTO_MODE_bm16;
        bpp = 4;
        ratio = PIXEL_WIDE;
        break;
      case 0x80:  // 80col
        columns >>= 1;
        width = 16 * columns;
        mode = MOTO_MODE_80col;
        bpp = 1;
        ratio = PIXEL_TALL;
        break;
    }
    GFX2_Log(GFX2_DEBUG, "Map mode &H%02X row=%u line=%u (%dx%d) %d\n", map_mode, col_count, line_count, width, height, columns * height);
    vram_forme = malloc(columns * height);
    vram_couleur = malloc(columns * height);
    // Check extension (TO-SNAP / PPM / ???)
    if (length > 36)
    {
      long pos_backup;
      word data;

      pos_backup = ftell(file);
      fseek(file, length-2, SEEK_CUR);  // go to last word of chunk
      Read_word_be(file, &data);
      GFX2_Log(GFX2_DEBUG, "%04X\n", data);
      switch (data)
      {
      case 0xA55A:  // TO-SNAP
        fseek(file, -40, SEEK_CUR); // go to begin of extension
        Read_word_be(file, &data);  // SCRMOD. 0=>40col, 1=>bm4, $40=>bm16, $80=>80col
        GFX2_Log(GFX2_DEBUG, "SCRMOD=&H%04X ", data);
        Read_word_be(file, &data);  // Border color
        GFX2_Log(GFX2_DEBUG, "BORDER=%u ", data);
        Read_word_be(file, &data);  // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc.
        GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data);
        if(data == 2)
        {
          mode = MOTO_MODE_bm4;
          bpp = 2;
        }
        for (i = 0; i < 16; i++)
        {
          Read_word_be(file, &data);  // Palette entry
          if (data & 0x8000) data = ~data;
          MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data);
        }
        snprintf(context->Comment, sizeof(context->Comment), "TO-SNAP .MAP file");
        break;
      case 0x484C:  // 'HL' PPM
        fseek(file, -36, SEEK_CUR); // go to begin of extension
        for (i = 0; i < 16; i++)
        {
          Read_word_be(file, &data);  // Palette entry
          if (data & 0x8000) data = ~data;
          MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data);
        }
        Read_word_be(file, &data);  // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc.
        GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data);
        if(data == 2)
        {
          mode = MOTO_MODE_bm4;
          bpp = 2;
        }
        snprintf(context->Comment, sizeof(context->Comment), "PPM .MAP file");
        break;
      default:
        snprintf(context->Comment, sizeof(context->Comment), "standard .MAP file");
      }
      fseek(file, pos_backup, SEEK_SET);  // RESET Position
    }
    i = 0;
    vram_current = vram_forme;
    end_marks = 0;
    while (length > 1)
    {
      byte byte1, byte2;
      Read_byte(file,&byte1);
      Read_byte(file,&byte2);
      length-=2;
      if(byte1 == 0)
      {
        if (byte2 == 0)
        {
          // end of vram stream
          GFX2_Log(GFX2_DEBUG, "0000 i=%d length=%ld\n", i, length);
          if (end_marks == 1)
            break;
          i = 0;
          vram_current = vram_couleur;
          end_marks++;
        }
        else while(byte2-- > 0 && length > 0) // copy
        {
          Read_byte(file,vram_current + i);
          length--;
          i += columns; // to the next line
          if (i >= columns * height)
          {
            if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col)
              i -= (columns * height - 1);  // to the 1st line of the next column
            else
            {
              i -= columns * height;  // back to the 1st line of the current column
              if (vram_current == vram_forme)   // other VRAM
                vram_current = vram_couleur;
              else
              {
                vram_current = vram_forme;
                i++;  // next column
              }
            }
          }
        }
      }
      else while(byte1-- > 0) // run length
      {
        vram_current[i] = byte2;
        i += columns; // to the next line
        if (i >= columns * height)
        {
          if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col)
            i -= (columns * height - 1);  // to the 1st line of the next column
          else
          {
            i -= columns * height;  // back to the 1st line of the current column
            if (vram_current == vram_forme)   // other VRAM
              vram_current = vram_couleur;
            else
            {
              vram_current = vram_forme;
              i++;  // next column
            }
          }
        }
      }
    }
    fclose(file);
  }
  else if(file_type == 3 || file_type == 4)
  {
    if (file_type == 4) // MO file
    {
      transpose = 0;
      MOTO_set_MO5_palette(context->Palette);
    }

    do
    {
      if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address)))
      {
        if (vram_forme)
          break;
        fclose(file);
        return;
      }
      // MO5/MO6 VRAM address is &H0000
      // TO7/TO8/TO9 VRAM addres is &H4000
      if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0))
      {
        if (vram_forme == NULL)
        {
          vram_forme = calloc(8192, 1);
          Read_bytes(file, vram_forme, length);
          length = 0;
        }
        else if (vram_couleur == NULL)
        {
          vram_couleur = calloc(8192, 1);
          Read_bytes(file, vram_couleur, length);
          if (length >= 8032)
          {
            for (x = 0; x < 16; x++)
            {
              // 1 byte Blue (4 lower bits)
              // 1 byte Green (4 upper bits) / Red (4 lower bits)
              MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x],
                                             vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]);
            }
            if (length >= 8064)
            {
              memcpy(context->Comment, vram_couleur + 8032, 32);
              if (vram_couleur[8063] >= '0' && vram_couleur[8063] <= '3')
                mode = vram_couleur[8063] - '0';
            }
            context->Comment[COMMENT_SIZE] = '\0';
          }
          length = 0;
        }
      }
      if (length > 0)
        fseek(file, length, SEEK_CUR);
    } while(code == 0);
    fclose(file);
    switch (mode)
    {
      case MOTO_MODE_40col: // default
        break;
      case MOTO_MODE_bm4:
        bpp = 2;
        break;
      case MOTO_MODE_80col:
        bpp = 1;
        width = 640;
        ratio = PIXEL_TALL;
        break;
      case MOTO_MODE_bm16:
        width = 160;
        ratio = PIXEL_WIDE;
        break;
    }
  }
  else
  {
    char filename[MAX_PATH_CHARACTERS];
    char path[MAX_PATH_CHARACTERS];
    char * ext;
    int n_colors;

    vram_forme = malloc(file_size);
    if (vram_forme == NULL)
    {
      fclose(file);
      return;
    }
    if (!Read_bytes(file, vram_forme, file_size))
    {
      free(vram_forme);
      fclose(file);
      return;
    }
    n_colors = (file_size - 8000) / 2;
    switch(n_colors)
    {
      case 16:
        bpp = 4;
        // 16 colors : either 40col or bm16 mode !
        // select later
        break;
      case 4:
        bpp = 2;
        mode = MOTO_MODE_bm4;
        break;
      default:
        bpp = 1;
        mode = MOTO_MODE_80col;
        width = 640;
        ratio = PIXEL_TALL;
    }
    strncpy(filename, context->File_name, sizeof(filename));
    filename[sizeof(filename)-1] = '\0';
    ext = strrchr(filename, '.');
    if (ext == NULL || ext == filename)
    {
      free(vram_forme);
      return;
    }
    if ((ext[-1] | 32) == 'c')
    {
      vram_couleur = vram_forme;
      vram_forme = NULL;
      ext[-1] = (ext[-1] & 32) | 'P';
    }
    else if ((ext[-1] | 32) == 'p')
    {
      ext[-1] = (ext[-1] & 32) | 'C';
      if (n_colors == 16)
      {
        mode = MOTO_MODE_bm16;
        width = 160;
        ratio = PIXEL_WIDE;
      }
    }
    else
    {
      free(vram_forme);
      return;
    }
    Get_full_filename(path, filename, context->File_directory);
    file = fopen(path, "rb");
    if (vram_forme == NULL)
    {
      vram_forme = malloc(file_size);
      if (vram_forme == NULL)
      {
        free(vram_couleur);
        fclose(file);
        return;
      }
      Read_bytes(file,vram_forme,file_size);
    }
    else
    {
      vram_couleur = malloc(file_size);
      if (vram_couleur == NULL)
      {
        free(vram_forme);
        fclose(file);
        return;
      }
      Read_bytes(file,vram_couleur,file_size);
    }
    fclose(file);
    GFX2_Log(GFX2_DEBUG, "MO/TO: %s,%s file_size=%ld n_colors=%d\n", context->File_name, filename, file_size, n_colors);
    for (x = 0; x < n_colors; x++)
    {
      // 1 byte Blue (4 lower bits)
      // 1 byte Green (4 upper bits) / Red (4 lower bits)
      MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x],
                                     vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]);
    }
  }
  Pre_load(context, width, height, file_size, FORMAT_MOTO, ratio, bpp);
  if (mode == MOTO_MODE_40col)
    Set_image_mode(context, IMAGE_MODE_THOMSON);
  File_error = 0;
  i = 0;
  for (y = 0; y < height; y++)
  {
    for (bx = 0; bx < columns; bx++)
    {
      byte couleur_forme;
      byte couleur_fond;
      byte forme, couleurs;

      forme = vram_forme[i];
      if (vram_couleur)
        couleurs = vram_couleur[i];
      else
        couleurs = (mode == MOTO_MODE_40col) ? 0x01 : 0x00;
      i++;
      switch(mode)
      {
        case MOTO_MODE_bm4:
          for (x = bx*8; x < bx*8+8; x++)
          {
            Set_pixel(context, x, y, ((forme & 0x80) >> 6) | ((couleurs & 0x80) >> 7));
            forme <<= 1;
            couleurs <<= 1;
          }
#if 0     // the following would be for the alternate bm4 mode
          for (x = bx*8; x < bx*8+4; x++)
          {
            Set_pixel(context, x, y, couleurs >> 6);
            couleurs <<= 2;
          }
          for (x = bx*8 + 4; x < bx*8+8; x++)
          {
            Set_pixel(context, x, y, forme >> 6);
            forme <<= 2;
          }
#endif
          break;
        case MOTO_MODE_bm16:
          Set_pixel(context, bx*4, y, forme >> 4);
          Set_pixel(context, bx*4+1, y, forme & 0x0F);
          Set_pixel(context, bx*4+2, y, couleurs >> 4);
          Set_pixel(context, bx*4+3, y, couleurs & 0x0F);
          break;
        case MOTO_MODE_80col:
          for (x = bx*16; x < bx*16+8; x++)
          {
            Set_pixel(context, x, y, (forme & 0x80) >> 7);
            Set_pixel(context, x+8, y, (couleurs & 0x80) >> 7);
            forme <<= 1;
            couleurs <<= 1;
          }
          break;
        case MOTO_MODE_40col:
        default:
          if (transpose)
          {
            // the color plane byte is bfFFFBBB (for TO7 compatibility)
            // with the upper bits of both foreground (forme) and
            // background (fond) inverted.
            couleur_forme = ((couleurs & 0x78) >> 3) ^ 0x08;
            couleur_fond = ((couleurs & 7) | ((couleurs & 0x80) >> 4)) ^ 0x08;
          }
          else
          {
            // MO5 : the color plane byte is FFFFBBBB
            couleur_forme = couleurs >> 4;
            couleur_fond = couleurs & 0x0F;
          }
          for (x = bx*8; x < bx*8+8; x++)
          {
            Set_pixel(context, x, y, (forme & 0x80)?couleur_forme:couleur_fond);
            forme <<= 1;
          }
      }
    }
  }
  free(vram_forme);
  free(vram_couleur);
}

/**
 * Pack a stream of byte in the format used by Thomson MO/TO MAP files.
 *
 * - 00 cc xx yy .. : encodes a "copy run" (cc = bytes to copy)
 * - cc xx          : encodes a "repeat run" (cc > 0 : count)
 */
//#define MOTO_MAP_NOPACKING
static unsigned int MOTO_MAP_pack(byte * packed, const byte * unpacked, unsigned int unpacked_len)
{
  unsigned int src;
  unsigned int dst = 0;
  unsigned int count;
#ifndef MOTO_MAP_NOPACKING
  unsigned int repeat;
  unsigned int i;
  word * counts;
#endif

  GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack(%p, %p, %u)\n", packed, unpacked, unpacked_len);
  if (unpacked_len == 0)
    return 0;
  if (unpacked_len == 1)
  {
    packed[0] = 1;
    packed[1] = unpacked[0];
    return 2;
  }
#ifdef MOTO_MAP_NOPACKING
  // compression disabled
  src = 0;
  while ((unpacked_len - src) > 255)
  {
    packed[dst++] = 0;
    packed[dst++] = 255;
    memcpy(packed+dst, unpacked+src, 255);
    dst += 255;
    src += 255;
  }
  count = unpacked_len - src;
  packed[dst++] = 0;
  packed[dst++] = count;
  memcpy(packed+dst, unpacked+src, count);
  dst += count;
  src += count;
  return dst;
#else
  counts = malloc(sizeof(word) * (unpacked_len + 1));
  i = 0;
  repeat = (unpacked[0] == unpacked[1]);
  count = 2;
  src = 2;
  // 1st step : count lenght of the Copy runs and Repeat runs
  while (src < unpacked_len)
  {
    if (repeat)
    {
      if (unpacked[src-1] == unpacked[src])
        count++;
      else
      {
        // flush the repeat run
        counts[i++] = count | 0x8000; // 0x8000 is the marker for repeat runs
        count = 1;
        repeat = 0;
      }
    }
    else
    {
      if (unpacked[src-1] != unpacked[src])
        count++;
      else if (count == 1)
      {
        count++;
        repeat = 1;
      }
      else
      {
        // flush the copy run
        counts[i++] = (count-1) | (count == 2 ? 0x8000 : 0); // mark copy run of 1 as repeat of 1
        count = 2;
        repeat = 1;
      }
    }
    src++;
  }
  // flush the last run
  counts[i++] = ((repeat || count == 1) ? 0x8000 : 0) | count;
  counts[i++] = 0;  // end marker
  // check consistency of counts
  count = 0;
  for (i = 0; counts[i] != 0; i++)
    count += (counts[i] & ~0x8000);
  if (count != unpacked_len)
    GFX2_Log(GFX2_ERROR, "*** encoding error in MOTO_MAP_pack() *** count=%u unpacked_len=%u\n",
             count, unpacked_len);
  // output optimized packed stream
  // repeat run are encoded cc xx
  // copy run are encoded   00 cc xx xx xx xx
  i = 0;
  src = 0;
  while (counts[i] != 0)
  {
    while (counts[i] & 0x8000)  // repeat run
    {
      count = counts[i] & ~0x8000;
      GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u repeat %u times %02x\n", src, i, count, unpacked[src]);
      while(count > 255)
      {
        packed[dst++] = 255;
        packed[dst++] = unpacked[src];
        count -= 255;
        src += 255;
      }
      packed[dst++] = count;
      packed[dst++] = unpacked[src];
      src += count;
      i++;
    }
    while (counts[i] != 0 && !(counts[i] & 0x8000))  // copy run
    {
      // calculate the "savings" of repeat runs between 2 copy run
      int savings = 0;
      unsigned int j;
      GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u copy %u bytes\n", src, i, counts[i]);
      for (j = i + 1; counts[j] & 0x8000; j++) // check repeat runs until the next copy run
      {
        count = counts[j] & ~0x8000;
        if (savings < 0 && (savings + (int)count - 2) > 0)
          break;
        savings += count - 2; // a repeat run outputs 2 bytes for count bytes of input
      }
      count = counts[i];
GFX2_Log(GFX2_DEBUG, " savings=%d i=%u j=%u (counts[j]=0x%04x)\n", savings, i, j, counts[j]);
      if (savings < 2 && (j > i + 1))
      {
        unsigned int k;
        if (counts[j] == 0) // go to the end of stream
        {
          for (k = i + 1; k < j; k++)
            count += (counts[k] & ~0x8000);
          GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extend copy from %u to %u\n", src, counts[i], count);
          i = j - 1;
        }
        else
        {
          for (k = i + 1; k < j; k++)
            count += (counts[k] & ~0x8000);
          if (!(counts[j] & 0x8000))
          { // merge with the next copy run (and the repeat runs between)
            GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u merge savings=%d\n", src, savings);
            i = j;
            counts[i] += count;
            continue;
          }
          else
          { // merge with the next few repeat runs
            GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extends savings=%d\n", src, savings);
            i = j - 1;
          }
        }
      }
      while (count > 255)
      {
        packed[dst++] = 0;
        packed[dst++] = 255;
        memcpy(packed+dst, unpacked+src, 255);
        dst += 255;
        src += 255;
        count -= 255;
      }
      packed[dst++] = 0;
      packed[dst++] = count;
      memcpy(packed+dst, unpacked+src, count);
      dst += count;
      src += count;
      i++;
    }
  }
  free(counts);
  return dst;
#endif
}


/**
 * GUI window to choose Thomson MO/TO saving parameters
 *
 * @param[out] machine target machine
 * @param[out] format file format (0 = BIN, 1 = MAP)
 * @param[in,out] mode video mode @ref MOTO_Graphic_Mode
 */
static int Save_MOTO_window(enum MOTO_Machine_Type * machine, int * format, enum MOTO_Graphic_Mode * mode)
{
  int button;
  T_Dropdown_button * machine_dd;
  T_Dropdown_button * format_dd;
  T_Dropdown_button * mode_dd;
  static const char * mode_list[] = { "40col", "80col", "bm4", "bm16" };
  char text_info[24];

  Open_window(200, 125, "Thomson MO/TO Saving");
  Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1
  Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2

  Print_in_window(13,18,"Target Machine:",MC_Dark,MC_Light);
  machine_dd = Window_set_dropdown_button(10,28,110,15,100,
                                          (*mode == MOTO_MODE_40col) ? "TO7/TO7-70" : "TO9/TO8/TO9+",
                                          1, 0, 1, LEFT_SIDE,0); // 3
  if (*mode == MOTO_MODE_40col)
    Window_dropdown_add_item(machine_dd, MACHINE_TO7, "TO7/TO7-70");
  Window_dropdown_add_item(machine_dd, MACHINE_TO8, "TO9/TO8/TO9+");
  if (*mode == MOTO_MODE_40col)
    Window_dropdown_add_item(machine_dd, MACHINE_MO5, "MO5");
  Window_dropdown_add_item(machine_dd, MACHINE_MO6, "MO6");

  Print_in_window(13,46,"Format:",MC_Dark,MC_Light);
  format_dd = Window_set_dropdown_button(10,56,110,15,92,"BIN",1, 0, 1, LEFT_SIDE,0); // 4
  Window_dropdown_add_item(format_dd, 0, "BIN");
  Window_dropdown_add_item(format_dd, 1, "MAP/TO-SNAP");

  Print_in_window(136,46,"Mode:",MC_Dark,MC_Light);
  mode_dd = Window_set_dropdown_button(136,56,54,15,44,mode_list[*mode],1, 0, 1, LEFT_SIDE,0); // 5
  if (*mode == MOTO_MODE_40col)
    Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
  if (*mode == MOTO_MODE_80col)
    Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
  if (*mode == MOTO_MODE_40col)
    Window_dropdown_add_item(mode_dd, MOTO_MODE_bm4, mode_list[MOTO_MODE_bm4]);
  if (*mode == MOTO_MODE_bm16)
    Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);

  Update_window_area(0,0,Window_width,Window_height);
  Display_cursor();
  do
  {
    button = Window_clicked_button();
    if (Is_shortcut(Key, 0x100+BUTTON_HELP))
    {
      Key = 0;
      Window_help(BUTTON_SAVE, "THOMSON MO/TO FORMAT");
    }
    else switch (button)
    {
      case 3:
        *machine = (enum MOTO_Machine_Type)Window_attribute2;
        break;
      case 4:
        *format = Window_attribute2;
        break;
      case 5:
        *mode = (enum MOTO_Graphic_Mode)Window_attribute2;
        break;
    }
    Hide_cursor();
    //"ABCDEFGHIJKLMNOPQRSTUVW"
    memset(text_info, ' ', 23);
    text_info[23] = '\0';
    if (*machine == MACHINE_TO7 || *machine == MACHINE_TO770 || *machine == MACHINE_MO5)
    {
      if (*mode != MOTO_MODE_40col)
        snprintf(text_info, sizeof(text_info), "%s only supports 40col",
                 (*machine == MACHINE_MO5) ? "MO5" : "TO7");
      else if (*format == 1)
        strncpy(text_info, "No TO-SNAP extension.  ", sizeof(text_info));
      else
        strncpy(text_info, "No palette to save.    ", sizeof(text_info));
    }
    Print_in_window(9, 80, text_info, MC_Dark, MC_Light);
    Display_cursor();
  } while(button!=1 && button!=2);

  Close_window();
  Display_cursor();
  return button==1;
}

/**
 * Save a picture in MAP or BIN Thomson MO/TO file format.
 *
 * File format details :
 * http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO
 */
void Save_MOTO(T_IO_Context * context)
{
  int transpose = 1;  // transpose upper bits in "couleur" vram
  enum MOTO_Machine_Type target_machine = MACHINE_TO7;
  int format = 0; // 0 = BIN, 1 = MAP
  enum MOTO_Graphic_Mode mode;
  FILE * file = NULL;
  byte * vram_forme;
  byte * vram_couleur;
  int i, x, y, bx;
  word reg_prc = 0xE7C3; // PRC : TO7/8/9 0xE7C3 ; MO5/MO6 0xA7C0
  byte prc_value = 0x65;// Value to write to PRC to select VRAM bank
  // MO5 : 0x51
  word vram_address = 0x4000; // 4000 on TO7/TO8/TO9, 0000 on MO5/MO6

  File_error = 1;

  /**
   * In the future we could support other resolution for .MAP
   * format.
   * And even in .BIN format, we could store less lines. */
  if (context->Height != 200)
  {
    Warning_message("must be 640x200, 320x200 or 160x200");
    return;
  }

  switch (context->Width)
  {
    case 160:
      mode = MOTO_MODE_bm16;
      target_machine = MACHINE_TO8;
      break;
    case 640:
      mode = MOTO_MODE_80col;
      target_machine = MACHINE_TO8;
      break;
    case 320:
      mode = MOTO_MODE_40col; // or bm4
      break;
    default:
      Warning_message("must be 640x200, 320x200 or 160x200");
      return;
  }

  if (!Save_MOTO_window(&target_machine, &format, &mode))
    return;

  if (target_machine == MACHINE_MO5 || target_machine == MACHINE_MO6)
  {
    reg_prc = 0xA7C0; // PRC : MO5/MO6 0xA7C0
    prc_value = 0x51;
    vram_address = 0;
    transpose = 0;
  }

  vram_forme = malloc(8192);
  vram_couleur = malloc(8192);
  switch (mode)
  {
  case MOTO_MODE_40col:
    {
      /**
       * The 40col encoding algorithm is optimized for further vertical
       * RLE packing. The "attibute" byte is kept as constant as possible
       * between adjacent blocks.
       */
      unsigned color_freq[16];
      unsigned max_freq = 0;
      byte previous_fond = 0, previous_forme = 0;
      byte most_used_color = 0;

      // search for most used color to prefer it as background color
      for (i = 0; i < 16; i++)
        color_freq[i] = 0;
      for (y = 0; y < context->Height; y++)
      {
        for (x = 0; x < context->Width; x++)
        {
          byte col = Get_pixel(context, x, y);
          if (col > 15)
          {
            Warning_with_format("color %u > 15 at pixel (%d,%d)", col, x, y);
            goto error;
          }
          color_freq[col]++;
        }
      }
      for (i = 0; i < 16; i++)
      {
        if (color_freq[i] > max_freq)
        {
          max_freq = color_freq[i];
          most_used_color = (byte)i;  // most used color
        }
      }
      previous_fond = most_used_color;
      max_freq = 0;
      for (i = 0; i < 16; i++)
      {
        if (i != most_used_color && color_freq[i] > max_freq)
        {
          max_freq = color_freq[i];
          previous_forme = (byte)i;  // second most used color
        }
      }
      GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme);

      if (target_machine == MACHINE_MO5)
      {
        /**
         * For MO5 we use a different 40col algorithm
         * to make sure the last pixel of a GPL and the first the next
         * are both FORME or both FOND, else we get an ugly glitch on the
         * EFGJ033 Gate Array MO5!
         */
        byte forme_byte = 0;
        byte couleur_byte = 0x10;
        GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using MO5 algo\n");
        for (y = 0; y < context->Height; y++)
        {
          for (bx = 0; bx < 40; bx++)
          {
            byte fond = 0xff, forme = 0xff;
            forme_byte &= 1;  // Last bit of the previous FORME byte
            x = bx*8;
            if (forme_byte)
              forme = Get_pixel(context, x, y);
            else
              fond = Get_pixel(context, x, y);
            while (++x < bx * 8 + 8)
            {
              byte col = Get_pixel(context, x, y);
              forme_byte <<= 1;
              if (col == forme)
                forme_byte |= 1;
              else if (col != fond)
              {
                if (forme == 0xff)
                {
                  forme_byte |= 1;
                  forme = col;
                }
                else if (fond == 0xff)
                  fond = col;
                else
                {
                  Warning_with_format("Constraint error at pixel (%d,%d)", x, y);
                  goto error;
                }
              }
            }
            if (forme != 0xff)
              couleur_byte = (forme << 4) | (couleur_byte & 0x0f);
            if (fond != 0xff)
              couleur_byte = (couleur_byte & 0xf0) | fond;
            vram_forme[bx+y*40] = forme_byte;
            vram_couleur[bx+y*40] = couleur_byte;
          }
        }
      }
      else
      {
        GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using optimized algo\n");
        // encoding of each 8x1 block
        for (bx = 0; bx < 40; bx++)
        {
          for (y = 0; y < context->Height; y++)
          {
            byte forme_byte = 1;
            byte col;
            byte c1, c1_count = 1;
            byte c2 = 0xff, c2_count = 0;
            byte fond, forme;
            x = bx * 8;
            c1 = Get_pixel(context, x, y);
            while (++x < bx * 8 + 8)
            {
              forme_byte <<= 1;
              col = Get_pixel(context, x, y);
              if (col > 15)
              {
                Warning_with_format("color %d > 15 at pixel (%d,%d)", col, x, y);
                goto error;
              }
              if (col == c1)
              {
                forme_byte |= 1;
                c1_count++;
              }
              else
              {
                c2_count++;
                if (c2 == 0xff)
                  c2 = col;
                else if (col != c2)
                {
                  Warning_with_format("constraint error at pixel (%d,%d)", x, y);
                  goto error;
                }
              }
            }
            if (c2 == 0xff)
            {
              // Only one color in the 8x1 block
              if (c1 == previous_fond)
                c2 = previous_forme;
              else
                c2 = previous_fond;
            }
            // select background color (fond)
            // and foreground color (forme)
            if (c1 == previous_fond)
            {
              fond = c1;
              forme = c2;
              forme_byte = ~forme_byte;
            }
            else if (c2 == previous_fond)
            {
              fond = c2;
              forme = c1;
            }
            else if (c1 == most_used_color)
            {
              fond = c1;
              forme = c2;
              forme_byte = ~forme_byte;
            }
            else if (c2 == most_used_color)
            {
              fond = c2;
              forme = c1;
            }
            else if (c1_count >= c2_count)
            {
              fond = c1;
              forme = c2;
              forme_byte = ~forme_byte;
            }
            else
            {
              fond = c2;
              forme = c1;
            }
            // write to VRAM
            vram_forme[bx+y*40] = forme_byte;
            // transpose for TO7 compatibility
            if (transpose)
              vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0;
            else
              vram_couleur[bx+y*40] = fond | (forme << 4);
            previous_fond = fond;
            previous_forme = forme;
          }
          if (transpose)
          {
            previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4;
            previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8;
          }
          else
          {
            previous_fond = vram_couleur[bx] & 15;
            previous_forme = vram_couleur[bx] >> 4;
          }
        }
      }
    }
    break;
  case MOTO_MODE_80col:
    for (bx = 0; bx < context->Width / 16; bx++)
    {
      for (y = 0; y < context->Height; y++)
      {
        byte val = 0;
        for (x = bx * 16; x < bx*16 + 8; x++)
          val = (val << 1) | Get_pixel(context, x, y);
        vram_forme[y*(context->Width/16)+bx] = val;
        for (; x < bx*16 + 16; x++)
          val = (val << 1) | Get_pixel(context, x, y);
        vram_couleur[y*(context->Width/16)+bx] = val;
      }
    }
    break;
  case MOTO_MODE_bm4:
    for (y = 0; y < context->Height; y++)
    {
      for (bx = 0; bx < context->Width / 8; bx++)
      {
        byte val1 = 0, val2 = 0, pixel;
        for (x = bx * 8; x < bx*8 + 8; x++)
        {
          pixel = Get_pixel(context, x, y);
          if (pixel > 3)
          {
            Warning_with_format("color %d > 3 at pixel (%d,%d)", pixel, x, y);
            goto error;
          }
          val1 = (val1 << 1) | (pixel >> 1);
          val2 = (val2 << 1) | (pixel & 1);
        }
        vram_forme[y*(context->Width/8)+bx] = val1;
        vram_couleur[y*(context->Width/8)+bx] = val2;
      }
    }
    break;
  case MOTO_MODE_bm16:
    for (bx = 0; bx < context->Width / 4; bx++)
    {
      for (y = 0; y < context->Height; y++)
      {
         vram_forme[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4, y) << 4) | Get_pixel(context, bx*4+1, y);
         vram_couleur[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4+2, y) << 4) | Get_pixel(context, bx*4+3, y);
      }
    }
    break;
  }
  // palette
  for (i = 0; i < 16; i++)
  {
    word to8color = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + i);
    vram_forme[8000+i*2] = to8color >> 8;
    vram_forme[8000+i*2+1] = to8color & 0xFF;
  }

  file = Open_file_write(context);
  if (file == NULL)
    goto error;

  if (format == 0)  // BIN
  {
    word chunk_length;

    if (target_machine == MACHINE_TO7 || target_machine == MACHINE_TO770 || target_machine == MACHINE_MO5)
      chunk_length = 8000;  // Do not save palette
    else
    {
      chunk_length = 8000 + 32 + 32;  // data + palette + comment
      // Commentaire
      if (context->Comment[0] != '\0')
        strncpy((char *)vram_forme + 8032, context->Comment, 32);
      else
        snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision);
      // also saves the video mode
      vram_forme[8063] = '0' + mode;
      memcpy(vram_couleur + 8000, vram_forme + 8000, 64);
    }

    // Format BIN
    // TO8/TO9 : set LGAMOD 0xE7DC  40col=0 bm4=0x21 80col=0x2a bm16=0x7b
    if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value))
      goto error;
    if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_forme))
      goto error;
    prc_value &= 0xFE; // select color data
    if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value))
      goto error;
    if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_couleur))
      goto error;
    if (!DECB_BIN_Add_End(file, 0x0000))
      goto error;
  }
  else
  {
    // format MAP with TO-SNAP extensions
    byte * unpacked_data;
    byte * packed_data;

    unpacked_data = malloc(16*1024);
    packed_data = malloc(16*1024);
    if (packed_data == NULL || unpacked_data == NULL)
    {
      GFX2_Log(GFX2_ERROR, "Failed to allocate 2x16kB of memory\n");
      free(packed_data);
      free(unpacked_data);
      goto error;
    }
    switch (mode)
    {
      case MOTO_MODE_40col:
      case MOTO_MODE_bm4:
        packed_data[0] = 0;  // mode
        packed_data[1] = (context->Width / 8) - 1;
        break;
      case MOTO_MODE_80col:
        packed_data[0] = 0x80; // mode
        packed_data[1] = (context->Width / 8) - 1;
        break;
      case MOTO_MODE_bm16:
        packed_data[0] = 0x40; // mode
        packed_data[1] = (context->Width / 2) - 1;
        break;
    }
    packed_data[2] = (context->Height / 8) - 1;
    // 1st step : put data to pack in a linear buffer
    // 2nd step : pack data
    i = 0;
    switch (mode)
    {
      case MOTO_MODE_40col:
      case MOTO_MODE_bm4:
        for (bx = 0; bx <= packed_data[1]; bx++)
        {
          for (y = 0; y < context->Height; y++)
          {
            unpacked_data[i] = vram_forme[bx + y*(packed_data[1]+1)];
            unpacked_data[i+8192] = vram_couleur[bx + y*(packed_data[1]+1)];
            i++;
          }
        }
        i = 3;
        i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1));
        packed_data[i++] = 0; // ending of VRAM forme packing
        packed_data[i++] = 0;
        i += MOTO_MAP_pack(packed_data+i, unpacked_data + 8192, context->Height * (packed_data[1]+1));
        packed_data[i++] = 0; // ending of VRAM couleur packing
        packed_data[i++] = 0;
        break;
      case MOTO_MODE_80col:
      case MOTO_MODE_bm16:
        for (bx = 0; bx < (packed_data[1] + 1) / 2; bx++)
        {
          for (y = 0; y < context->Height; y++)
            unpacked_data[i++] = vram_forme[bx + y*(packed_data[1]+1)/2];
          for (y = 0; y < context->Height; y++)
            unpacked_data[i++] = vram_couleur[bx + y*(packed_data[1]+1)/2];
        }
        i = 3;
        i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1));
        packed_data[i++] = 0; // ending of VRAM forme packing
        packed_data[i++] = 0;
        packed_data[i++] = 0; // ending of VRAM couleur packing
        packed_data[i++] = 0;
        break;
    }
    if (i&1)  // align
      packed_data[i++] = 0;
    if (target_machine != MACHINE_TO7 && target_machine != MACHINE_TO770 && target_machine != MACHINE_MO5)
    {
      // add TO-SNAP extension
      // see http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13
      // bytes 0-1 : Hardware video mode (value of SCRMOD 0x605F)
      packed_data[i++] = 0;
      switch (mode)
      {
        case MOTO_MODE_40col:
          packed_data[i++] = 0;
          break;
        case MOTO_MODE_bm4:
          packed_data[i++] = 0x01;
          break;
        case MOTO_MODE_80col:
          packed_data[i++] = 0x80;
          break;
        case MOTO_MODE_bm16:
          packed_data[i++] = 0x40;
          break;
      }
      // bytes 2-3 : Border color
      packed_data[i++] = 0;
      packed_data[i++] = 0;
      // bytes 4-5 : BASIC video mode (CONSOLE,,,,X)
      packed_data[i++] = 0;
      switch (mode)
      {
        case MOTO_MODE_40col:
          packed_data[i++] = 0;
          break;
        case MOTO_MODE_bm4:
          packed_data[i++] = 2;
          break;
        case MOTO_MODE_80col:
          packed_data[i++] = 1;
          break;
        case MOTO_MODE_bm16:
          packed_data[i++] = 3;
          break;
      }
      // bytes 6-37 : BGR palette
      for (x = 0; x < 16; x++)
      {
        word bgr = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + x);
        packed_data[i++] = bgr >> 8;
        packed_data[i++] = bgr & 0xff;
      }
      // bytes 38-39 : TO-SNAP signature
      packed_data[i++] = 0xA5;
      packed_data[i++] = 0x5A;
    }

    free(unpacked_data);

    if (!DECB_BIN_Add_Chunk(file, i, 0, packed_data) ||
        !DECB_BIN_Add_End(file, 0x0000))
    {
      free(packed_data);
      goto error;
    }
    free(packed_data);
  }
  fclose(file);
  File_error = 0;
  return;

error:
  free(vram_forme);
  free(vram_couleur);
  if (file)
    fclose(file);
  File_error = 1;
}

/////////////////////////////// Apple II Files //////////////////////////////

/**
 * Test for an Apple II HGR or DHGR raw file
 */
void Test_HGR(T_IO_Context * context, FILE * file)
{
  long file_size;

  (void)context;
  File_error = 1;

  file_size = File_length_file(file);
  if (file_size == 8192)  // HGR
    File_error = 0;
  else if(file_size == 16384) // DHGR
    File_error = 0;
}

/**
 * Load HGR (280x192) or DHGR (560x192) Apple II pictures
 *
 * Creates 2 layers :
 * 1. Monochrome
 * 2. Color
 */
void Load_HGR(T_IO_Context * context)
{
  unsigned long file_size;
  FILE * file;
  byte * vram[2];
  int bank;
  int x, y;
  int is_dhgr = 0;

  file = Open_file_read(context);
  if (file == NULL)
  {
    File_error = 1;
    return;
  }
  file_size = File_length_file(file);
  if (file_size == 16384)
    is_dhgr = 1;

  vram[0] = malloc(8192);
  Read_bytes(file, vram[0], 8192);
  if (is_dhgr)
  {
    vram[1] = malloc(8192);
    Read_bytes(file, vram[1], 8192);
  }
  else
    vram[1] = NULL;
  fclose(file);

  if (Config.Clear_palette)
    memset(context->Palette,0,sizeof(T_Palette));
  if (is_dhgr)
  {
    DHGR_set_palette(context->Palette);
    Pre_load(context, 560, 192, file_size, FORMAT_HGR, PIXEL_TALL, 4);
  }
  else
  {
    HGR_set_palette(context->Palette);
    Pre_load(context, 280, 192, file_size, FORMAT_HGR, PIXEL_SIMPLE, 2);
  }
  for (y = 0; y < 192; y++)
  {
    byte palette = 0, color = 0;
    byte previous_palette = 0;  // palette for the previous pixel pair
    int column, i;
    int offset = ((y & 7) << 10) + ((y & 070) << 4) + ((y >> 6) * 40);
    x = 0;
    for (column = 0; column < 40; column++)
    for (bank = 0; bank <= is_dhgr; bank++)
    {
      byte b = vram[bank][offset+column];
      if (!is_dhgr)
        palette = (b & 0x80) ? 4 : 0;
      else
        palette = (b & 0x80) ? 0 : 16;
      for (i = 0; i < 7; i++)
      {
        if (context->Type == CONTEXT_MAIN_IMAGE)
        {
          // monochrome
          Set_loading_layer(context, 0);
          Set_pixel(context, x, y, ((b & 1) * (is_dhgr ? 15 : 3)) + palette);
          Set_loading_layer(context, 1);
        }
        // color
        color = (color << 1) | (b & 1);
        if (is_dhgr)
        {
          if ((x & 3) == 0)
            previous_palette = palette; // what is important is the value when the 1st bit was read...
          /// emulate "chat mauve" DHGR mixed mode.
          /// see http://boutillon.free.fr/Underground/Anim_Et_Graph/Extasie_Chat_Mauve_Reloaded/Extasie_Chat_Mauve_Reloaded.html
          if (previous_palette) // BW
            Set_pixel(context, x, y, ((b & 1) * 15) + palette);
          else if ((x & 3) == 3)
          {
            Set_pixel(context, x - 3, y, (color & 15) + palette);
            Set_pixel(context, x - 2, y, (color & 15) + palette);
            Set_pixel(context, x - 1, y, (color & 15) + palette);
            Set_pixel(context, x, y, (color & 15) + palette);
          }
        }
        else
        {
          /// HGR emulation following the behaviour of a "Le Chat Mauve"
          /// RVB adapter for the Apple //c.
          /// Within the bit stream, the color of the middle pixel is :<br>
          /// <tt>
          /// 111 \          <br>
          /// 110  }- white  <br>
          /// 011 /          <br>
          /// 010 \ _ color  <br>
          /// 101 /          <br>
          /// 000 \          <br>
          /// 001  }- black  <br>
          /// 100 /          <br>
          /// </tt>
          /// Color depends on the selected palette for the current byte
          /// and the position of the pixel (odd or even).
          if ((color & 3) == 3) // 11 => white
          {
            Set_pixel(context, x - 1, y, 3 + previous_palette);
            Set_pixel(context, x, y, 3 + palette);
          }
          else if ((color & 1) == 0) // 0 => black
            Set_pixel(context, x, y, palette);
          else // 01 => color
          {
            if ((color & 7) == 5) // 101
              Set_pixel(context, x - 1, y, 2 - (x & 1) + previous_palette);
            Set_pixel(context, x, y, 2 - (x & 1) + palette);
          }
          previous_palette = palette;
        }
        b >>= 1;
        x++;
      }
    }
  }
  // show hidden data in HOLES
  for (y = 0; y < 64; y++)
  for (bank = 0; bank < 1; bank++)
  {
    byte b = 0;
    for (x = 0; x < 8; x++)
      b |= vram[bank][x + (y << 7) + 120];
    if (b != 0)
      GFX2_LogHexDump(GFX2_DEBUG, bank ? "AUX " : "MAIN", vram[bank], (y << 7) + 120, 8);
  }
  free(vram[0]);
  free(vram[1]);
  File_error = 0;

  Set_image_mode(context, is_dhgr ? IMAGE_MODE_DHGR : IMAGE_MODE_HGR);
}

/**
 * Save HGR (280x192) or DHGR (560x192) Apple II pictures
 *
 * The data saved is the "monochrome" data from layer 1
 */
void Save_HGR(T_IO_Context * context)
{
  FILE * file;
  byte * vram[2];
  int bank;
  int x, y;
  int is_dhgr = 0;

  File_error = 1;
  if (context->Height != 192 || (context->Width != 280 && context->Width != 560))
  {
    Warning_message("Picture must be 280x192 (HGR) or 560x192 (DHGR)");
    return;
  }
  if (context->Width == 560)
    is_dhgr = 1;
  
  file = Open_file_write(context);
  if (file == NULL)
    return;
  vram[0] = calloc(8192, 1);
  if (vram[0] == NULL)
  {
    fclose(file);
    return;
  }
  if (is_dhgr)
  {
    vram[1] = calloc(8192, 1);
    if (vram[1] == NULL)
    {
      free(vram[0]);
      fclose(file);
      return;
    }
  }
  else
    vram[1] = NULL;

  Set_saving_layer(context, 0); // "monochrome" layer
  for (y = 0; y < 192; y++)
  {
    int i, column = 0;
    int offset = ((y & 7) << 10) + ((y & 070) << 4) + ((y >> 6) * 40);
    x = 0;
    bank = 0;
    while (x < context->Width)
    {
      byte b;
      if (is_dhgr)
        b = (Get_pixel(context, x, y) & 16) ? 0 : 0x80;
      else
        b = (Get_pixel(context, x, y) & 4) ? 0x80 : 0;
      for (i = 0; i < 7; i++)
      {
        b = b | ((Get_pixel(context, x++, y) & 1) << i);
      }
      vram[bank][offset + column] = b;
      if (is_dhgr)
      {
        if (++bank > 1)
        {
          bank = 0;
          column++;
        }
      }
      else
        column++;
    }
  }

  if (Write_bytes(file, vram[0], 8192))
  {
    if (is_dhgr)
    {
      if (Write_bytes(file, vram[1], 8192))
        File_error = 0;
    }
    else
      File_error = 0;
  }

  free(vram[0]);
  free(vram[1]);
  fclose(file);
}
