grafX2/src/miscfileformats.c
2019-12-07 16:15:50 +01:00

2246 lines
66 KiB
C

/* vim:expandtab:ts=2 sw=2:
*/
/* Grafx2 - The Ultimate 256-color bitmap paint program
Copyright 2018-2019 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
#define strdup _strdup
#endif
#include "global.h"
#include "io.h"
#include "loadsave.h"
#include "loadsavefuncs.h"
#include "misc.h"
#include "struct.h"
#include "windows.h"
#include "oldies.h"
#include "fileformats.h"
#include "gfx2mem.h"
#include "gfx2log.h"
//////////////////////////////////// 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>
for (i = 0; i < 256; i++)
{
for (;;)
{
// skip comments
int c = getc(file);
if (c == '#')
{
if (!Read_byte_line(file, buffer, sizeof(buffer)))
return;
GFX2_Log(GFX2_DEBUG, "comment: %s", buffer);
}
else
{
fseek(file, -1, SEEK_CUR);
break;
}
}
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
GFX2_Log(GFX2_DEBUG, "PKM original screen %dx%d\n", (int)Original_screen_X, (int)Original_screen_Y);
}
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;
}
/////////////////////////////////// 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)
GFX2_Log(GFX2_WARNING, "Load_FLI(): file size mismatch in header %lu != %u\n", file_size, header.size);
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 && Get_image_mode(context) == 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
{
GFX2_Log(GFX2_WARNING, "Unsupported opcode %04x\n", opcode);
File_error = 2;
break;
}
}
}
if (sub_chunk_size > 0)
{
fseek(file, sub_chunk_size, SEEK_CUR);
}
}
}
break;
default: // skip
GFX2_Log(GFX2_WARNING, "Load_FLI(): unrecognized chunk %04x\n", chunk_type);
}
if (chunk_size > 0 && header.size != 12)
{
fseek(file, chunk_size, SEEK_CUR);
}
}
fclose(file);
}
/////////////////////////////// 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] = GFX2_malloc(8192);
Read_bytes(file, vram[0], 8192);
if (is_dhgr)
{
vram[1] = GFX2_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);
}
///////////////////////////// HP-48 Grob Files ////////////////////////////
/**
* HP48 addresses are 20bits (5 nibbles)
* offset is in nibble (half byte)
*/
static dword Read_HP48Address(const byte * buffer, int offset)
{
dword data = 0;
int i = 4;
do
{
byte nibble;
nibble = buffer[(offset + i) >> 1];
if ((offset + i) & 1)
nibble >>= 4;
nibble &= 15;
data = (data << 4) | nibble;
}
while (i-- > 0);
return data;
}
/**
* Test for a HP-48 Grob file
*/
void Test_GRB(T_IO_Context * context, FILE * file)
{
byte buffer[18];
unsigned long file_size;
dword prologue, size, width, height;
(void)context;
File_error = 1;
file_size = File_length_file(file);
if (!Read_bytes(file, buffer, 18))
return;
if(memcmp(buffer, "HPHP48-R", 8) != 0)
return;
prologue = Read_HP48Address(buffer+8, 0);
size = Read_HP48Address(buffer+8, 5);
GFX2_Log(GFX2_DEBUG, "HP48 File detected. %lu bytes prologue %05x %u nibbles\n",
file_size, prologue, size);
if (prologue != 0x02b1e)
return;
height = Read_HP48Address(buffer+8, 10);
width = Read_HP48Address(buffer+8, 15);
GFX2_Log(GFX2_DEBUG, " Grob dimensions : %ux%u\n", width, height);
if ((file_size - 8) < ((size + 5) / 2))
return;
if (file_size < (18 + ((width + 7) >> 3) * height))
return;
File_error = 0;
}
void Load_GRB(T_IO_Context * context)
{
byte buffer[18];
byte * bitplane[4];
unsigned long file_size;
dword prologue, size, width, height;
byte bp, bpp;
FILE * file;
unsigned x, y;
File_error = 1;
file = Open_file_read(context);
if (file == NULL)
return;
file_size = File_length_file(file);
if (!Read_bytes(file, buffer, 18))
{
fclose(file);
return;
}
prologue = Read_HP48Address(buffer+8, 0);
size = Read_HP48Address(buffer+8, 5);
height = Read_HP48Address(buffer+8, 10);
width = Read_HP48Address(buffer+8, 15);
if (height >= 256)
bpp = 4;
else if (height >= 192)
bpp = 3;
else if (height >= 128)
bpp = 2;
else
bpp = 1;
GFX2_Log(GFX2_DEBUG, "HP48: %05X size=%u %ux%u\n", prologue, size, width, height);
File_error = 0;
Pre_load(context, width, height/bpp, file_size, FORMAT_GRB, PIXEL_SIMPLE, bpp);
if (File_error == 0)
{
dword bytes_per_plane = ((width + 7) >> 3) * (height/bpp);
dword offset = 0;
if (Config.Clear_palette)
memset(context->Palette, 0, sizeof(T_Palette));
for (x = 0; x < ((unsigned)1 << bpp); x++)
{
context->Palette[x].R = context->Palette[x].G = (x * 255) / ((1 << bpp) - 1);
context->Palette[x].B = 127;
}
// Load all bit planes
for (bp = 0; bp < bpp; bp++)
{
bitplane[bp] = GFX2_malloc(bytes_per_plane);
if (bitplane[bp])
{
if (!Read_bytes(file, bitplane[bp], bytes_per_plane))
File_error = 1;
}
}
// set pixels
for (y = 0; y < (height/bpp) && File_error == 0; y++)
{
for (x = 0; x < width; x++)
{
byte b = 0;
for (bp = 0; bp < bpp; bp++)
b |= ((bitplane[bp][offset] >> (x & 7)) & 1) << bp;
// invert because 1 is a black pixel on HP-48 LCD display
Set_pixel(context, x, y, b ^ ((1 << bpp) - 1));
if ((x & 7) == 7)
offset++;
}
if ((x & 7) != 7)
offset++;
}
// Free bit planes
for (bp = 0; bp < bpp; bp++)
free(bitplane[bp]);
}
fclose(file);
}