/* vim:expandtab:ts=2 sw=2: */ /* Grafx2 - The Ultimate 256-color bitmap paint program 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 */ ///@file miscfileformats.c /// Formats that aren't fully saving, either because of palette restrictions or other things #include #include #include #include "engine.h" #include "errors.h" #include "global.h" #include "io.h" #include "libraw2crtc.h" #include "loadsave.h" #include "misc.h" #include "sdlscreen.h" #include "struct.h" #include "windows.h" #include "oldies.h" //////////////////////////////////// PAL //////////////////////////////////// // // -- Test wether a file is in PAL format -------------------------------- void Test_PAL(T_IO_Context * context) { FILE *file; char filename[MAX_PATH_CHARACTERS]; long file_size; Get_full_filename(filename, context->File_name, context->File_directory); File_error = 1; if ((file = fopen(filename, "rb"))) { 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 { // Bigger (or smaller ?) files may be in other formats. These have an // header, so look for it. fread(filename, 1, 8, file); if (strncmp(filename,"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(filename,"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); fread(filename, 1, 8, file); if (strncmp(filename, "PAL data", 8) == 0) { File_error = 0; } } } fclose(file); } } void Test_GPL(T_IO_Context * context) { FILE *file; char filename[MAX_PATH_CHARACTERS]; long file_size; Get_full_filename(filename, context->File_name, context->File_directory); File_error = 1; if ((file = fopen(filename, "rb"))) { file_size = File_length_file(file); if (file_size > 33) { // minimum header length == 33 // "GIMP Palette" == 12 fread(filename, 1, 12, file); if (strncmp(filename,"GIMP Palette",12) == 0) File_error = 0; } } fclose(file); } // skip the padding before a space-padded field. static int skip_padding(FILE *file, int max_chars) { char buffer[1]; int chars_read = 0; int latest_chars_read = 0; size_t tmp; buffer[0] = ' '; while (buffer[0] == ' '){ latest_chars_read = fread(buffer, 1, 1, file); if ((latest_chars_read != 1) || (chars_read == max_chars)) return chars_read; // eof chars_read += latest_chars_read; } if (chars_read > 0){ tmp = ftell(file); // printf ("rewinding to %d", tmp - 1); fseek(file, tmp - 1, SEEK_SET); } return chars_read; } // -- Load file with format GPL ----------------------------------------- void Load_GPL(T_IO_Context * context) { FILE *file; char filename[MAX_PATH_CHARACTERS]; // full filename long file_size; long pos; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Open file if ((file=fopen(filename, "rb"))) { fread(filename, 1, 13, file); file_size = File_length_file(file); if (strncmp(filename,"GIMP Palette\n",13) == 0) { int i, j, r, g, b, columns, chars_read; fscanf(file, "Name: %s\n", filename); printf("DBG: Escaped nominal destruction ~%s~\n", filename); // y fscanf(file, "Columns: %d\n", &columns); // TODO: set grafx2 columns setting to match. printf("DBG: Escaped architectural destruction %d\n", columns); // y // # fread(filename,1, 2, file); filename[2] = 0; printf("DBG: Escaped grammatical destruction ~%s~\n", filename); for (i = 0; i < 256; i++) { pos = ftell(file); skip_padding(file, 32); fscanf(file, "%d", &r); skip_padding(file, 32); fscanf(file, "%d", &g); skip_padding(file, 32); fscanf(file, "%d\t", &b); filename[0] = 0; j = 0; do { chars_read = fscanf(file, "%s", filename+j); if (chars_read > 0){ j += chars_read; // space or newline follows. fread(filename+j, 1, 1, file); } else{ filename[j] = '\n'; } } while (filename[j] != '\n'); filename[j] = 0; if (ftell(file) == pos) break; // no more colors. // TODO: analyze color names to build shade table printf("DBG: %d: %s\n", i, filename); context->Palette[i].R = r; context->Palette[i].G = g; context->Palette[i].B = b; } } else File_error = 2; // close the file fclose(file); } else // Si on n'a pas réussi à ouvrir le fichier, alors il y a eu une erreur File_error=1; } void Save_GPL (T_IO_Context * context) { FILE *file; char filename[MAX_PATH_CHARACTERS]; // full filename Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Open output file if ((file=fopen(filename,"w"))){ int i; 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(filename); } 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 char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier //long file_size; // Taille du fichier Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Ouverture du fichier if ((file=fopen(filename, "rb"))) { 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 { fread(filename, 1, 8, file); if (strncmp(filename,"JASC-PAL",8) == 0) { int i, n, r, g, b; fscanf(file, "%d",&n); if(n != 100) { File_error = 2; fclose(file); return; } // Read color count fscanf(file, "%d",&n); for (i = 0; i < n; i++) { fscanf(file, "%d %d %d",&r, &g, &b); context->Palette[i].R = r; context->Palette[i].G = g; context->Palette[i].B = b; } } else if(strncmp(filename, "RIFF", 4) == 0) { // Microsoft RIFF format. fseek(file, 8, SEEK_SET); fread(filename, 1, 8, file); if (strncmp(filename, "PAL data", 8) == 0) { char buffer[4]; word color_count; word i = 0; fseek(file, 22, SEEK_SET); Read_word_le(file, &color_count); for(i = 0; i < color_count; i++) { Read_bytes(file, buffer, 4); context->Palette[i].R = buffer[0]; context->Palette[i].G = buffer[1]; context->Palette[i].B = buffer[2]; } } else File_error = 2; } else File_error = 2; } // Fermeture du fichier 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) { FILE *file; char filename[MAX_PATH_CHARACTERS]; ///< full filename Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Open output file if ((file=fopen(filename,"w"))) { int i; setvbuf(file, NULL, _IOFBF, 64*1024); if (fputs("JASC-PAL\n0100\n256\n", file)==EOF) File_error=1; for (i = 0; i < 256 && File_error==0; i++) { if (fprintf(file,"%d %d %d\n",context->Palette[i].R, context->Palette[i].G, context->Palette[i].B) <= 0) File_error=1; } fclose(file); if (File_error) remove(filename); } 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; // Fichier du fichier char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier T_PKM_Header header; Get_full_filename(filename, context->File_name, context->File_directory); File_error=1; // Ouverture du fichier if ((file=fopen(filename, "rb"))) { // 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; } fclose(file); } } // -- Lire un fichier au format PKM ----------------------------------------- void Load_PKM(T_IO_Context * context) { FILE *file; // Fichier du fichier char filename[MAX_PATH_CHARACTERS]; // Nom complet 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; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; if ((file=fopen(filename, "rb"))) { 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 ( (indexCOMMENT_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_pixelsWidth, 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; indexWidth, (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; indexWidth, (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]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) header.Jump+=comment_size+2; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Ouverture du fichier if ((file=fopen(filename,"wb"))) { 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) { Write_one_byte(file,0); Write_one_byte(file,comment_size); for (Compteur_de_pixels=0; Compteur_de_pixelsComment[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_pixelsWidth,Compteur_de_pixels / context->Width); } while ( (pixel_value==last_color) && (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(filename); } //////////////////////////////////// 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) { char filename[MAX_PATH_CHARACTERS]; int size; FILE *file; T_CEL_Header1 header1; T_CEL_Header2 header2; int file_size; File_error=0; Get_full_filename(filename, context->File_name, context->File_directory); file_size=File_length(filename); if (file_size==0) { File_error = 1; // Si on ne peut pas faire de stat il vaut mieux laisser tomber return; } if (! (file=fopen(filename, "rb"))) { File_error = 1; return; } 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; } fclose(file); } // -- Lire un fichier au format CEL ----------------------------------------- void Load_CEL(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; 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; Get_full_filename(filename, context->File_name, context->File_directory); if ((file=fopen(filename, "rb"))) { 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_posHeight) && (!File_error));y_pos++) for (x_pos=0;((x_posWidth) && (!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_posWidth;x_pos++) Set_pixel(context, x_pos,y_pos,0); for (y_pos=header2.Y_offset;y_posHeight;y_pos++) for (x_pos=0;x_pos> 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_posFile_name, context->File_directory); if ((file=fopen(filename,"wb"))) { 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_posHeight) && (!File_error));y_pos++) { for (x_pos=0;((x_posWidth) && (!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_posHeight;y_pos++) { for (x_pos=0;x_posWidth;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_posWidth;x_pos++) { for (y_pos=0;y_posHeight;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_posFile_name, context->File_directory); if ((file=fopen(filename, "rb"))) { 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; } fclose(file); } else File_error=1; } // -- Lire un fichier au format KCF ----------------------------------------- void Load_KCF(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; 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; Get_full_filename(filename, context->File_name, context->File_directory); if ((file=fopen(filename, "rb"))) { 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_indexPalette[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) { char filename[MAX_PATH_CHARACTERS]; 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; Get_full_filename(filename, context->File_name, context->File_directory); if ((file=fopen(filename,"wb"))) { 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(filename); } else File_error=1; } //////////////////////////////////// PI1 //////////////////////////////////// //// DECODAGE d'une partie d'IMAGE //// void PI1_8b_to_16p(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; } } //// 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 //// void PI1_decode_palette(byte * src,byte * palette) { int i; // Numéro de la couleur traitée int ip; // index dans la palette word w; // Word contenant le code // Schéma d'un word = // // Low High // VVVV RRRR | 0000 BBBB // 0321 0321 | 0321 ip=0; for (i=0;i<16;i++) { #if SDL_BYTEORDER == SDL_LIL_ENDIAN w=(((word)src[(i*2)+1]<<8) | (src[(i*2)+0])); // Traitement des couleurs rouge, verte et bleue: palette[ip++]=(((w & 0x0007) << 1) | ((w & 0x0008) >> 3)) << 4; palette[ip++]=(((w & 0x7000) >> 11) | ((w & 0x8000) >> 15)) << 4; palette[ip++]=(((w & 0x0700) >> 7) | ((w & 0x0800) >> 11)) << 4; #else w=(((word)src[(i*2+1)])|(((word)src[(i*2)])<<8)); palette[ip++] = (((w & 0x0700)>>7) | ((w & 0x0800) >> 7))<<4 ; palette[ip++]=(((w & 0x0070)>>3) | ((w & 0x0080) >> 3))<<4 ; palette[ip++] = (((w & 0x0007)<<1) | ((w & 0x0008)))<<4 ; #endif } } //// CODAGE de la PALETTE //// void PI1_code_palette(byte * palette,byte * dest) { int i; // Numéro de la couleur traitée int ip; // index dans la palette word w; // Word contenant le code // Schéma d'un word = // // Low High // VVVV RRRR | 0000 BBBB // 0321 0321 | 0321 ip=0; for (i=0;i<16;i++) { #if SDL_BYTEORDER == SDL_LIL_ENDIAN // Traitement des couleurs rouge, verte et bleue: w =(((word)(palette[ip]>>2) & 0x38) >> 3) | (((word)(palette[ip]>>2) & 0x04) << 1); ip++; w|=(((word)(palette[ip]>>2) & 0x38) << 9) | (((word)(palette[ip]>>2) & 0x04) << 13); ip++; w|=(((word)(palette[ip]>>2) & 0x38) << 5) | (((word)(palette[ip]>>2) & 0x04) << 9); ip++; dest[(i*2)+0]=w & 0x00FF; dest[(i*2)+1]=(w>>8); #else w=(((word)(palette[ip]<<3))&0x0700);ip++; w|=(((word)(palette[ip]>>1))&0x0070);ip++; w|=(((word)(palette[ip]>>5))&0x0007);ip++; dest[(i*2)+1]=w & 0x00FF; dest[(i*2)+0]=(w>>8); #endif } } /// Load color ranges from a PI1 or PC1 image (Degas Elite format) 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) // Sanity checks if (min_col < 256 && max_col < 256 && direction < 3 && (direction == 1 || delay < 128)) { int speed = 210/(128-delay); // Grafx2's slider has a limit of 105 if (speed>105) speed = 105; 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; iColor_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 ++; } } } } // -- Tester si un fichier est au format PI1 -------------------------------- void Test_PI1(T_IO_Context * context) { FILE * file; // Fichier du fichier char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier int size; // Taille du fichier word resolution; // Résolution de l'image Get_full_filename(filename, context->File_name, context->File_directory); File_error=1; // Ouverture du fichier if ((file=fopen(filename, "rb"))) { // Vérification de la taille size=File_length_file(file); if ((size==32034) || (size==32066)) { // Lecture et vérification de la résolution if (Read_word_le(file,&resolution)) { if (resolution==0x0000) File_error=0; } } // Fermeture du fichier fclose(file); } } // -- Lire un fichier au format PI1 ----------------------------------------- void Load_PI1(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier FILE *file; word x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; if ((file=fopen(filename, "rb"))) { // allocation d'un buffer mémoire buffer=(byte *)malloc(32034); if (buffer!=NULL) { // Lecture du fichier dans le buffer if (Read_bytes(file,buffer,32034)) { // Initialisation de la preview Pre_load(context, 320,200,File_length_file(file),FORMAT_PI1,PIXEL_SIMPLE,0); if (File_error==0) { // Initialisation de la palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); PI1_decode_palette(buffer+2,(byte *)context->Palette); context->Width=320; context->Height=200; // Chargement/décompression de l'image ptr=buffer+34; for (y_pos=0;y_pos<200;y_pos++) { for (x_pos=0;x_pos<(320>>4);x_pos++) { PI1_8b_to_16p(ptr,pixels+(x_pos<<4)); ptr+=8; } for (x_pos=0;x_pos<320;x_pos++) Set_pixel(context, x_pos,y_pos,pixels[x_pos]); } PI1_load_ranges(context, buffer, 32034); } } else File_error=1; free(buffer); buffer = NULL; } else File_error=1; fclose(file); } else File_error=1; } // -- Sauver un fichier au format PI1 --------------------------------------- void Save_PI1(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier FILE *file; short x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Ouverture du fichier if ((file=fopen(filename,"wb"))) { 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((byte *)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_posHeight) { for (x_pos=0;(x_pos<320) && (x_posWidth);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; } } PI1_save_ranges(context, buffer, 32034); if (Write_bytes(file,buffer,32034)) { fclose(file); } else // Error d'écriture (disque plein ou protégé) { fclose(file); remove(filename); File_error=1; } // Libération du buffer mémoire free(buffer); buffer = NULL; } else { fclose(file); remove(filename); File_error=1; } } //////////////////////////////////// PC1 //////////////////////////////////// //// DECOMPRESSION d'un buffer selon la méthode PACKBITS //// void PC1_uncompress_packbits(byte * src,byte * dest) { int is,id; // Les indices de parcour des buffers int n; // Octet de contrôle for (is=id=0;id<32000;) { n=src[is++]; if (n & 0x80) { // Recopier src[is] -n+1 fois n=257-n; for (;(n>0) && (id<32000);n--) dest[id++]=src[is]; is++; } else { // Recopier n+1 octets littéralement n=n+1; for (;(n>0) && (id<32000);n--) dest[id++]=src[is++]; } // Contrôle des erreurs if (n>0) File_error=1; } } //// COMPRESSION d'un buffer selon la méthode PACKBITS //// 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 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 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; } } // -- Tester si un fichier est au format PC1 -------------------------------- void Test_PC1(T_IO_Context * context) { FILE *file; // Fichier du fichier char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier int size; // Taille du fichier word resolution; // Résolution de l'image Get_full_filename(filename, context->File_name, context->File_directory); File_error=1; // Ouverture du fichier if ((file=fopen(filename, "rb"))) { // Vérification de la taille size=File_length_file(file); if ((size<=32066)) { // Lecture et vérification de la résolution if (Read_word_le(file,&resolution)) { if (resolution==0x0080) File_error=0; } } // Fermeture du fichier fclose(file); } } // -- Lire un fichier au format PC1 ----------------------------------------- void Load_PC1(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier FILE *file; int size; word x_pos,y_pos; byte * buffercomp; byte * bufferdecomp; byte * ptr; byte pixels[320]; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; if ((file=fopen(filename, "rb"))) { size=File_length_file(file); // allocation des buffers mémoire buffercomp=(byte *)malloc(size); bufferdecomp=(byte *)malloc(32000); if ( (buffercomp!=NULL) && (bufferdecomp!=NULL) ) { // Lecture du fichier dans le buffer if (Read_bytes(file,buffercomp,size)) { // Initialisation de la preview Pre_load(context, 320,200,File_length_file(file),FORMAT_PC1,PIXEL_SIMPLE,0); if (File_error==0) { // Initialisation de la palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); PI1_decode_palette(buffercomp+2,(byte *)context->Palette); context->Width=320; context->Height=200; // Décompression du buffer PC1_uncompress_packbits(buffercomp+34,bufferdecomp); // Décodage de l'image ptr=bufferdecomp; for (y_pos=0;y_pos<200;y_pos++) { // Décodage de la scanline PC1_4bp_to_1line(ptr,ptr+40,ptr+80,ptr+120,pixels); ptr+=160; // Chargement de la ligne for (x_pos=0;x_pos<320;x_pos++) Set_pixel(context, x_pos,y_pos,pixels[x_pos]); } if (!File_error) { PI1_load_ranges(context, buffercomp, size); } } } else File_error=1; free(bufferdecomp); free(buffercomp); buffercomp = bufferdecomp = NULL; } else { File_error=1; free(bufferdecomp); free(buffercomp); buffercomp = bufferdecomp = NULL; } fclose(file); } else File_error=1; } // -- Sauver un fichier au format PC1 --------------------------------------- void Save_PC1(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier FILE *file; int size; short x_pos,y_pos; byte * buffercomp; byte * bufferdecomp; byte * ptr; byte pixels[320]; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Ouverture du fichier if ((file=fopen(filename,"wb"))) { 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((byte *)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_posHeight) { for (x_pos=0;(x_pos<320) && (x_posWidth);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(filename); File_error=1; } // Libération des buffers mémoire free(bufferdecomp); free(buffercomp); buffercomp = bufferdecomp = NULL; } else { fclose(file); remove(filename); File_error=1; } } //////////////////////////////////// NEO //////////////////////////////////// void Test_NEO(T_IO_Context * context) { FILE *file; // Fichier du fichier char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier int size; // Taille du fichier word resolution; // Résolution de l'image Get_full_filename(filename, context->File_name, context->File_directory); File_error=1; // Ouverture du fichier if ((file=fopen(filename, "rb"))) { // Vérification de la taille size=File_length_file(file); if ((size==32128)) { // Flag word : toujours 0 if (Read_word_le(file,&resolution)) { if (resolution == 0) File_error = 0; } // Lecture et vérification de la résolution if (Read_word_le(file,&resolution)) { if (resolution==0 || resolution==1 || resolution==2) File_error |= 0; } } // Fermeture du fichier fclose(file); } } void Load_NEO(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier FILE *file; word x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; if ((file=fopen(filename, "rb"))) { // allocation d'un buffer mémoire buffer=(byte *)malloc(32128); if (buffer!=NULL) { // Lecture du fichier dans le buffer if (Read_bytes(file,buffer,32128)) { // Initialisation de la preview Pre_load(context, 320,200,File_length_file(file),FORMAT_NEO,PIXEL_SIMPLE,0); if (File_error==0) { // Initialisation de la palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); // on saute la résolution et le flag, chacun 2 bits PI1_decode_palette(buffer+4,(byte *)context->Palette); context->Width=320; context->Height=200; // Chargement/décompression de l'image ptr=buffer+128; for (y_pos=0;y_pos<200;y_pos++) { for (x_pos=0;x_pos<(320>>4);x_pos++) { PI1_8b_to_16p(ptr,pixels+(x_pos<<4)); ptr+=8; } for (x_pos=0;x_pos<320;x_pos++) Set_pixel(context, x_pos,y_pos,pixels[x_pos]); } } } else File_error=1; free(buffer); buffer = NULL; } else File_error=1; fclose(file); } else File_error=1; } void Save_NEO(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; // Nom complet du fichier FILE *file; short x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; Get_full_filename(filename, context->File_name, context->File_directory); File_error=0; // Ouverture du fichier if ((file=fopen(filename,"wb"))) { setvbuf(file, NULL, _IOFBF, 64*1024); // allocation d'un buffer mémoire buffer=(byte *)malloc(32128); // Codage de la résolution buffer[0]=0x00; buffer[1]=0x00; buffer[2]=0x00; buffer[3]=0x00; // Codage de la palette PI1_code_palette((byte *)context->Palette,buffer+4); // Codage de l'image ptr=buffer+128; for (y_pos=0;y_pos<200;y_pos++) { // Codage de la ligne memset(pixels,0,320); if (y_posHeight) { for (x_pos=0;(x_pos<320) && (x_posWidth);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,32128)) { fclose(file); } else // Error d'écriture (disque plein ou protégé) { fclose(file); remove(filename); File_error=1; } // Libération du buffer mémoire free(buffer); buffer = NULL; } else { fclose(file); remove(filename); File_error=1; } } //////////////////////////////////// C64 //////////////////////////////////// void Test_C64(T_IO_Context * context) { FILE* file; char filename[MAX_PATH_CHARACTERS]; long file_size; Get_full_filename(filename, context->File_name, context->File_directory); file = fopen(filename,"rb"); if (file) { file_size = File_length_file(file); switch (file_size) { // case 1000: // screen or color // case 1002: // (screen or color) + loadaddr case 8000: // raw bitmap case 8002: // raw bitmap with loadaddr case 9000: // bitmap + ScreenRAM case 9002: // bitmap + ScreenRAM + loadaddr case 10001: // multicolor case 10003: // multicolor + loadaddr case 17472: // FLI (BlackMail) case 17474: // FLI (BlackMail) + loadaddr case 10277: // multicolor CDU-Paint + loadaddr File_error = 0; break; default: // then we don't know for now. File_error = 1; } fclose (file); } else { File_error = 1; } } 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++) { c[0]=screen_ram[cy*40+cx]&15; c[1]=screen_ram[cy*40+cx]>>4; 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); } } } } } 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); } } } } } 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]; for(y=0; y<200; y++) { for(x=0; x<160; x++) { Set_pixel(context, x,y,background[y]); } } 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+(3-x),cy*8+y,c[3]); } } } } 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]=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); } } } } Set_loading_layer(context, 3); for(y=0; y<200; y++) { for(x=0; x<160; x++) { Set_pixel(context, x,y,16); } } } void Load_C64(T_IO_Context * context) { FILE* file; char filename[MAX_PATH_CHARACTERS]; long file_size; byte hasLoadAddr=0; int loadFormat=0; enum c64_format {F_hires,F_multi,F_bitmap,F_fli}; const char *c64_format_names[]={"Hires","Multicolor","Bitmap","FLI"}; // Palette from http://www.pepto.de/projects/colorvic/ byte pal[48]={ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x68, 0x37, 0x2B, 0x70, 0xA4, 0xB2, 0x6F, 0x3D, 0x86, 0x58, 0x8D, 0x43, 0x35, 0x28, 0x79, 0xB8, 0xC7, 0x6F, 0x6F, 0x4F, 0x25, 0x43, 0x39, 0x00, 0x9A, 0x67, 0x59, 0x44, 0x44, 0x44, 0x6C, 0x6C, 0x6C, 0x9A, 0xD2, 0x84, 0x6C, 0x5E, 0xB5, 0x95, 0x95, 0x95}; byte *file_buffer; byte *bitmap, *screen_ram, *color_ram=NULL, *background=NULL; // Only pointers to existing data word width=320, height=200; static byte dummy_screen[1000]; Get_full_filename(filename, context->File_name, context->File_directory); file = fopen(filename,"rb"); if (file) { File_error=0; file_size = File_length_file(file); // Check for known file sizes switch (file_size) { case 8000: // raw bitmap case 8002: // raw bitmap with loadaddr case 9000: // bitmap + ScreenRAM case 9002: // bitmap + ScreenRAM + loadaddr case 10001: // multicolor case 10003: // multicolor + loadaddr case 10277: // multicolor CDU-Paint + loadaddr case 17472: // FLI (BlackMail) case 17474: // FLI (BlackMail) + loadaddr break; default: File_error = 1; fclose(file); return; } // 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); memset(dummy_screen,1,1000); switch (file_size) { case 8000: // raw bitmap hasLoadAddr=0; loadFormat=F_bitmap; bitmap=file_buffer+0; // length: 8000 screen_ram=dummy_screen; break; case 8002: // raw bitmap with loadaddr hasLoadAddr=1; loadFormat=F_bitmap; bitmap=file_buffer+2; // length: 8000 screen_ram=dummy_screen; 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 9002: // bitmap + ScreenRAM + loadaddr hasLoadAddr=1; loadFormat=F_hires; bitmap=file_buffer+2; // length: 8000 screen_ram=file_buffer+8002; // length: 1000 break; case 10001: // multicolor hasLoadAddr=0; loadFormat=F_multi; context->Ratio = PIXEL_WIDE; 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 hasLoadAddr=1; loadFormat=F_multi; context->Ratio = PIXEL_WIDE; 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 10277: // multicolor CDU-Paint + loadaddr hasLoadAddr=1; loadFormat=F_multi; context->Ratio = PIXEL_WIDE; // 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; context->Ratio = PIXEL_WIDE; 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; context->Ratio = PIXEL_WIDE; 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; default: File_error = 1; free(file_buffer); return; } if (context->Ratio == PIXEL_WIDE) width=160; // Write detailed format in comment strcpy(context->Comment, c64_format_names[loadFormat]); if (hasLoadAddr) { // get load address word load_addr; load_addr = file_buffer[0] | (file_buffer[1] << 8); sprintf(context->Comment+strlen(context->Comment),", load at $%4.4X",load_addr); } else { sprintf(context->Comment+strlen(context->Comment),", no addr"); } Pre_load(context, width, height, file_size, FORMAT_C64, context->Ratio,0); // Do this as soon as you can memcpy(context->Palette,pal,48); // this set the software palette for grafx2 // Transparent color "16" is a dark grey that is distinguishable // from black, but darker than normal colors. context->Palette[16].R=20; context->Palette[16].G=20; context->Palette[16].B=20; context->Width = width ; context->Height = height; context->Transparent_color=16; if(loadFormat==F_fli) { Load_C64_fli(context,bitmap,screen_ram,color_ram,background); } else if(loadFormat==F_multi) { Load_C64_multi(context,bitmap,screen_ram,color_ram,*background); } else { Load_C64_hires(context,bitmap,screen_ram); } File_error = 0; free(file_buffer); } else File_error = 1; } int Save_C64_window(byte *saveWhat, byte *loadAddr) { int button; unsigned int i; T_Dropdown_button *what, *addr; char * what_label[] = { "All", "Bitmap", "Screen", "Color" }; char * address_label[] = { "None", "$2000", "$4000", "$6000", "$8000", "$A000", "$C000", "$E000" }; Open_window(200,120,"c64 settings"); Window_set_normal_button(110,100,80,15,"Save",1,1,SDLK_RETURN); // 1 Window_set_normal_button(10,100,80,15,"Cancel",1,1,SDLK_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; i2) { Warning_message("More than 2 colors in 8x8 pixels"); // TODO here we should hilite the offending block printf("\nerror at %dx%d (%d colors)\n",cx*8,cy*8,numcolors); return 1; } c1 = 0; c2 = 0; for(i=0;i<16;i++) { if(cusage[i]) { c2=i; break; } } c1=c2+1; for(i=c2;i<16;i++) { if(cusage[i]) { c1=i; } } screen_ram[cx+cy*40]=(c2<<4)|c1; for(y=0; y<8; y++) { bits=0; for(x=0; x<8; x++) { 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; } bits=bits<<1; if (pixel==c2) bits|=1; } bitmap[pos++]=bits; //Write_byte(file,bits&255); } } } file = fopen(filename,"wb"); if(!file) { Warning_message("File open failed"); File_error = 1; return 1; } setvbuf(file, NULL, _IOFBF, 64*1024); if (loadAddr) { Write_byte(file,0); Write_byte(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; } int Save_C64_multi(T_IO_Context *context, char *filename, byte saveWhat, byte 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,count; dword cusage[256]; byte i,background=0; FILE *file; numcolors=Count_used_colors(cusage); count=0; // Detect the ackground 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); 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 << pixel)) used++; } if (used>3) { // 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 was not used in any other tile yet, // but 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 invalids |= 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 found"); return 1; } } } } // Now just pick the first valid candidate for (n = 0; n<16; n++) { if (candidates & (1 << n)) { background = n; break; } } // Now that we know which color is the background, we can encode the cells for(cy=0; cy<25; cy++) { //printf("\ny:%2d ",cy); for(cx=0; cx<40; cx++) { numcolors=Count_used_colors_area(cusage,cx*4,cy*8,4,8); if(numcolors>4) { Warning_message("More than 4 colors in 4x8"); // TODO hilite offending block return 1; } color=1; c[0]=background; for(i=0; i<16; i++) { lut[i]=0; if(cusage[i]) { if(i!=background) { lut[i]=color; c[color]=i; color++; } else { lut[i]=0; } } } // add to screen_ram and color_ram screen_ram[cx+cy*40]=c[1]<<4|c[2]; color_ram[cx+cy*40]=c[3]; //printf("%x%x%x ",c[1],c[2],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); 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; } bits=bits<<2; bits|=lut[pixel]; } //Write_byte(file,bits&255); bitmap[pos++]=bits; } } } file = fopen(filename,"wb"); if(!file) { Warning_message("File open failed"); File_error = 2; return 2; } setvbuf(file, NULL, _IOFBF, 64*1024); if (loadAddr) { Write_byte(file,0); Write_byte(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); //printf("\nbg:%d\n",background); return 0; } int Save_C64_fli(char *filename, byte saveWhat, byte loadAddr) { FILE *file; byte file_buffer[17474]; memset(file_buffer,0,sizeof(file_buffer)); if (C64_FLI(file_buffer+9474, file_buffer+1282, file_buffer+258, file_buffer+2)) { File_error=1; return 1; } file = fopen(filename,"wb"); if(!file) { Warning_message("File open failed"); File_error = 1; return 1; } setvbuf(file, NULL, _IOFBF, 64*1024); if (loadAddr) { file_buffer[0]=0; file_buffer[1]=loadAddr; Write_bytes(file,file_buffer,2); } if (saveWhat==0) Write_bytes(file,file_buffer+2,256); if (saveWhat==0 || saveWhat==3) Write_bytes(file,file_buffer+258,1024); if (saveWhat==0 || saveWhat==1) Write_bytes(file,file_buffer+1282,8192); if (saveWhat==0 || saveWhat==2) Write_bytes(file,file_buffer+9474,8000); fclose(file); //printf("\nbg:%d\n",background); return 0; } void Save_C64(T_IO_Context * context) { char filename[MAX_PATH_CHARACTERS]; static byte saveWhat=0, loadAddr=0; dword numcolors,cusage[256]; numcolors=Count_used_colors(cusage); Get_full_filename(filename, context->File_name, context->File_directory); /* if (numcolors>16) { Warning_message("Error: Max 16 colors"); File_error = 1; return; } */ if (((context->Width!=320) && (context->Width!=160)) || context->Height!=200) { Warning_message("must be 320x200 or 160x200"); File_error = 1; return; } if(!Save_C64_window(&saveWhat,&loadAddr)) { File_error = 1; return; } //printf("saveWhat=%d, loadAddr=%d\n",saveWhat,loadAddr); if (strcasecmp(filename + strlen(filename) - 4, ".fli") == 0) { // FIXME moving FLI to a separate format in the fileselector would be smarter File_error = Save_C64_fli(filename,saveWhat,loadAddr); } else if (context->Width==320) File_error = Save_C64_hires(context,filename,saveWhat,loadAddr); else { File_error = Save_C64_multi(context, filename,saveWhat,loadAddr); } } // SCR (Amstrad CPC) void Test_SCR(T_IO_Context * context) { // Mmh... not sure what we could test. Any idea ? // The palette file can be tested, if it exists and have the right size it's // ok. But if it's not there the pixel data may still be valid. And we can't // use the filesize as this depends on the screen format. // An AMSDOS header would be a good indication but in some cases it may not // be there (void)context; // unused } 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. // 1) Seek for a palette // 2) If palette found get screenmode from there, else ask user // 3) ask user for screen size (or register values) // 4) Load color data from palette (if found) // 5) Close palette // 6) Open the file // 7) Run around the screen to untangle the pixeldata // 8) Close the file (void)context; // unused } void Save_SCR(T_IO_Context * context) { // 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. unsigned char* output; unsigned long outsize; unsigned char r1; int cpc_mode; FILE* file; char filename[MAX_PATH_CHARACTERS]; Get_full_filename(filename, context->File_name, context->File_directory); 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; } output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0); file = fopen(filename,"wb"); Write_bytes(file, output, outsize); fclose(file); free (output); output = NULL; File_error = 0; } // CM5 - Amstrad CPC "Mode 5" picture // This is a format designed by SyX. There is one .SCR file in the usual amstrad format, // and a .CM5 file with the palette, which varies over time. void Test_CM5(T_IO_Context * context) { // check cm5 file size == 2049 bytes FILE *file; char filename[MAX_PATH_CHARACTERS]; long file_size; Get_full_filename(filename, context->File_name, context->File_directory); File_error = 1; if ((file = fopen(filename, "rb"))) { file_size = File_length_file(file); if (file_size == 2049) File_error = 0; } fclose(file); // TODO: check existence of a .SCR file with the same name } 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; char filename[MAX_PATH_CHARACTERS]; byte value = 0; int mod=0; short line = 0; int tx, ty; byte buffer[48*6/4]; Get_full_filename(filename, context->File_name, context->File_directory); if (!(file = fopen(filename, "rb"))) { File_error = 1; return; } Pre_load(context, 48*6, 256, 2049, FORMAT_CM5, PIXEL_SIMPLE, 0); context->Width=48*6; context->Height=256; // Setup the palette (amstrad hardware palette) context->Palette[0x40].R = 0x6E; context->Palette[0x40].G = 0x7D; context->Palette[0x40].B = 0x6B; context->Palette[0x41].R = 0x6E; context->Palette[0x41].G = 0x7B; context->Palette[0x41].B = 0x6B; context->Palette[0x42].R = 0; context->Palette[0x42].G = 0xF3; context->Palette[0x42].B = 0x6B; context->Palette[0x43].R = 0xF3; context->Palette[0x43].G = 0xF3; context->Palette[0x43].B = 0x6D; context->Palette[0x44].R = 0; context->Palette[0x44].G = 2; context->Palette[0x44].B = 0x6B; context->Palette[0x45].R = 0xF0; context->Palette[0x45].G = 2; context->Palette[0x45].B = 0x68; context->Palette[0x46].R = 0; context->Palette[0x46].G = 0x78; context->Palette[0x46].B = 0x68; context->Palette[0x47].R = 0xF3; context->Palette[0x47].G = 0x7D; context->Palette[0x47].B = 0x6B; context->Palette[0x48].R = 0xF3; context->Palette[0x48].G = 0x02; context->Palette[0x48].B = 0x68; context->Palette[0x49].R = 0xF3; context->Palette[0x49].G = 0xF3; context->Palette[0x49].B = 0x6B; context->Palette[0x4A].R = 0xF3; context->Palette[0x4A].G = 0xF3; context->Palette[0x4A].B = 0xD; context->Palette[0x4B].R = 255; context->Palette[0x4B].G = 0xF3; context->Palette[0x4B].B = 0xF9; context->Palette[0x4C].R = 0xF3; context->Palette[0x4C].G = 5; context->Palette[0x4C].B = 6; context->Palette[0x4D].R = 0xF3; context->Palette[0x4D].G = 2; context->Palette[0x4D].B = 0xF4; context->Palette[0x4E].R = 0xF3; context->Palette[0x4E].G = 0x7D; context->Palette[0x4E].B = 0xD; context->Palette[0x4F].R = 0xFA; context->Palette[0x4F].G = 0x80; context->Palette[0x4F].B = 0xF9; context->Palette[0x50].R = 0x00; context->Palette[0x50].G = 0x02; context->Palette[0x50].B = 0x68; context->Palette[0x51].R = 0x02; context->Palette[0x51].G = 0xF3; context->Palette[0x51].B = 0x6B; context->Palette[0x52].R = 2; context->Palette[0x52].G = 0xF0; context->Palette[0x52].B = 1; context->Palette[0x53].R = 0xF; context->Palette[0x53].G = 0xF3; context->Palette[0x53].B = 0xF2; context->Palette[0x54].R = 0; context->Palette[0x54].G = 2; context->Palette[0x54].B = 1; context->Palette[0x55].R = 0x0C; context->Palette[0x55].G = 2; context->Palette[0x55].B = 0xF4; context->Palette[0x56].R = 2; context->Palette[0x56].G = 0x78; context->Palette[0x56].B = 1; context->Palette[0x57].R = 0xC; context->Palette[0x57].G = 0x7B; context->Palette[0x57].B = 0xF4; context->Palette[0x58].R = 0x69; context->Palette[0x58].G = 2; context->Palette[0x58].B = 0x68; context->Palette[0x59].R = 0x71; context->Palette[0x59].G = 0xF3; context->Palette[0x59].B = 0x6B; context->Palette[0x5A].R = 0x71; context->Palette[0x5A].G = 0xF5; context->Palette[0x5A].B = 4; context->Palette[0x5B].R = 0x71; context->Palette[0x5B].G = 0xF3; context->Palette[0x5B].B = 0xF4; context->Palette[0x5C].R = 0x6C; context->Palette[0x5C].G = 2; context->Palette[0x5C].B = 1; context->Palette[0x5D].R = 0x6C; context->Palette[0x5D].G = 2; context->Palette[0x5D].B = 0xF2; context->Palette[0x5E].R = 0x6E; context->Palette[0x5E].G = 0x7B; context->Palette[0x5E].B = 1; context->Palette[0x5F].R = 0x6E; context->Palette[0x5F].G = 0x7B; context->Palette[0x5F].B = 0xF6; if (Read_byte(file, &value)!=1) 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 0 again Set_loading_layer(context, 0); if (context->Type == CONTEXT_MAIN_IMAGE) Main_backups->Pages->Image_mode = IMAGE_MODE_MODE5; for(ty=0; tyHeight; ty++) for(tx=0; txWidth; tx++) { Set_pixel(context, tx, ty, value); } // Fill layer with color we just read while(Read_byte(file, &value) == 1) { switch(mod) { case 0: // This is color for layer 1 Set_loading_layer(context, 1); for(tx=0; txWidth; tx++) Set_pixel(context, tx, line, value); break; case 1: // This is color for layer 2 Set_loading_layer(context, 2); for(tx=0; txWidth; tx++) Set_pixel(context, tx, line, value); break; default: // This is color for a block in layer 4 Set_loading_layer(context, 3); for(tx=(mod-2)*48; tx<(mod-1)*48; tx++) Set_pixel(context, tx, line, value); break; } mod = mod + 1; if (mod > 7) { mod = 0; line ++; } } fclose(file); // Load the pixeldata to the 5th layer { char* ext = filename + strlen(filename) - 3; int idx = 8; do { if (-- idx < 0) { File_error = 1; return; } ext[0] = (idx & 1) ? 'g':'G'; ext[1] = (idx & 2) ? 'f':'F'; ext[2] = (idx & 4) ? 'x':'X'; file = fopen(filename, "rb"); } while(file == NULL); } Set_loading_layer(context, 4); for (ty = 0; ty < 256; ty++) { Read_bytes(file, buffer, 48*6/4); for (tx = 0; tx < 48*6; tx+=4) { Set_pixel(context, tx+0, ty, 3-(((buffer[tx/4]&0x80) >> 7) |((buffer[tx/4]&0x8)>>2))); Set_pixel(context, tx+1, ty, 3-(((buffer[tx/4]&0x40) >> 6) |((buffer[tx/4]&0x4)>>1))); Set_pixel(context, tx+2, ty, 3-(((buffer[tx/4]&0x20) >> 5) |((buffer[tx/4]&0x2)>>0))); Set_pixel(context, tx+3, ty, 3-(((buffer[tx/4]&0x10) >> 4) |((buffer[tx/4]&0x1)<<1))); } } fclose(file); } void Save_CM5(T_IO_Context* context) { char filename[MAX_PATH_CHARACTERS]; FILE* file; int tx, ty; Get_full_filename(filename, context->File_name, context->File_directory); // 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 = fopen(filename,"wb"))) { 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 filename[strlen(filename) - 3] = 0; strcat(filename,"gfx"); if (!(file = fopen(filename, "wb"))) { 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. */ void Test_PPH(T_IO_Context * context) { FILE *file; unsigned char buffer[MAX_PATH_CHARACTERS]; long file_size; int w; int expected; Get_full_filename(buffer, context->File_name, context->File_directory); File_error = 1; if ((file = fopen(buffer, "rb"))) { // First check file size is large enough to hold the header file_size = File_length_file(file); if (file_size < 11) { File_error = 1; goto abort; } // File is large enough for the header, now check if the data makes some sense fread(buffer, 1, 6, file); if (buffer[0] > 5) { // Unknown mode File_error = 2; goto abort; } w = buffer[1] | (buffer[2] << 8); if (w < 2 || w > 384) { // Invalid width File_error = 3; goto abort; } w = buffer[3] | (buffer[4] << 8); if (w < 1 || w > 272) { // Invalid height File_error = 4; goto abort; } if (buffer[5] < 1 || buffer[5] > 28) { // Invalid palettes count File_error = 5; goto abort; } 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; goto abort; } expected += 16; break; case 1: case 5: expected += buffer[5] * 5 - 1; break; case 2: // Palete size should be 2 bytes if (buffer[5] != 1) { File_error = 7; goto abort; } expected += 2; break; } if (file_size != expected) { File_error = 6; goto abort; } File_error = 0; } else { File_error = 8; } abort: fclose(file); // TODO: check existence of .ODD/.EVE files with the same name } 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; char filename[MAX_PATH_CHARACTERS]; // 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; char* ext; 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 } }; Get_full_filename(filename, context->File_name, context->File_directory); if (!(file = fopen(filename, "rb"))) { 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. ext = filename + strlen(filename) - 3; ext[0] = 'O'; ext[1] = 'D'; ext[2] = 'D'; file = fopen(filename, "rb"); ext[0] = 'E'; ext[1] = 'V'; ext[2] = 'E'; feven = fopen(filename, "rb"); 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) { // 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? }