/* vim:expandtab:ts=2 sw=2: */ /* Grafx2 - The Ultimate 256-color bitmap paint program Copyright 2018 Thomas Bernard Copyright 2011 Pawel Góralski Copyright 2009 Petter Lindquist Copyright 2008 Yves Rizoud Copyright 2008 Franck Charlet Copyright 2007-2011 Adrien Destugues Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) Grafx2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. Grafx2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grafx2; if not, see */ ///@file miscfileformats.c /// Formats that aren't fully saving, either because of palette restrictions or other things #include #include #include #ifdef _MSC_VER #include #if _MSC_VER < 1900 #define snprintf _snprintf #endif #endif #include "engine.h" #include "errors.h" #include "global.h" #include "io.h" #include "libraw2crtc.h" #include "loadsave.h" #include "misc.h" #include "screen.h" #include "struct.h" #include "windows.h" #include "oldies.h" #include "pages.h" #include "keycodes.h" #include "input.h" #include "help.h" #include "fileformats.h" extern char Program_version[]; // generated in pversion.c extern const char SVN_revision[]; // generated in version.c //////////////////////////////////// PAL //////////////////////////////////// // // -- Test wether a file is in PAL format -------------------------------- void Test_PAL(T_IO_Context * context, FILE * file) { char buffer[32]; long file_size; (void)context; File_error = 1; file_size = File_length_file(file); // First check for GrafX2 legacy palette format. The simplest one, 768 bytes // of RGB data. It is a raw dump of the T_Palette structure. There is no // header at all, so we check for the file size. if (file_size == sizeof(T_Palette)) File_error = 0; else if (file_size > 8) { // Bigger (or smaller ?) files may be in other formats. These have an // header, so look for it. if (!Read_bytes(file, buffer, 8)) return; if (strncmp(buffer,"JASC-PAL",8) == 0) { // JASC file format, used by Paint Shop Pro and GIMP. This is also the // one used for saving, as it brings greater interoperability. File_error = 0; } else if(strncmp(buffer,"RIFF", 4) == 0) { // Microsoft RIFF file // This is a data container (similar to IFF). We only check the first // chunk header, and give up if that's not a palette. fseek(file, 8, SEEK_SET); if (!Read_bytes(file, buffer, 8)) return; if (strncmp(buffer, "PAL data", 8) == 0) { File_error = 0; } } } } 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 file with format GPL ----------------------------------------- void Load_GPL(T_IO_Context * context) { FILE *file; char buffer[256]; File_error=0; // Open file if ((file=Open_file_read(context))) { if (!Read_byte_line(file, buffer, sizeof(buffer))) { File_error = 1; 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))) { File_error = 1; return; } // Columns: 16 fscanf(file, "Columns: %d", &columns); Read_byte_line(file, buffer, sizeof(buffer)); // TODO: set grafx2 columns setting to match. // # if (!Read_byte_line(file, buffer, sizeof(buffer))) { File_error = 1; return; } for (i = 0; i < 256; i++) { 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); 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 //printf("DBG: %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; } } 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) { // Gimp is a unix program, so use Unix file endings (LF aka '\n') FILE *file; File_error=0; // Open output file if ((file=Open_file_write(context)) != NULL ){ 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_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; 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(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 ( (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 > 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_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_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_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_pos16 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_pos>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_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) { 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; } //////////////////////////////////// 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 //// static void PI1_decode_palette(const byte * src, T_Components * palette) { int i; // Numéro de la couleur traitée word w; // Word contenant le code // Schéma d'un word = // // Low High // VVVV RRRR | 0000 BBBB // 0321 0321 | 0321 for (i=0;i<16;i++) { w = (word)src[0] << 8 | (word)src[1]; src += 2; palette[i].R = (((w & 0x0700)>>7) | ((w & 0x0800) >> 11)) * 0x11 ; palette[i].G = (((w & 0x0070)>>3) | ((w & 0x0080) >> 7)) * 0x11 ; palette[i].B = (((w & 0x0007)<<1) | ((w & 0x0008) >> 3)) * 0x11 ; } } //// CODAGE de la PALETTE //// void PI1_code_palette(const T_Components * palette, byte * dest) { int i; // Numéro de la couleur traitée word w; // Word contenant le code // Schéma d'un word = // // Low High // VVVV RRRR | 0000 BBBB // 0321 0321 | 0321 for (i=0;i<16;i++) { w = ((word)(palette[i].R & 0xe0) << 3) | ((word)(palette[i].R & 0x10) << 7); w |= ((word)(palette[i].G & 0xe0) >> 1) | ((word)(palette[i].G & 0x10) << 3); w |= ((word)(palette[i].B & 0xe0) >> 5) | ((word)(palette[i].B & 0x10) >> 1); *dest++ = (w >> 8); *dest++ = (w & 0xff); } } /// Load color 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 = 105; if (delay < 128) 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) { int size; // Taille du fichier word resolution; // Résolution de l'image (void)context; File_error=1; // 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; } } } // -- Lire un fichier au format PI1 ----------------------------------------- void Load_PI1(T_IO_Context * context) { FILE *file; word x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; File_error=0; if ((file=Open_file_read(context))) { // 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,4); if (File_error==0) { // Initialisation de la palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); PI1_decode_palette(buffer+2, context->Palette); // 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) { FILE *file; short x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; File_error=0; // Ouverture du fichier if ((file=Open_file_write(context))) { setvbuf(file, NULL, _IOFBF, 64*1024); // allocation d'un buffer mémoire buffer=(byte *)malloc(32034); // Codage de la résolution buffer[0]=0x00; buffer[1]=0x00; // Codage de la palette PI1_code_palette(context->Palette, buffer+2); // Codage de l'image ptr=buffer+34; for (y_pos=0;y_pos<200;y_pos++) { // Codage de la ligne memset(pixels,0,320); if (y_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,32034)) { if (context->Color_cycles) { PI1_save_ranges(context, buffer, 32); if (!Write_bytes(file,buffer,32)) File_error=1; } fclose(file); } else // Error d'écriture (disque plein ou protégé) { fclose(file); Remove_file(context); File_error=1; } // Libération du buffer mémoire free(buffer); buffer = NULL; } else { fclose(file); Remove_file(context); File_error=1; } } //////////////////////////////////// PC1 //////////////////////////////////// //// 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) { int size; // Taille du fichier word resolution; // Résolution de l'image (void)context; File_error=1; // 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; } } } // -- Lire un fichier au format PC1 ----------------------------------------- void Load_PC1(T_IO_Context * context) { FILE *file; int size; word x_pos,y_pos; byte * buffercomp; byte * bufferdecomp; byte * ptr; byte pixels[320]; File_error=0; if ((file=Open_file_read(context))) { 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,4); if (File_error==0) { // Initialisation de la palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); PI1_decode_palette(buffercomp+2, context->Palette); // 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) { FILE *file; int size; short x_pos,y_pos; byte * buffercomp; byte * bufferdecomp; byte * ptr; byte pixels[320]; File_error=0; // Ouverture du fichier if ((file=Open_file_write(context))) { setvbuf(file, NULL, _IOFBF, 64*1024); // Allocation des buffers mémoire bufferdecomp=(byte *)malloc(32000); buffercomp =(byte *)malloc(64066); // Codage de la résolution buffercomp[0]=0x80; buffercomp[1]=0x00; // Codage de la palette PI1_code_palette(context->Palette, buffercomp+2); // Codage de l'image ptr=bufferdecomp; for (y_pos=0;y_pos<200;y_pos++) { // Codage de la ligne memset(pixels,0,320); if (y_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_file(context); File_error=1; } // Libération des buffers mémoire free(bufferdecomp); free(buffercomp); buffercomp = bufferdecomp = NULL; } else { fclose(file); Remove_file(context); File_error=1; } } //////////////////////////////////// NEO //////////////////////////////////// void Test_NEO(T_IO_Context * context, FILE * file) { int size; // Taille du fichier word resolution; // Résolution de l'image (void)context; File_error=1; // 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; } } } void Load_NEO(T_IO_Context * context) { FILE *file; word x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; File_error=0; if ((file=Open_file_read(context))) { // 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,4); 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, context->Palette); // 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) { FILE *file; short x_pos,y_pos; byte * buffer; byte * ptr; byte pixels[320]; File_error=0; // Ouverture du fichier if ((file=Open_file_write(context))) { setvbuf(file, NULL, _IOFBF, 64*1024); // allocation d'un buffer mémoire buffer=(byte *)malloc(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(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_file(context); File_error=1; } // Libération du buffer mémoire free(buffer); buffer = NULL; } else { fclose(file); Remove_file(context); File_error=1; } } //////////////////////////////////// C64 //////////////////////////////////// /** C64 file formats */ enum c64_format { F_invalid = -1, F_hires = 0, ///< 320x200 F_multi = 1, ///< 160x200 F_bitmap = 2, ///< 320x200 monochrome F_fli = 3 ///< FLI (Flexible Line Interpretation) }; /** C64 file formats names */ static const char *c64_format_names[] = { "Hires", "Multicolor", "Bitmap", "FLI" }; /** * Test for a C64 picture file * * Checks the file size and the load address * * References : * - http://unusedino.de/ec64/technical/formats/bitmap.html * - http://codebase64.org/doku.php?id=base:c64_grafix_files_specs_list_v0.03 * - https://sourceforge.net/p/view64/code/HEAD/tree/trunk/libview64.c#l3737 */ void Test_C64(T_IO_Context * context, FILE * file) { unsigned long file_size; word load_addr; byte header[14]; (void)context; File_error = 1; file_size = File_length_file(file); if (file_size < 16 || file_size > 48*1024) return; // File too short or too long, exit now // First test for formats without load address switch (file_size) { // case 1000: // screen or color case 8000: // raw bitmap case 9000: // bitmap + ScreenRAM case 10001: // multicolor case 17472: // FLI (BlackMail) File_error = 0; return; default: // then we don't know for now. if (!Read_word_le(file, &load_addr)) return; } GFX2_Log(GFX2_DEBUG, "Test_C64() file_size=%ld LoadAddr=$%04X\n", file_size, load_addr); if (!Read_bytes(file, header, sizeof(header))) return; if (memcmp(header, "DRAZPAINT", 9) == 0) { GFX2_Log(GFX2_DEBUG, "Test_C64() header=%.13s RLE code = $%02X\n", header, header[13]); File_error = 0; return; } // check last 2 bytes if (fseek(file, -2, SEEK_END) < 0) return; if (!Read_bytes(file, header, 2)) return; if (load_addr == 0x4000 && header[0] == 0xC2 && header[1] == 0x00) // Amica Paint EOF mark { File_error = 0; return; } switch (file_size) { // case 1002: // (screen or color) + loadaddr case 8002: // raw bitmap with loadaddr case 9002: // bitmap + ScreenRAM + loadaddr // $4000 => InterPaint Hi-Res (.iph) case 9003: // bitmap + ScreenRAM + loadaddr (+ border ?) case 9009: // bitmap + ScreenRAM + loadaddr // $2000 => Art Studio case 9218: // $5C00 => Doodle case 9332: // $3F8E => Paint Magic (.pmg) 'JEDI' at offset $0010 and $2010 case 10003: // multicolor + loadaddr // $4000 => InterPaint multicolor // $6000 => Koala Painter case 10004: // $4000 => Face Paint (.fpt) case 10006: // $6000 => Run Paint (.rpm) case 10018: // $2000 => Advanced Art Studio case 10022: // $18DC => Micro Illustrator (uncompressed) case 10050: // $1800 => Picasso64 case 10218: // $3C00 => Image System (.ism) case 10219: // $7800 => Saracen Paint (.sar) File_error = 0; break; case 10242: // $4000 => Artist 64 (.a64) // $A000 => Blazing paddles (.pi) // $5C00 => Rainbow Painter (.rp) if (load_addr != 0x4000 && load_addr != 0xa000 && load_addr != 0x5c00) { File_error = 1; return; } File_error = 0; break; case 17409: // $3c00 => FLI-designer v1.1 // ? $3ff0 => FLI designer 2 ? case 17410: // $3c00 => FLI MATIC case 17474: // FLI (BlackMail) + loadaddr // $3b00 => FLI Graph 2 case 17665: // $3b00 => FLI editor case 10277: // multicolor CDU-Paint + loadaddr // $7EEF File_error = 0; break; default: // then we don't know for now. File_error = 1; } } /** * Load C64 hires (320x200) * * @param context the IO context * @param bitmap the bitmap RAM (8000 bytes) * @param screen_ram the screen RAM (1000 bytes) */ static void Load_C64_hires(T_IO_Context *context, byte *bitmap, byte *screen_ram) { int cx,cy,x,y,c[4],pixel,color; for(cy=0; cy<25; cy++) { for(cx=0; cx<40; cx++) { if(screen_ram != NULL) { c[0]=screen_ram[cy*40+cx]&15; c[1]=screen_ram[cy*40+cx]>>4; } else { /// If screen_ram is NULL, uses default C64 basic colors c[0] = 6; c[1] = 14; } for(y=0; y<8; y++) { pixel=bitmap[cy*320+cx*8+y]; for(x=0; x<8; x++) { color=c[pixel&(1<<(7-x))?1:0]; Set_pixel(context, cx*8+x,cy*8+y,color); } } } } } /** * Load C64 multicolor (160x200) * * @param context the IO context * @param bitmap the bitmap RAM (8000 bytes) * @param screen_ram the screen RAM (1000 bytes) * @param color_ram the color RAM (1000 bytes) * @param background the background color */ static void Load_C64_multi(T_IO_Context *context, byte *bitmap, byte *screen_ram, byte *color_ram, byte background) { int cx,cy,x,y,c[4],pixel,color; c[0]=background&15; for(cy=0; cy<25; cy++) { for(cx=0; cx<40; cx++) { c[1]=screen_ram[cy*40+cx]>>4; c[2]=screen_ram[cy*40+cx]&15; c[3]=color_ram[cy*40+cx]&15; for(y=0; y<8; y++) { pixel=bitmap[cy*320+cx*8+y]; for(x=0; x<4; x++) { color=c[(pixel&3)]; pixel>>=2; Set_pixel(context, cx*4+(3-x),cy*8+y,color); } } } } } /** * Loads a C64 FLI (Flexible Line Interpretation) picture. * Sets 4 layers : * - Layer 0 : filled with background colors (1 per line) * - Layer 1 : "Color RAM" 4x8 blocks * - Layer 2 : pixels (From Screen RAMs + Bitmap) * - Layer 3 : Transparency layer filled with color 16 * * @param context the IO context * @param bitmap 8000 bytes buffer * @param screen_ram 8 x 1024 bytes buffers * @param color_ram 1000 byte buffer * @param background 200 byte buffer */ void Load_C64_fli(T_IO_Context *context, byte *bitmap, byte *screen_ram, byte *color_ram, byte *background) { // Thanks to MagerValp for complement of specifications. // // background : length: 200 (+ padding 56) // These are the BG colors for lines 0-199 (top to bottom) // Low nybble: the color. // High nybble: garbage. ignore it. // color_ram : length: 1000 (+ padding 24) // Color RAM. Contains one color per 4x8 block. // There are 40x25 such blocks, arranged from top left to bottom // right, starting in right direction. For each block there is one byte. // Low nybble: the color. // High nybble: garbage. ignore it. // screen_ram : length: 8192 // Screen RAMs. The s is important. // This is actually 8 blocks of 1000 bytes, each separated by a filler of // 24 bytes. Each byte contains data for a 4x1 pixel group, and a complete // block will contain 40x25 of them. 40 is from left to right, and 25 is from // top to bottom, spacing them 8 lines apart. // The second block start at y=1, the third block starts at y=2, etc... // Each byte contains 2 colors that *can* be used by the 4x1 pixel group: // Low nybble: Color 1 // High nybble: Color 2 // // bitmap : length: 8000 // This is the final structure that refers to all others. It describes // 160x200 pixels linearly, from top left to bottom right, starting in // right direction. For each pixel, two bits say which color is displayed // (So 4 pixels are described by the same byte) // 00 Use the BG color of the current line (background[y]) // 01 Use the Color 2 from the current 4x8 block of Screen RAM // ((screen_ram[y/8][x/4] & 0xF0) >> 8) // 10 Use the Color 1 from the current 4x8 block of Screen RAM // (screen_ram[y/8][x/4] & 0x0F) // 11 Use the color from Color RAM // (color_ram[y/8][x/4] & 0x0F) // int cx,cy,x,y,c[4]; // Fill layer 0 with background colors for(y=0; y<200; y++) { byte bg_color = 0; if (background != NULL) bg_color = background[y]; for(x=0; x<160; x++) Set_pixel(context, x,y, bg_color); } // Fill layer 1 with color ram (1 color per 4x8 block) Set_loading_layer(context, 1); for(cy=0; cy<25; cy++) { for(cx=0; cx<40; cx++) { c[3]=color_ram[cy*40+cx]&15; for(y=0; y<8; y++) { for(x=0; x<4; x++) { Set_pixel(context, cx*4+(3-x),cy*8+y,c[3]); } } } } // Layer 2 are actual pixels Set_loading_layer(context, 2); for(cy=0; cy<25; cy++) { for(cx=0; cx<40; cx++) { c[3]=color_ram[cy*40+cx]&15; for(y=0; y<8; y++) { int pixel=bitmap[cy*320+cx*8+y]; c[0] = 0; if(background != NULL) c[0] = background[cy*8+y]&15; c[1]=screen_ram[y*1024+cy*40+cx]>>4; c[2]=screen_ram[y*1024+cy*40+cx]&15; for(x=0; x<4; x++) { int color=c[(pixel&3)]; pixel>>=2; Set_pixel(context, cx*4+(3-x),cy*8+y,color); } } } } // Fill layer 3 with color 16 Set_loading_layer(context, 3); for(y=0; y<200; y++) { for(x=0; x<160; x++) { Set_pixel(context, x,y,16); } } } /** * Unpack the Amica Paint RLE packing * * @param[in,out] file_buffer will contain the unpacked buffer on return * @param[in] file_size packed buffer size * @return the unpacked data size or -1 in case of error * * Ref: * - http://codebase64.org/doku.php?id=base:c64_grafix_files_specs_list_v0.03 */ static long C64_unpack_amica(byte ** file_buffer, long file_size) { long unpacked_size = 0; byte * unpacked_buffer; byte * current, * end; byte * unpacked; const byte RLE_code = 0xC2; if (file_size <= 16 || file_buffer == NULL || *file_buffer == NULL) return -1; // First pass to know unpacked size end = *file_buffer + file_size; for (current = *file_buffer + 2; current < end; current++) { if (*current == RLE_code) { current++; if (*current == 0) break; unpacked_size += *current++; } else unpacked_size++; } GFX2_Log(GFX2_DEBUG, "C64_unpack_amica() unpacked_size=%ld\n", unpacked_size); // 2nd pass to unpack unpacked_buffer = malloc(unpacked_size); if (unpacked_buffer == NULL) return -1; unpacked = unpacked_buffer; for (current = *file_buffer + 2; current < end; current++) { if (*current == RLE_code) { byte count; current++; count = *current++; if (count == 0) break; while (count-- > 0) *unpacked++ = *current; } else *unpacked++ = *current; } free(*file_buffer); *file_buffer = unpacked_buffer; return unpacked_size; } /** * Unpack the DRAZPAINT RLE packing * * @param[in,out] file_buffer will contain the unpacked buffer on return * @param[in] file_size packed buffer size * @return the unpacked data size or -1 in case of error * * Ref: * - https://www.godot64.de/german/l_draz.htm * - https://sourceforge.net/p/view64/code/HEAD/tree/trunk/libview64.c#l2805 */ static long C64_unpack_draz(byte ** file_buffer, long file_size) { long unpacked_size = 0; byte * unpacked_buffer; byte * current, * end; byte * unpacked; byte RLE_code; if (file_size <= 16 || file_buffer == NULL || *file_buffer == NULL) return -1; RLE_code = (*file_buffer)[15]; GFX2_Log(GFX2_DEBUG, "C64_unpack_draz() \"%.13s\" RLE code=$%02X RLE data length=%ld\n", *file_buffer + 2, RLE_code, file_size - 16); // First pass to know unpacked size end = *file_buffer + file_size; for (current = *file_buffer + 16; current < end; current++) { if (*current == RLE_code) { current++; unpacked_size += *current++; } else unpacked_size++; } GFX2_Log(GFX2_DEBUG, "C64_unpack_draz() unpacked_size=%ld\n", unpacked_size); // 2nd pass to unpack unpacked_buffer = malloc(unpacked_size); if (unpacked_buffer == NULL) return -1; unpacked = unpacked_buffer; for (current = *file_buffer + 16; current < end; current++) { if (*current == RLE_code) { byte count; current++; count = *current++; while (count-- > 0) *unpacked++ = *current; } else *unpacked++ = *current; } free(*file_buffer); *file_buffer = unpacked_buffer; return unpacked_size; } /** * Load C64 pictures formats. * * Supports: * - Hires (with or without ScreenRAM) * - Multicolor (Koala or CDU-paint format) * - FLI * * see http://unusedino.de/ec64/technical/formats/bitmap.html * * @param context the IO context */ void Load_C64(T_IO_Context * context) { FILE* file; long file_size; byte hasLoadAddr=0; word load_addr; enum c64_format loadFormat = F_invalid; byte *file_buffer; byte *bitmap, *screen_ram, *color_ram=NULL, *background=NULL; // Only pointers to existing data byte *temp_buffer = NULL; word width, height=200; file = Open_file_read(context); if (file) { File_error=0; file_size = File_length_file(file); // Load entire file in memory file_buffer=(byte *)malloc(file_size); if (!file_buffer) { File_error = 1; fclose(file); return; } if (!Read_bytes(file,file_buffer,file_size)) { File_error = 1; free(file_buffer); fclose(file); return; } fclose(file); // get load address (valid only if hasLoadAddr = 1) load_addr = file_buffer[0] | (file_buffer[1] << 8); // Unpack if needed if (memcmp(file_buffer + 2, "DRAZPAINT", 9) == 0) file_size = C64_unpack_draz(&file_buffer, file_size); else if(load_addr == 0x4000 && file_buffer[file_size-2] == 0xC2 && file_buffer[file_size-1] == 0) file_size = C64_unpack_amica(&file_buffer, file_size); switch (file_size) { case 8000: // raw bitmap hasLoadAddr=0; loadFormat=F_bitmap; bitmap=file_buffer+0; // length: 8000 screen_ram=NULL; break; case 8002: // raw bitmap with loadaddr hasLoadAddr=1; loadFormat=F_bitmap; bitmap=file_buffer+2; // length: 8000 screen_ram=NULL; break; case 9000: // bitmap + ScreenRAM hasLoadAddr=0; loadFormat=F_hires; bitmap=file_buffer+0; // length: 8000 screen_ram=file_buffer+8000; // length: 1000 break; case 9003: // bitmap + ScreenRAM + loadaddr (+ border ?) case 9002: // bitmap + ScreenRAM + loadaddr hasLoadAddr=1; loadFormat=F_hires; bitmap=file_buffer+2; // length: 8000 screen_ram=file_buffer+8002; // length: 1000 break; case 9009: // Art Studio (.aas) hasLoadAddr=1; loadFormat=F_hires; bitmap=file_buffer+2; // length: 8000 screen_ram=file_buffer+8002; // length: 1000 break; case 9218: // Doodle (.dd) hasLoadAddr=1; loadFormat=F_hires; screen_ram=file_buffer+2; // length: 1000 (+24 padding) bitmap=file_buffer+1024+2; // length: 8000 break; case 9332: // Paint Magic .pmg hasLoadAddr=1; loadFormat=F_multi; // Display routine between offset $0002 and $0073 (114 bytes) // duplicated between offset $2002 and $2073 bitmap=file_buffer+114+2; // $0074 background=file_buffer+8000+114+2;// $1FB4 temp_buffer=malloc(1000); memset(temp_buffer, file_buffer[3+8000+114+2], 1000); // color RAM Byte color_ram=temp_buffer; //border byte = file_buffer[4+8000+114+2]; screen_ram=file_buffer+8192+114+2; // $2074 break; case 10001: // multicolor hasLoadAddr=0; loadFormat=F_multi; bitmap=file_buffer+0; // length: 8000 screen_ram=file_buffer+8000; // length: 1000 color_ram=file_buffer+9000; // length: 1000 background=file_buffer+10000; // only 1 break; case 10003: // multicolor + loadaddr case 10004: // extra byte is border color case 10006: // Run Paint hasLoadAddr=1; loadFormat=F_multi; bitmap=file_buffer+2; // length: 8000 screen_ram=file_buffer+8002; // length: 1000 color_ram=file_buffer+9002; // length: 1000 background=file_buffer+10002; // only 1 break; case 10018: // Advanced Art Studio (.ocp) + loadaddr hasLoadAddr=1; loadFormat=F_multi; bitmap=file_buffer+2; // length: 8000 screen_ram=file_buffer+8000+2; // length: 1000 color_ram=file_buffer+9016+2; // length: 1000 // filebuffer+9000+2 is border background=file_buffer+9001+2; // only 1 break; case 10022: // Micro Illustrator (.mil) hasLoadAddr=1; loadFormat=F_multi; screen_ram=file_buffer+20+2; color_ram=file_buffer+1000+20+2; bitmap=file_buffer+2*1000+20+2; break; case 10049: // unpacked DrazPaint hasLoadAddr=1; loadFormat=F_multi; color_ram=file_buffer; // length: 1000 + (padding 24) screen_ram=file_buffer+1024; // length: 1000 + (padding 24) bitmap=file_buffer+1024*2; // length: 8000 background=file_buffer+8000+1024*2; break; case 10050: // Picasso64 multicolor + loadaddr hasLoadAddr=1; loadFormat=F_multi; color_ram=file_buffer+2; // length: 1000 + (padding 24) screen_ram=file_buffer+1024+2; // length: 1000 + (padding 24) bitmap=file_buffer+1024*2+2; // length: 8000 background=file_buffer+1024*2+2-1; // only 1 break; case 10218: // Image System hasLoadAddr=1; loadFormat=F_multi; color_ram=file_buffer+2; // Length: 1000 (+ padding 24) bitmap=file_buffer+1024+2; // Length: 8000 (+padding 192) screen_ram=file_buffer+8192+1024+2; // Length: 1000 (no padding) background=file_buffer+8192+1024+2-1; // only 1 break; case 10219: // Saracen Paint (.sar) hasLoadAddr=1; loadFormat=F_multi; screen_ram=file_buffer+2; // Length: 1000 (+ padding24) background=file_buffer+1008+2; // offset 0x3F0 (only 1 byte) bitmap=file_buffer+1024+2; // Length: 8000 (+padding 192) color_ram=file_buffer+8192+1024+2; // Length: 1000 (+ padding 24) break; case 10242: // Artist 64/Blazing Paddles/Rainbow Painter multicolor + loadaddr hasLoadAddr=1; loadFormat=F_multi; switch(load_addr) { default: case 0x4000: // Artist 64 bitmap=file_buffer+2; // length: 8000 (+padding 192) screen_ram=file_buffer+8192+2; // length: 1000 + (padding 24) color_ram=file_buffer+1024+8192+2; // length: 1000 + (padding 24) background=file_buffer+1024*2+8192+2-1; // only 1 break; case 0xa000: // Blazing Paddles bitmap=file_buffer+2; // length: 8000 (+padding 192) screen_ram=file_buffer+8192+2; // length: 1000 + (padding 24) color_ram=file_buffer+1024+8192+2; // length: 1000 + (padding 24) background=file_buffer+8064+2; // only 1 break; case 0x5c00: // Rainbow Painter screen_ram=file_buffer+2; // length: 1000 + (padding 24) bitmap=file_buffer+1024+2; // length: 8000 (+padding 192) color_ram=file_buffer+1024+8192+2; // length: 1000 + (padding 24) background=file_buffer; // only 1 break; } break; case 10257: // unpacked Amica Paint (.ami) hasLoadAddr=1; loadFormat=F_multi; bitmap=file_buffer; // length 8000 screen_ram=file_buffer+8000; // length: 1000 color_ram=file_buffer+1000+8000;// length:1000 background=file_buffer+2*1000+8000;//1 // remaining bytes (offset 10001, length 256) are a "Color Rotation Table" // we should decode if we learn its format... break; case 10277: // multicolor CDU-Paint + loadaddr hasLoadAddr=1; loadFormat=F_multi; // 273 bytes of display routine bitmap=file_buffer+275; // length: 8000 screen_ram=file_buffer+8275; // length: 1000 color_ram=file_buffer+9275; // length: 1000 background=file_buffer+10275; // only 1 break; case 17472: // FLI (BlackMail) hasLoadAddr=0; loadFormat=F_fli; background=file_buffer+0; // length: 200 (+ padding 56) color_ram=file_buffer+256; // length: 1000 (+ padding 24) screen_ram=file_buffer+1280; // length: 8192 bitmap=file_buffer+9472; // length: 8000 break; case 17474: // FLI (BlackMail) + loadaddr hasLoadAddr=1; loadFormat=F_fli; background=file_buffer+2; // length: 200 (+ padding 56) color_ram=file_buffer+258; // length: 1000 (+ padding 24) screen_ram=file_buffer+1282; // length: 8192 bitmap=file_buffer+9474; // length: 8000 break; case 17409: // FLI-Designer v1.1 (+loadaddr) case 17410: // => FLI MATIC (background at 2+1024+8192+8000+65 ?) hasLoadAddr=1; loadFormat=F_fli; background=NULL; color_ram=file_buffer+2; // length: 1000 (+ padding 24) screen_ram=file_buffer+1024+2; // length: 8192 bitmap=file_buffer+8192+1024+2; // length: 8000 break; case 17665: // FLI Editor hasLoadAddr=1; loadFormat=F_fli; background=file_buffer+8; color_ram=file_buffer+256+2; // length: 1000 (+ padding 24) screen_ram=file_buffer+1024+256+2; // length: 8192 bitmap=file_buffer+8192+1024+256+2; // length: 8000 break; default: File_error = 1; free(file_buffer); return; } if (loadFormat == F_invalid) { File_error = 1; free(file_buffer); return; } if (loadFormat == F_fli || loadFormat == F_multi) { context->Ratio = PIXEL_WIDE; width = 160; } else { context->Ratio = PIXEL_SIMPLE; width = 320; } // Write detailed format in comment if (hasLoadAddr) snprintf(context->Comment,COMMENT_SIZE+1,"%s, load at $%4.4X",c64_format_names[loadFormat],load_addr); else snprintf(context->Comment,COMMENT_SIZE+1,"%s, no addr",c64_format_names[loadFormat]); Pre_load(context, width, height, file_size, FORMAT_C64, context->Ratio, (loadFormat == F_bitmap) ? 1 : 4); // Do this as soon as you can if (Config.Clear_palette) memset(context->Palette,0, sizeof(T_Palette)); C64_set_palette(context->Palette); context->Transparent_color=16; switch(loadFormat) { case F_fli: Load_C64_fli(context,bitmap,screen_ram,color_ram,background); break; case F_multi: Load_C64_multi(context,bitmap,screen_ram,color_ram, (background==NULL) ? 0 : *background); break; default: Load_C64_hires(context,bitmap,screen_ram); } free(file_buffer); if (temp_buffer) free(temp_buffer); } else File_error = 1; } /** * Display the dialog for C64 save parameters * * @param[in,out] saveFormat one of the C64 mode from @ref c64_format * @param[in,out] saveWhat 0=All, 1=Only bitmap, 2=Only Screen RAM, 3=Only color RAM * @param[in,out] loadAddr actual load address/0x2000 or 0 for "None" * @return true to proceed, false to abort */ static int Save_C64_window(enum c64_format *saveFormat, byte *saveWhat, byte *loadAddr) { int button; unsigned int i; T_Dropdown_button *what, *addr; T_Dropdown_button *format; static const char * what_label[] = { "All", "Bitmap", "Screen", "Color" }; static const char * address_label[] = { "None", "$2000", "$4000", "$6000", "$8000", "$A000", "$C000", "$E000" }; Open_window(200,120,"C64 saving settings"); Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1 Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2 Print_in_window(13,18,"Data:",MC_Dark,MC_Light); what = Window_set_dropdown_button(10,28,90,15,70,what_label[*saveWhat],1, 0, 1, LEFT_SIDE,0); // 3 Window_dropdown_clear_items(what); for (i=0; i2) { Warning_with_format("More than 2 colors\nin 8x8 pixel cell: (%d, %d)\nRect: (%d, %d, %d, %d)", cx, cy, cx * 8, cy * 8, cx * 8 + 7, cy * 8 + 7); // TODO here we should hilite the offending block return 1; } 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; } } } file = Open_file_write(context); 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; } #ifdef __GNUC__ /* use GCC built in's */ #define count_set_bits __builtin_popcount #define count_trailing_zeros __builtin_ctz #else /** * Count the number of bit sets */ static int count_set_bits(unsigned int value) { int count; for (count = 0; value != 0; value >>= 1) count += (value & 1); return count; } /** * Count the number of low order zero's before the first bit set */ static int count_trailing_zeros(unsigned int value) { int count; if (value == 0) return -1; for (count = 0; (value & 1) == 0; value >>= 1) count++; return count; } #endif /** * Save a C64 FLI (Flexible Line Interpretation) picture. * * This function is able to save a one layer picture, by finding * itself the background colors and color RAM value to be used. * * The algorithm is : * - first choose the lowest value for all possible background colors for each line * - first the lowest value from the possible colors for color RAM * - encode bitmap and screen RAMs * * The algorithm can fail by picking a "wrong" background color for a line, * that make the choice for the color RAM value of one of the 40 blocks impossible. * * @param context the IO context * @param saveWhat what part of the data to save * @param loadAddr The load address */ int Save_C64_fli_monolayer(T_IO_Context *context, byte saveWhat, byte loadAddr) { FILE * file; int bx, by; // 4x8 block coordinates int cx, cy; // coordinates inside block byte bitmap[8000],screen_ram[1024*8],color_ram[1024]; byte background[256]; byte pixel; memset(bitmap, 0, sizeof(bitmap)); memset(screen_ram, 0, sizeof(screen_ram)); memset(color_ram, 0, sizeof(color_ram)); memset(background, 0, sizeof(background)); for (by = 0; by < 25; by++) { word background_possible[8]; word color_ram_possible[40]; for(cy = 0; cy < 8; cy++) background_possible[cy] = 0xffff; for(bx = 0; bx < 40; bx++) color_ram_possible[bx] = 0xffff; // first we try to find the background color of the 8 lines // and the Color RAM (color 11) of the 40 blocks for(cy = 0; cy < 8; cy++) { for(bx = 0; bx < 40; bx++) { word colors_used = 0; int n_color_used; for(cx = 0; cx < 4; cx++) { pixel = Get_pixel(context, bx*4+cx, by*8+cy); // if (pixel > 15) error colors_used |= 1 << pixel; } n_color_used = count_set_bits(colors_used); if (n_color_used == 4) { background_possible[cy] &= colors_used; // The background is among the color used in this 4x1 block color_ram_possible[bx] &= colors_used; // The color 11 is among the color used in this 4x1 block } } } // Choose background (default is black #0) for(cy = 0; cy < 8; cy++) { int n_possible_backgrounds = count_set_bits(background_possible[cy]); if (n_possible_backgrounds == 0) { //ERROR Warning_with_format("No possible background color for line %d.\n4x1 pixel blocks using 4 different colors must share at least one color.", by*8+cy); return 1; } else { // pick the first one background[by*8+cy] = count_trailing_zeros(background_possible[cy]); #ifdef _DEBUG if (background[by*8+cy] != 0) GFX2_Log(GFX2_DEBUG, " y=%d background_possible=$%04x (count=%d) background color=#%d\n", by*8+cy, background_possible[cy], n_possible_backgrounds, background[by*8+cy]); #endif } } // 2nd pass for color RAM values for(cy = 0; cy < 8; cy++) { for(bx = 0; bx < 40; bx++) { word colors_used = 0; int n_color_used; for(cx = 0; cx < 4; cx++) { pixel = Get_pixel(context, bx*4+cx, by*8+cy); colors_used |= 1 << pixel; } colors_used &= ~(1 << background[by*8+cy]); // remove background n_color_used = count_set_bits(colors_used); if (n_color_used == 3) { color_ram_possible[bx] &= colors_used; // The color 11 is among the color used in this 4x1 block } } } // choose the color RAM values (default to #0) for(bx = 0; bx < 40; bx++) { int n_possibles_color11 = count_set_bits(color_ram_possible[bx]); if (n_possibles_color11 == 0) { Warning_with_format("No possible color RAM value for 4x8 block (%d,%d) coordinates (%d,%d)\nThe 8 4x1 blocks must share a color.", bx, by, bx*4, by*8); return 1; } else { color_ram[by*40+bx] = count_trailing_zeros(color_ram_possible[bx]); #ifdef _DEBUG if (color_ram[by*40+bx] != 0) GFX2_Log(GFX2_DEBUG, "bx=%d by=%d color_ram_possible=%04x (count=%d) color11=#%d\n", bx, by, color_ram_possible[bx], n_possibles_color11, color_ram[by*40+bx]); #endif } } // Now it is possible to encode Screen RAM and Bitmap for(cy = 0; cy < 8; cy++) { for(bx = 0; bx < 40; bx++) { byte bits = 0; byte c[4]; c[0] = background[by*8+cy]; // color 00 = background c[1] = c[0]; // color 01 (defaulting to same as background) c[2] = c[1]; // color 10 (defaulting to same as background) c[3] = color_ram[by*40+bx]; // color 11 = color RAM value for(cx = 0; cx < 4; cx++) { bits <<= 2; pixel = Get_pixel(context, bx*4+cx, by*8+cy); if (pixel == c[0]) // background => color 00 continue; if(pixel == c[3]) // color RAM value => color 11 bits |= 3; else { if (c[1] == c[0]) c[1] = pixel; // set color 01 = upper nibble of screen RAM if (pixel == c[1]) bits |= 1; else { c[2] = pixel; // set color 02 = lower nibble of screen RAM bits |= 2; } } } screen_ram[1024*cy + bx + by * 40] = (c[1] << 4) | c[2]; bitmap[(by*40 + bx)*8 + cy] = bits; } } } file = Open_file_write(context); if(!file) { Warning_message("File open failed"); File_error = 1; return 1; } if (loadAddr) { Write_byte(file, 0); Write_byte(file, loadAddr); } if (saveWhat==0) Write_bytes(file,background,256); // Background colors for lines 0-199 (+ 56bytes padding) if (saveWhat==0 || saveWhat==3) Write_bytes(file,color_ram,1024); // Color RAM (1000 bytes + padding 24) if (saveWhat==0 || saveWhat==1) Write_bytes(file,screen_ram,8192); // Screen RAMs 8 x (1000 bytes + padding 24) if (saveWhat==0 || saveWhat==2) Write_bytes(file,bitmap,8000); // BitMap fclose(file); return 0; } /** * Save a C64 multicolor picture * * @param context the IO context * @param saveWhat what part of the data to save * @param loadAddr The load address */ int Save_C64_multi(T_IO_Context *context, byte saveWhat, 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; dword cusage[256]; byte i,background=0; FILE *file; // Detect the background color the image should be using. It's the one that's // used on all tiles having 4 colors. for(y=0;y<200;y=y+8) { for (x = 0; x<160; x=x+4) { cols = 0; // Compute the usage count of each color in the tile for (cy=0;cy<8;cy++) for (cx=0;cx<4;cx++) { pixel=Get_pixel(context, x+cx,y+cy); if(pixel>15) { Warning_message("Color above 15 used"); // TODO hilite as in hires, you should stay to // the fixed 16 color palette return 1; } cols |= (1 << pixel); } cand = 0; used = 0; // Count the number of used colors in the tile for (n = 0; n<16; n++) { if (cols & (1 << n)) used++; } if (used>3) { GFX2_Log(GFX2_DEBUG, "(%3d,%3d) used=%d cols=%04x\n", x, y, used,(unsigned)cols); // This is a tile that uses the background color (and 3 others) // Try to guess which color is most likely the background one for (n = 0; n<16; n++) { if ((cols & (1 << n)) && !((candidates | invalids) & (1 << n))) { // This color is used in this tile but // was not used in any other tile yet, // so it could be the background one. candidates |= 1 << n; } if ((cols & (1 << n)) == 0 ) { // This color isn't used at all in this tile: // Can't be the global background invalids |= 1 << n; candidates &= ~(1 << n); } if (candidates & (1 << n)) { // We have a candidate, mark it as such cand++; } } // After checking the constraints for this tile, do we have // candidate background colors left ? if (cand==0) { Warning_message("No possible global background color"); return 1; } } } } // Now just pick the first valid candidate for (n = 0; n<16; n++) { if (candidates & (1 << n)) { background = n; break; } } GFX2_Log(GFX2_DEBUG, "Save_C64_multi() background=%d ($%x) candidates=%x invalid=%x\n", (int)background, (int)background, (unsigned)candidates, (unsigned)invalids); // Now that we know which color is the background, we can encode the cells for(cy=0; cy<25; cy++) { for(cx=0; cx<40; cx++) { numcolors=Count_used_colors_area(cusage,cx*4,cy*8,4,8); if(numcolors>4) { Warning_with_format("More than 4 colors\nin 4x8 pixel cell: (%d, %d)\nRect: (%d, %d, %d, %d)", cx, cy, cx * 4, cy * 8, cx * 4 + 3, cy * 8 + 7); // TODO hilite offending block return 1; } color=1; c[0]=background; for(i=0; i<16; i++) { lut[i]=0; if(cusage[i] && (i!=background)) { lut[i]=color; c[color]=i; color++; } } // add to screen_ram and color_ram screen_ram[cx+cy*40]=c[1]<<4|c[2]; color_ram[cx+cy*40]=c[3]; for(y=0;y<8;y++) { bits=0; for(x=0;x<4;x++) { pixel = Get_pixel(context, cx*4+x,cy*8+y); bits = (bits << 2) | lut[pixel]; } bitmap[pos++]=bits; } } } file = Open_file_write(context); if(!file) { Warning_message("File open failed"); File_error = 2; return 2; } setvbuf(file, NULL, _IOFBF, 64*1024); if (loadAddr) { Write_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); return 0; } /** * Save a C64 FLI (Flexible Line Interpretation) picture. * * This function need a 3 layer image : * - layer 0 is background colors * - layer 1 is color RAM values (4x8 blocks) * - layer 2 is the actual picture * * @param context the IO context * @param saveWhat what part of the data to save * @param loadAddr The load address */ int Save_C64_fli(T_IO_Context * context, byte saveWhat, byte loadAddr) { FILE *file; byte file_buffer[17474]; memset(file_buffer,0,sizeof(file_buffer)); switch(C64_FLI(file_buffer+9474, file_buffer+1282, file_buffer+258, file_buffer+2)) { case 0: // OK break; case 1: Warning_message("Less than 3 layers"); File_error=1; return 1; case 2: Warning_message("Picture must be 160x200"); File_error=1; return 1; default: File_error=1; return 1; } file = Open_file_write(context); if(!file) { Warning_message("File open failed"); File_error = 1; return 1; } if (loadAddr) { file_buffer[0]=0; file_buffer[1]=loadAddr; Write_bytes(file,file_buffer,2); } if (saveWhat==0) Write_bytes(file,file_buffer+2,256); // Background colors for lines 0-199 (+ 56bytes padding) if (saveWhat==0 || saveWhat==3) Write_bytes(file,file_buffer+258,1024); // Color RAM (1000 bytes + padding 24) if (saveWhat==0 || saveWhat==1) Write_bytes(file,file_buffer+1282,8192); // Screen RAMs 8 x (1000 bytes + padding 24) if (saveWhat==0 || saveWhat==2) Write_bytes(file,file_buffer+9474,8000); // BitMap fclose(file); return 0; } /** * Save C64 picture. * * Supports : * - HiRes (320x200) * - Multicolor * - FLI * * @param context the IO context */ void Save_C64(T_IO_Context * context) { enum c64_format saveFormat = F_invalid; static byte saveWhat=0, loadAddr=0; if (((context->Width!=320) && (context->Width!=160)) || context->Height!=200) { Warning_message("must be 320x200 or 160x200"); File_error = 1; return; } saveFormat = (context->Width == 320) ? F_hires : F_multi; GFX2_Log(GFX2_DEBUG, "Save_C64() extension : %s\n", context->File_name + strlen(context->File_name) - 4); if (strcasecmp(context->File_name + strlen(context->File_name) - 4, ".fli") == 0) saveFormat = F_fli; if(!Save_C64_window(&saveFormat, &saveWhat,&loadAddr)) { File_error = 1; return; } switch (saveFormat) { case F_fli: if (Main.backups->Pages->Nb_layers < 3) File_error = Save_C64_fli_monolayer(context, saveWhat, loadAddr); else File_error = Save_C64_fli(context, saveWhat, loadAddr); break; case F_multi: File_error = Save_C64_multi(context, saveWhat, loadAddr); break; case F_bitmap: saveWhat = 1; // force save bitmap #if defined(__GNUC__) && (__GNUC__ >= 7) __attribute__ ((fallthrough)); #endif case F_hires: default: File_error = Save_C64_hires(context, saveWhat, loadAddr); } } /** * Test for SCR file (Amstrad CPC) * * SCR file format is from "Advanced OCP Art Studio" : * http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats * * .WIN "window" format is also supported. * * For now we check the presence of a valid PAL file. * If the PAL file is not there the pixel data may still be valid. * The file size depends on the screen resolution. * An AMSDOS header would be a good indication but in some cases it may not * be there. */ void Test_SCR(T_IO_Context * context, FILE * file) { FILE * pal_file; unsigned long pal_size, file_size; byte mode, color_anim_flag; (void)file; File_error = 1; file_size = File_length_file(file); if (file_size > 16384+128) return; // requires the PAL file pal_file = Open_file_read_with_alternate_ext(context, "pal"); if (pal_file == NULL) return; pal_size = File_length_file(pal_file); if (pal_size == 239+128) { if (!CPC_check_AMSDOS(pal_file, NULL, NULL)) { fclose(pal_file); return; } fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header } else if (pal_size != 239) { fclose(pal_file); return; } if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag)) { fclose(pal_file); return; } GFX2_Log(GFX2_DEBUG, "Test_SCR() mode=%d color animation flag %02X\n", mode, color_anim_flag); if (mode <= 2 && (color_anim_flag == 0 || color_anim_flag == 0xff)) File_error = 0; fclose(pal_file); } /** * Load Advanced OCP Art Studio files (Amstrad CPC) * * Only standard resolution files (Mode 0 160x200, mode 1 320x200 and * mode 2 640x200) are supported. The .PAL file presence is required. * "MJH" RLE packing is supported. * * .WIN "window" format is also supported. * * @todo Ask user for screen size (or register values) in order to support * non standard resolutions. */ void Load_SCR(T_IO_Context * context) { // The Amstrad CPC screen memory is mapped in a weird mode, somewhere // between bitmap and textmode. Basically the only way to decode this is to // emulate the video chip and read the bytes as needed... // Moreover, the hardware allows the screen to have any size from 8x1 to // 800x273 pixels, and there is no indication of that in the file besides // its size. It can also use any of the 3 screen modes. Fortunately this // last bit of information is stored in the palette file. // Oh, and BTW, the picture can be offset, and it's even usual to do it, // because letting 128 pixels unused at the beginning of the file make it a // lot easier to handle screens using more than 16K of VRam. // The pixel encoding change with the video mode so we have to know that // before attempting to load anything... // As if this wasn't enough, Advanced OCP Art Studio, the reference tool on // Amstrad, can use RLE packing when saving files, meaning we also have to // handle that. // All this mess enforces us to load (and unpack if needed) the file to a // temporary 32k buffer before actually decoding it. FILE * pal_file, * file; unsigned long real_file_size, file_size, amsdos_file_size = 0; byte mode, color_anim_flag, color_anim_delay; byte pal_data[236]; // 12 palettes of 16+1 colors + 16 excluded inks + 16 protected inks word width, height = 200; byte bpp; enum PIXEL_RATIO ratio; byte * pixel_data; word x, y; int i; byte sig[3]; word block_length; word win_width, win_height; int is_win = 0; int columns = 80; File_error = 1; // requires the PAL file pal_file = Open_file_read_with_alternate_ext(context, "pal"); if (pal_file == NULL) return; file_size = File_length_file(pal_file); if (file_size == 239+128) { if (!CPC_check_AMSDOS(pal_file, NULL, NULL)) { fclose(pal_file); return; } fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header } if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag) || !Read_byte(pal_file, &color_anim_delay) || !Read_bytes(pal_file, pal_data, 236)) { GFX2_Log(GFX2_WARNING, "Load_SCR() failed to load .PAL file\n"); fclose(pal_file); return; } fclose(pal_file); GFX2_Log(GFX2_DEBUG, "Load_SCR() mode=%d color animation flag=%02X delay=%u\n", mode, color_anim_flag, color_anim_delay); switch (mode) { case 0: width = 160; bpp = 4; ratio = PIXEL_WIDE; break; case 1: width = 320; bpp = 2; ratio = PIXEL_SIMPLE; break; case 2: width = 640; bpp = 1; ratio = PIXEL_TALL; break; default: return; // unsupported } if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); // Setup the palette (amstrad hardware palette) CPC_set_HW_palette(context->Palette + 0x40); // Set the palette for this picture for (i = 0; i < 16; i++) context->Palette[i] = context->Palette[pal_data[12*i]]; file = Open_file_read(context); if (file == NULL) return; file_size = File_length_file(file); real_file_size = file_size; if (CPC_check_AMSDOS(file, NULL, &amsdos_file_size)) { if (file_size < (amsdos_file_size + 128)) { GFX2_Log(GFX2_ERROR, "Load_SCR() mismatch in file size. AMSDOS file size %lu, should be %lu\n", amsdos_file_size, file_size - 128); fclose(file); return; } else if (file_size > (amsdos_file_size + 128)) GFX2_Log(GFX2_INFO, "Load_SCR() %lu extra bytes at end of file\n", file_size - 128 - amsdos_file_size); fseek(file, 128, SEEK_SET); // right after AMSDOS header file_size = amsdos_file_size; } else fseek(file, 0, SEEK_SET); if (file_size > 16384) // we don't support bigger files yet { fclose(file); return; } if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) { fclose(file); return; } fseek(file, -5, SEEK_CUR); pixel_data = malloc(16384); memset(pixel_data, 0, 16384); if (0 != memcmp(sig, "MJH", 3) || block_length > 16384) { // raw data Read_bytes(file, pixel_data, file_size); i = file_size; } else { // MJH packed format i = 0; do { if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) break; if (0 != memcmp(sig, "MJH", 3)) break; GFX2_Log(GFX2_DEBUG, " %.3s block %u\n", sig, block_length); file_size -= 5; while (block_length > 0) { byte code; if (!Read_byte(file, &code)) break; file_size--; if (code == 1) { byte repeat, value; if (!Read_byte(file, &repeat) || !Read_byte(file, &value)) break; file_size -= 2; do { pixel_data[i++] = value; block_length--; } while(--repeat != 0); } else { pixel_data[i++] = code; block_length--; } } GFX2_Log(GFX2_DEBUG, " unpacked %d bytes. remaining bytes in file=%lu\n", i, file_size); } while(file_size > 0 && i < 16384); } fclose(file); if (i > 5) { win_width = pixel_data[i-4] + (pixel_data[i-3] << 8); // in bits win_height = pixel_data[i-2]; if (((win_width + 7) >> 3) * win_height + 5 == i) // that's a WIN file ! { width = win_width >> (2 - mode); height = win_height; is_win = 1; columns = (win_width + 7) >> 3; GFX2_Log(GFX2_DEBUG, ".WIN file detected len=%d (%d,%d) %dcols %02X %02X %02X %02X %02X\n", i, width, height, columns, pixel_data[i-5], pixel_data[i-4], pixel_data[i-3], pixel_data[i-2], pixel_data[i-1]); } else { ///@todo guess the picture size, or ask the user GFX2_Log(GFX2_DEBUG, ".SCR file. Data length %d\n", i); // i <= 16384 && i >= 16336 => Standard resolution } } Pre_load(context, width, height, real_file_size, FORMAT_SCR, ratio, bpp); for (y = 0; y < height; y++) { const byte * line; if (is_win) line = pixel_data + y * columns; else line = pixel_data + ((y & 7) << 11) + ((y >> 3) * columns); x = 0; for (i = 0; i < columns; i++) { byte pixels = line[i]; switch (mode) { case 0: Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); break; case 1: do { // upper nibble is 4 lower color bits, lower nibble is 4 upper color bits Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2); pixels <<= 1; } while ((x & 3) != 0); break; case 2: do { Set_pixel(context, x++, y, (pixels & 0x80) >> 7); pixels <<= 1; } while ((x & 7) != 0); } } } free(pixel_data); File_error = 0; } /** * Save Amstrad SCR file * * guess mode from aspect ratio : * - normal pixels are mode 1 * - wide pixels are mode 0 * - tall pixels are mode 2 * * Mode and palette are stored in a .PAL file. * * The picture color index should be 0-15, * The CPC Hardware palette is expected to be set (indexes 64 to 95) * * @todo Add possibility to set R9, R12, R13 values * @todo Add OCP packing support * @todo Add possibility to include AMSDOS header, with proper loading * address guessed from r12/r13 values. */ void Save_SCR(T_IO_Context * context) { int i, j; unsigned char* output; unsigned long outsize = 0; unsigned char r1 = 0; int cpc_mode; FILE* file; switch(Pixel_ratio) { case PIXEL_WIDE: case PIXEL_WIDE2: cpc_mode = 0; break; case PIXEL_TALL: case PIXEL_TALL2: case PIXEL_TALL3: cpc_mode = 2; break; default: cpc_mode = 1; break; } file = Open_file_write_with_alternate_ext(context, "pal"); if (file == NULL) return; if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0)) { fclose(file); return; } for (i = 0; i < 16; i++) { // search for the color in the HW palette (0x40-0x5F) byte index = 0x40; while ((index < 0x60) && (0 != memcmp(context->Palette + i, context->Palette + index, sizeof(T_Components)))) index++; if (index >= 0x60) { GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i); index = 0x54 - i; // default } for (j = 0; j < 12; j++) // write the same color for the 12 frames { Write_byte(file, index); } } // border for (j = 0; j < 12; j++) { Write_byte(file, 0x54); // black } // excluded inks for (i = 0; i < 16; i++) { Write_byte(file, 0); } // protected inks for (i = 0; i < 16; i++) { Write_byte(file, 0); } fclose(file); output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0); GFX2_Log(GFX2_DEBUG, "Save_SCR() output=%p outsize=%lu r1=$%02X\n", output, outsize, r1); if (output == NULL) return; file = Open_file_write(context); if (file == NULL) File_error = 1; else { File_error = 0; if (!Write_bytes(file, output, outsize)) File_error = 1; fclose(file); } free (output); } /** * Test for CM5 - Amstrad CPC "Mode 5" picture * * This is a format designed by SyX. * There is one .GFX file in the usual amstrad format * and a .CM5 file with the palette, which varies over time. * * CM5 file is 2049 bytes, GFX is 18432 bytes. * * @todo check CM5 contains only valid values [0x40-0x5f] */ void Test_CM5(T_IO_Context * context, FILE * file) { // check cm5 file size == 2049 bytes FILE *file_gfx; long file_size; File_error = 1; file_size = File_length_file(file); if (file_size != 2049) return; // check existence of a .GFX file with the same name file_gfx = Open_file_read_with_alternate_ext(context, "gfx"); if (file_gfx == NULL) return; file_size = File_length_file(file_gfx); fclose(file_gfx); if (file_size != 18432) return; File_error = 0; } /** * Load Amstrad CPC "Mode 5" picture */ void Load_CM5(T_IO_Context* context) { // Ensure "8bit" constraint mode is switched on // Set palette to the CPC hardware colors // Load the palette data to the 4 colorlayers FILE *file; byte value = 0; int mod=0; short line = 0; int tx, ty; // for preview : byte ink0; byte ink1[256]; byte ink2[256]; byte ink3[256*6]; if (!(file = Open_file_read(context))) { File_error = 1; return; } Pre_load(context, 48*6, 256, 2049, FORMAT_CM5, PIXEL_SIMPLE, 0); if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); // Setup the palette (amstrad hardware palette) CPC_set_HW_palette(context->Palette + 0x40); if (!Read_byte(file, &ink0)) File_error = 2; // This forces the creation of 5 layers total : // Needed because the "pixel" functions will seek layer 4 Set_loading_layer(context, 4); // Now select layer 1 again Set_loading_layer(context, 0); if (context->Type == CONTEXT_MAIN_IMAGE) { Main.backups->Pages->Image_mode = IMAGE_MODE_MODE5; // Fill layer with color we just read (Layer 1 - INK 0) for(ty=0; tyHeight; ty++) for(tx=0; txWidth; tx++) Set_pixel(context, tx, ty, ink0); } while(Read_byte(file, &value)) { switch(mod) { case 0: // This is color for layer 2 - INK 1 Set_loading_layer(context, 1); for(tx=0; txWidth; tx++) Set_pixel(context, tx, line, value); ink1[line] = value; break; case 1: // This is color for layer 3 - INK 2 Set_loading_layer(context, 2); for(tx=0; txWidth; tx++) Set_pixel(context, tx, line, value); ink2[line] = value; break; default: // This is color for a block in layer 4 - INK 3 Set_loading_layer(context, 3); for(tx=(mod-2)*48; tx<(mod-1)*48; tx++) Set_pixel(context, tx, line, value); ink3[line*6+(mod-2)] = value; break; } mod++; if (mod > 7) { mod = 0; line++; } } fclose(file); // Load the pixeldata to the 5th layer file = Open_file_read_with_alternate_ext(context, "gfx"); if (file == NULL) { File_error = 1; return; } Set_loading_layer(context, 4); if (context->Type == CONTEXT_PREVIEW) for (ty = 0; ty < 256; ty++) for (tx = 0; tx < 48*6; ) { Read_byte(file, &value); for (mod = 0; mod < 4; mod++, tx++, value <<= 1) { switch(3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))) // INK { case 0: Set_pixel(context, tx, ty, ink0); break; case 1: Set_pixel(context, tx, ty, ink1[ty]); break; case 2: Set_pixel(context, tx, ty, ink2[ty]); break; default: Set_pixel(context, tx, ty, ink3[ty*6+(tx/48)]); } } } else for (ty = 0; ty < 256; ty++) for (tx = 0; tx < 48*6; ) { Read_byte(file, &value); Set_pixel(context, tx++, ty, 3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))); Set_pixel(context, tx++, ty, 3 ^ (((value&0x40) >> 6) | ((value&0x4)>>1))); Set_pixel(context, tx++, ty, 3 ^ (((value&0x20) >> 5) | ((value&0x2)>>0))); Set_pixel(context, tx++, ty, 3 ^ (((value&0x10) >> 4) | ((value&0x1)<<1))); } fclose(file); } void Save_CM5(T_IO_Context* context) { FILE* file; int tx, ty; // TODO: Check picture has 5 layers // TODO: Check the constraints on the layers // Layer 1 : 1 color Only // Layer 2 and 3 : 1 color/line // Layer 4 : 1 color / 48x1 block // TODO: handle filesize if (!(file = Open_file_write(context))) { File_error = 1; return; } setvbuf(file, NULL, _IOFBF, 64*1024); // Write layer 0 Set_saving_layer(context, 0); Write_byte(file, Get_pixel(context, 0, 0)); for(ty = 0; ty < 256; ty++) { Set_saving_layer(context, 1); Write_byte(file, Get_pixel(context, 0, ty)); Set_saving_layer(context, 2); Write_byte(file, Get_pixel(context, 0, ty)); Set_saving_layer(context, 3); for(tx = 0; tx < 6; tx++) { Write_byte(file, Get_pixel(context, tx*48, ty)); } } fclose(file); // Now the pixeldata if (!(file = Open_file_write_with_alternate_ext(context, "gfx"))) { File_error = 2; return; } setvbuf(file, NULL, _IOFBF, 64*1024); Set_saving_layer(context, 4); for (ty = 0; ty < 256; ty++) { for (tx = 0; tx < 48*6; tx+=4) { byte code = 0; byte pixel; pixel = 3-Get_pixel(context, tx+3, ty); code |= (pixel&2)>>1 | ((pixel & 1)<<4); pixel = 3-Get_pixel(context, tx+2, ty); code |= ((pixel&2)<<0) | ((pixel & 1)<<5); pixel = 3-Get_pixel(context, tx+1, ty); code |= ((pixel&2)<<1) | ((pixel & 1)<<6); pixel = 3-Get_pixel(context, tx, ty); code |= ((pixel&2)<<2) | ((pixel & 1)<<7); Write_byte(file, code); } } fclose(file); File_error = 0; } /* Amstrad CPC 'PPH' for Perfect Pix. // This is a format designed by Rhino. // There are 3 modes: // - Mode 'R': 1:1 pixels, 16 colors from the CPC 27 color palette. // (this is implemented on CPC as two pictures with wide pixels, the "odd" one // being shifted half a pixel to the right), and flipping) // - Mode 'B0': wide pixels, up to 126 out of 378 colors. // (this is implemented as two pictures with wide pixels, sharing the same 16 // color palette, and flipping) // - Mode 'B1': 1:1 pixels, 1 fixed color, up to 34 palettes of 9 colors // (actually 4 colors + flipping) // // - The standard CPC formats can also be encapsulated into a PPH file. */ void Test_PPH(T_IO_Context * context, FILE * file) { FILE *file_oddeve; byte buffer[6]; unsigned long file_size; unsigned int w, h; unsigned int expected; File_error = 1; // First check file size is large enough to hold the header file_size = File_length_file(file); if (file_size < 11) { File_error = 1; return; } // File is large enough for the header, now check if the data makes some sense if (!Read_bytes(file, buffer, 6)) return; if (buffer[0] > 5) { // Unknown mode File_error = 2; return; } w = buffer[1] | (buffer[2] << 8); if (w < 2 || w > 384) { // Invalid width File_error = 3; return; } h = buffer[3] | (buffer[4] << 8); if (h < 1 || h > 272) { // Invalid height File_error = 4; return; } if (buffer[5] < 1 || buffer[5] > 28) { // Invalid palettes count File_error = 5; return; } expected = 6; // Size of header switch(buffer[0]) { case 0: case 3: case 4: // Palette size should be 16 bytes, only 1 palette. if (buffer[5] != 1) { File_error = 7; return; } expected += 16; break; case 1: case 5: expected += buffer[5] * 5 - 1; break; case 2: // Palette size should be 2 bytes if (buffer[5] != 1) { File_error = 7; return; } expected += 2; break; } if (file_size != expected) { File_error = 6; return; } // check existence of .ODD/.EVE files with the same name // and the right size expected = w * h / 4; file_oddeve = Open_file_read_with_alternate_ext(context, "odd"); if (file_oddeve == NULL) return; file_size = File_length_file(file_oddeve); fclose (file_oddeve); if (file_size != expected) { File_error = 8; return; } file_oddeve = Open_file_read_with_alternate_ext(context, "eve"); if (file_oddeve == NULL) return; file_size = File_length_file(file_oddeve); fclose(file_oddeve); if (file_size != expected) { File_error = 8; return; } File_error = 0; } static uint8_t pph_blend(uint8_t a, uint8_t b) { uint32_t h,l; if (a > b) { h = a; l = b; } else { h = b; l = a; } return (23 * h + 9 * l) / 32; } void Load_PPH(T_IO_Context* context) { FILE *file; FILE *feven; // Read in the header uint8_t mode; uint16_t width; uint16_t height; uint8_t npal; int i,j; uint8_t a,b,c,d; int file_size; uint8_t pl[16]; static const T_Components CPCPAL[27] = { { 0x00, 0x02, 0x01 }, { 0x00, 0x02, 0x6B }, { 0x0C, 0x02, 0xF4 }, { 0x6C, 0x02, 0x01 }, { 0x69, 0x02, 0x68 }, { 0x6C, 0x02, 0xF2 }, { 0xF3, 0x05, 0x06 }, { 0xF0, 0x02, 0x68 }, { 0xF3, 0x02, 0xF4 }, { 0x02, 0x78, 0x01 }, { 0x00, 0x78, 0x68 }, { 0x0C, 0x7B, 0xF4 }, { 0x6E, 0x7B, 0x01 }, { 0x6E, 0x7D, 0x6B }, { 0x6E, 0x7B, 0xF6 }, { 0xF3, 0x7D, 0x0D }, { 0xF3, 0x7D, 0x6B }, { 0xFA, 0x80, 0xF9 }, { 0x02, 0xF0, 0x01 }, { 0x00, 0xF3, 0x6B }, { 0x0F, 0xF3, 0xF2 }, { 0x71, 0xF5, 0x04 }, { 0x71, 0xF3, 0x6B }, { 0x71, 0xF3, 0xF4 }, { 0xF3, 0xF3, 0x0D }, { 0xF3, 0xF3, 0x6D }, { 0xFF, 0xF3, 0xF9 } }; if (!(file = Open_file_read(context))) { File_error = 1; return; } file_size=File_length_file(file); Read_byte(file, &mode); Read_word_le(file, &width); Read_word_le(file, &height); Read_byte(file, &npal); if (npal > 16) npal = 16; // Switch to the proper aspect ratio switch (mode) { case 0: case 4: context->Ratio = PIXEL_WIDE; width /= 2; break; case 2: context->Ratio = PIXEL_TALL; break; case 1: case 5: case 3: context->Ratio = PIXEL_SIMPLE; break; } Pre_load(context, width, height, file_size, FORMAT_PPH, context->Ratio, 0); context->Width = width; context->Height = height; // First of all, detect the mode // 0, 1, 2 > Load as with SCR files? // R(3) > Load as single layer, square pixels, 16 colors // B0(4) > Load as single layer, wide pixels, expand palette with colorcycling // B1(5) > Load as ??? // Maybe special mode similar to mode5, with 2 layers + auto-flicker? switch (mode) { case 0: case 3: // R // 16-color palette for (i = 0; i < 16; i++) { uint8_t color; Read_byte(file, &color); context->Palette[i] = CPCPAL[color]; } break; case 1: case 5: // B1 { // Single or multiple 4-color palettes uint8_t base[4]; for (j = 0; j < npal; j++) { for (i = 0; i < 4; i++) { Read_byte(file,&base[i]); } for (i = 0; i < 16; i++) { context->Palette[i + 16*j].R = pph_blend( CPCPAL[base[i & 3]].R, CPCPAL[base[i >> 2]].R); context->Palette[i + 16*j].G = pph_blend( CPCPAL[base[i & 3]].G, CPCPAL[base[i >> 2]].G); context->Palette[i + 16*j].B = pph_blend( CPCPAL[base[i & 3]].B, CPCPAL[base[i >> 2]].B); } // TODO this byte marks where this palette stops being used and the // next starts. We must handle this! Read_byte(file,&pl[j]); } pl[npal - 1] = 255; break; } case 2: // Single 2-color palette break; case 4: // B0 { // Single 16-color palette + flipping, need to expand palette and // setup colorcycling ranges. uint8_t base[16]; for (i = 0; i < 16; i++) { Read_byte(file,&base[i]); } for (i = 0; i < 256; i++) { context->Palette[i].R = pph_blend( CPCPAL[base[i & 15]].R, CPCPAL[base[i >> 4]].R); context->Palette[i].G = pph_blend( CPCPAL[base[i & 15]].G, CPCPAL[base[i >> 4]].G); context->Palette[i].B = pph_blend( CPCPAL[base[i & 15]].B, CPCPAL[base[i >> 4]].B); } } break; } fclose(file); // Load the picture data // There are two pages, each storing bytes in the CPC vram format but lines in // linear order. file = Open_file_read_with_alternate_ext(context, "odd"); if (file == NULL) { File_error = 3; return; } feven = Open_file_read_with_alternate_ext(context, "eve"); if (feven == NULL) { File_error = 4; fclose(file); return; } c = 0; d = 0; for (j = 0; j < height; j++) { for (i = 0; i < width;) { uint8_t even, odd; Read_byte(feven, &even); Read_byte(file, &odd); switch (mode) { case 4: a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); a <<= 4; a |= ((odd & 0x02) << 2) | (( odd & 0x08) >> 2) | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); b = ((even & 0x01) << 3) | ((even & 0x04) >> 1) | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); b <<= 4; b |= ((odd & 0x01) << 3) | (( odd & 0x04) >> 1) | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); Set_pixel(context, i++, j, a); Set_pixel(context, i++, j, b); break; case 3: a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); b = (( odd & 0x02) << 2) | (( odd & 0x08) >> 2) | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); c = ((even & 0x01) << 3) | ((even & 0x04) >> 1) | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); d = (( odd & 0x01) << 3) | (( odd & 0x04) >> 1) | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); Set_pixel(context, i++, j, j & 1 ? b : a); Set_pixel(context, i++, j, j & 1 ? a : b); Set_pixel(context, i++, j, j & 1 ? d : c); Set_pixel(context, i++, j, j & 1 ? c : d); break; case 5: if (d >= pl[c]) { d = 0; c++; } a = ((even & 0x80) >> 6) | ((even & 0x08) >> 3); b = (( odd & 0x80) >> 6) | (( odd & 0x08) >> 3); Set_pixel(context, i++, j, a + (b << 2) + c * 16); a = ((even & 0x40) >> 5) | ((even & 0x04) >> 2); b = (( odd & 0x40) >> 5) | (( odd & 0x04) >> 2); Set_pixel(context, i++, j, a + (b << 2) + c * 16); a = ((even & 0x20) >> 4) | ((even & 0x02) >> 1); b = (( odd & 0x20) >> 4) | (( odd & 0x02) >> 1); Set_pixel(context, i++, j, a + (b << 2) + c * 16); a = ((even & 0x10) >> 3) | ((even & 0x01) >> 0); b = (( odd & 0x10) >> 3) | (( odd & 0x01) >> 0); Set_pixel(context, i++, j, a + (b << 2) + c * 16); break; default: File_error = 2; return; } } d++; } fclose(file); fclose(feven); File_error = 0; } void Save_PPH(T_IO_Context* context) { (void)context; // unused // TODO // Detect mode // Wide pixels => B0 (4) // Square pixels: // - 16 colors used => R // - more colors used => B1 (if <16 colors per line) // Check palette // B0: use diagonal: 0, 17, 34, ... (assume the other are mixes) // R: use 16 used colors (or 16 first?) // B1: find the 16 colors used in a line? Or assume they are in-order already? } /////////////////////////////////// FLI/FLC ///////////////////////////////// typedef struct { dword size; /* Size of FLIC including this header */ word type; /* File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ... */ word frames; /* Number of frames in first segment */ word width; /* FLIC width in pixels */ word height; /* FLIC height in pixels */ word depth; /* Bits per pixel (usually 8) */ word flags; /* Set to zero or to three */ dword speed; /* Delay between frames */ word reserved1; /* Set to zero */ dword created; /* Date of FLIC creation (FLC only) */ dword creator; /* Serial number or compiler id (FLC only) */ dword updated; /* Date of FLIC update (FLC only) */ dword updater; /* Serial number (FLC only), see creator */ word aspect_dx; /* Width of square rectangle (FLC only) */ word aspect_dy; /* Height of square rectangle (FLC only) */ word ext_flags; /* EGI: flags for specific EGI extensions */ word keyframes; /* EGI: key-image frequency */ word totalframes; /* EGI: total number of frames (segments) */ dword req_memory; /* EGI: maximum chunk size (uncompressed) */ word max_regions; /* EGI: max. number of regions in a CHK_REGION chunk */ word transp_num; /* EGI: number of transparent levels */ byte reserved2[24]; /* Set to zero */ dword oframe1; /* Offset to frame 1 (FLC only) */ dword oframe2; /* Offset to frame 2 (FLC only) */ byte reserved3[40]; /* Set to zero */ } T_FLIC_Header; static void Load_FLI_Header(FILE * file, T_FLIC_Header * header) { if (!(Read_dword_le(file,&header->size) && Read_word_le(file,&header->type) && Read_word_le(file,&header->frames) && Read_word_le(file,&header->width) && Read_word_le(file,&header->height) && Read_word_le(file,&header->depth) && Read_word_le(file,&header->flags) && Read_dword_le(file,&header->speed) && Read_word_le(file,&header->reserved1) && Read_dword_le(file,&header->created) && Read_dword_le(file,&header->creator) && Read_dword_le(file,&header->updated) && Read_dword_le(file,&header->updater) && Read_word_le(file,&header->aspect_dx) && Read_word_le(file,&header->aspect_dy) && Read_word_le(file,&header->ext_flags) && Read_word_le(file,&header->keyframes) && Read_word_le(file,&header->totalframes) && Read_dword_le(file,&header->req_memory) && Read_word_le(file,&header->max_regions) && Read_word_le(file,&header->transp_num) && Read_bytes(file,header->reserved2,24) && Read_dword_le(file,&header->oframe1) && Read_dword_le(file,&header->oframe2) && Read_bytes(file,header->reserved2,40) )) { File_error=1; } } /** * Test for the Autodesk Animator FLI/FLC format. * * Not to be confused with Commodore 64 FLI. */ void Test_FLI(T_IO_Context * context, FILE * file) { T_FLIC_Header header; (void)context; File_error=0; Load_FLI_Header(file, &header); if (File_error != 0) return; switch (header.type) { case 0xAF11: // standard FLI case 0xAF12: // FLC (8bpp) #if 0 case 0xAF30: // Huffman or BWT compression case 0xAF31: // frame shift compression case 0xAF44: // bpp != 8 #endif File_error=0; break; default: File_error=1; } } /** * Load file in the Autodesk Animator FLI/FLC format. * * Not to be confused with Commodore 64 FLI. */ void Load_FLI(T_IO_Context * context) { FILE * file; unsigned long file_size; T_FLIC_Header header; dword chunk_size; word chunk_type; word sub_chunk_count, sub_chunk_index; dword sub_chunk_size; word sub_chunk_type; word frame_delay, frame_width, frame_height; int current_frame = 0; file = Open_file_read(context); if (file == NULL) { File_error=1; return; } File_error=0; file_size = File_length_file(file); Load_FLI_Header(file, &header); if (File_error != 0) { fclose(file); return; } if (header.size == 12) { // special "magic carpet" format header.depth = 8; header.speed = 66; // about 15fps fseek(file, 12, SEEK_SET); } else if (file_size != header.size) Warning("Load_FLI(): file size mismatch in header"); if (header.speed == 0) { if (header.type == 0xAF11) // FLI header.speed = 1; // 1/70th seconds else header.speed = 10; // 10ms } while (File_error == 0 && Read_dword_le(file,&chunk_size) && Read_word_le(file,&chunk_type)) { chunk_size -= 6; switch (chunk_type) { case 0xf1fa: // FRAME Read_word_le(file, &sub_chunk_count); Read_word_le(file, &frame_delay); fseek(file, 2, SEEK_CUR); Read_word_le(file, &frame_width); Read_word_le(file, &frame_height); if (frame_width == 0) frame_width = header.width; if (frame_height == 0) frame_height = header.height; if (frame_delay == 0) frame_delay = header.speed; chunk_size -= 10; if (current_frame == 0) { Pre_load(context, header.width,header.height,file_size,FORMAT_FLI,PIXEL_SIMPLE,header.depth); if (context->Type == CONTEXT_MAIN_IMAGE) { Main.backups->Pages->Image_mode = IMAGE_MODE_ANIMATION; Update_screen_targets(); } if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); } else { Set_loading_layer(context, current_frame); if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) { // Copy the content of previous frame memcpy( Main.backups->Pages->Image[Main.current_layer].Pixels, Main.backups->Pages->Image[Main.current_layer-1].Pixels, Main.backups->Pages->Width*Main.backups->Pages->Height); } } if (header.type == 0xAF11) // FLI Set_frame_duration(context, (frame_delay * 100) / 7); // 1/70th sec else Set_frame_duration(context, frame_delay); // msec current_frame++; for (sub_chunk_index = 0; sub_chunk_index < sub_chunk_count; sub_chunk_index++) { if (!(Read_dword_le(file,&sub_chunk_size) && Read_word_le(file,&sub_chunk_type))) File_error = 1; else { chunk_size -= sub_chunk_size; sub_chunk_size -= 6; if (sub_chunk_type == 0x04 || sub_chunk_type == 0x0b) // color map { word packet_count; int i = 0; sub_chunk_size -= 2; if (!Read_word_le(file, &packet_count)) File_error = 1; else while (packet_count-- > 0 && File_error == 0) { byte skip, count; if (!(Read_byte(file, &skip) && Read_byte(file, &count))) File_error = 1; else { sub_chunk_size -= 2; i += skip; // count 0 means 256 do { byte r, g, b; if (!(Read_byte(file, &r) && Read_byte(file, &g) && Read_byte(file, &b))) { File_error = 1; break; } if (sub_chunk_type == 0x0b || header.size == 12) // 6bit per color { r = (r << 2) | (r >> 4); g = (g << 2) | (g >> 4); b = (b << 2) | (b >> 4); } context->Palette[i].R = r; context->Palette[i].G = g; context->Palette[i].B = b; i++; sub_chunk_size -= 3; } while (--count != 0); } } } else if (sub_chunk_type == 0x0f) // full frame RLE { word x, y; for (y = 0; y < frame_height && File_error == 0; y++) { byte count, data; Read_byte(file, &count); // packet count, but dont rely on it sub_chunk_size--; for (x = 0; x < frame_width; ) { if (!Read_byte(file, &count)) { File_error = 1; break; } sub_chunk_size--; if ((count & 0x80) == 0) { if (!Read_byte(file, &data)) // repeat data count times { File_error = 1; break; } sub_chunk_size--; while (count-- > 0 && x < frame_width) Set_pixel(context, x++, y, data); } else while (count++ != 0 && x < frame_width) // copy count bytes { if (!Read_byte(file, &data)) { File_error = 1; break; } Set_pixel(context, x++, y, data); sub_chunk_size--; } } } if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE) { // load only 1st frame in preview fclose(file); return; } } else if (sub_chunk_type == 0x0c) // delta image, RLE { word x, y, line_count; Read_word_le(file, &y); Read_word_le(file, &line_count); sub_chunk_size -= 4; while (sub_chunk_size > 0 && line_count > 0 && File_error == 0) { byte packet_count; x = 0; if (!Read_byte(file, &packet_count)) File_error = 1; else { sub_chunk_size--; while (packet_count-- > 0 && File_error == 0) { byte skip, count, data; if (!(Read_byte(file, &skip) && Read_byte(file, &count))) File_error = 1; else { sub_chunk_size -= 2; x += skip; if (count & 0x80) { Read_byte(file, &data); sub_chunk_size--; while (count++ != 0) Set_pixel(context, x++, y, data); } else while (count-- > 0) { Read_byte(file, &data); sub_chunk_size--; Set_pixel(context, x++, y, data); } } } } y++; line_count--; } } else if (sub_chunk_type == 0x07) // FLC delta image { word opcode, y, line_count; y = 0; Read_word_le(file, &line_count); sub_chunk_size -= 2; while (line_count > 0) { Read_word_le(file, &opcode); sub_chunk_size -= 2; if ((opcode & 0xc000) == 0x0000) // packet count { word x = 0; while (opcode-- > 0) { byte skip, count, data1, data2; if (!(Read_byte(file, &skip) && Read_byte(file, &count))) File_error = 1; else { sub_chunk_size -= 2; x += skip; if (count & 0x80) { Read_byte(file, &data1); Read_byte(file, &data2); sub_chunk_size -= 2; while (count++ != 0) { Set_pixel(context, x++, y, data1); Set_pixel(context, x++, y, data2); } } else while (count-- > 0) { Read_byte(file, &data1); Set_pixel(context, x++, y, data1); Read_byte(file, &data2); Set_pixel(context, x++, y, data2); sub_chunk_size -= 2; } } } y++; line_count--; } else if ((opcode & 0xc000) == 0xc000) // line skip { y -= opcode; } else if ((opcode & 0xc000) == 0x8000) // last byte { Set_pixel(context, frame_width - 1, y, opcode & 0xff); } else { Warning("Unsupported opcode"); File_error = 2; break; } } } if (sub_chunk_size > 0) { fseek(file, sub_chunk_size, SEEK_CUR); } } } break; default: // skip Warning("Load_FLI(): unrecognized chunk"); } if (chunk_size > 0 && header.size != 12) { fseek(file, chunk_size, SEEK_CUR); } } fclose(file); } /////////////////////////////// Thomson Files /////////////////////////////// /** * Test for Thomson file */ void Test_MOTO(T_IO_Context * context, FILE * file) { long file_size; file_size = File_length_file(file); File_error = 1; if (file_size <= 10) return; switch (MOTO_Check_binary_file(file)) { case 0: // Not Thomson binary format switch (file_size) { // Files in RAW formats (from TGA2teo) case 8004: // 2 colors palette case 8008: // 4 colors palette case 8032: // 16 colors palette { char filename[MAX_PATH_CHARACTERS]; char path[MAX_PATH_CHARACTERS]; char * ext; // Check there are both FORME and COULEUR files strncpy(filename, context->File_name, sizeof(filename)); filename[sizeof(filename)-1] = '\0'; ext = strrchr(filename, '.'); if (ext == NULL || ext == filename) return; if ((ext[-1] | 32) == 'c') ext[-1] = (ext[-1] & 32) | 'P'; else if ((ext[-1] | 32) == 'p') ext[-1] = (ext[-1] & 32) | 'C'; else return; Get_full_filename(path, filename, context->File_directory); if (File_exists(path)) File_error = 0; } return; default: break; } break; case 2: // MAP file (SAVEP/LOADP) case 3: // TO autoloading picture case 4: // MO autoloading picture File_error = 0; return; } } /** * Load a picture for Thomson TO8/TO8D/TO9/TO9+/MO6 * * One of the supported format is the one produced by TGA2Teo : * - Picture data is splitted into 2 files, one for each VRAM bank : * - The first VRAM bank is called "forme" (shape). * In 40col mode it stores pixels. * - The second VRAM bank is called "couleur" (color). * In 40col mode it store color indexes for foreground and background. * - File extension is .BIN, character before extension is "P" for the first * file, and "C" for the second. * - The color palette is stored in both files after the data. * * The mode is detected thanks to the number of color in the palette : * - 2 colors is 80col (640x200) * - 4 colors is bitmap4 (320x200 4 colors) * - 16 colors is either bitmap16 (160x200 16colors) * or 40col (320x200 16 colors with 2 unique colors in each 8x1 pixels * block). * * As it is not possible to disriminate bitmap16 and 40col, opening the "P" * file sets bitmap16, opening the "C" file sets 40col. * * This function also supports .MAP files (with optional TO-SNAP extension) * and our own "autoloading" BIN files. * See http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO for * a detailled description. */ void Load_MOTO(T_IO_Context * context) { // FORME / COULEUR FILE * file; byte * vram_forme = NULL; byte * vram_couleur = NULL; long file_size; int file_type; int bx, x, y, i; byte bpp = 4; byte code; word length, address; int transpose = 1; // transpose the upper bits of the color plane bytes // FFFFBBBB becomes bfFFFBBB (for TO7 compatibility) enum MOTO_Graphic_Mode mode = MOTO_MODE_40col; enum PIXEL_RATIO ratio = PIXEL_SIMPLE; int width = 320, height = 200, columns = 40; File_error = 1; file = Open_file_read(context); if (file == NULL) return; file_size = File_length_file(file); // Load default palette if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); MOTO_set_TO7_palette(context->Palette); file_type = MOTO_Check_binary_file(file); if (fseek(file, 0, SEEK_SET) < 0) { fclose(file); return; } if (file_type == 2) // MAP file { // http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 byte map_mode, col_count, line_count; byte * vram_current; int end_marks; if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) { fclose(file); return; } if (length < 5 || !(Read_byte(file,&map_mode) && Read_byte(file,&col_count) && Read_byte(file,&line_count))) { fclose(file); return; } length -= 3; columns = col_count + 1; height = 8 * (line_count + 1); switch(map_mode) { default: case 0: // bitmap4 or 40col width = 8 * columns; mode = MOTO_MODE_40col; // default to 40col bpp = 4; break; case 0x40: // bitmap16 columns >>= 1; width = 4 * columns; mode = MOTO_MODE_bm16; bpp = 4; ratio = PIXEL_WIDE; break; case 0x80: // 80col columns >>= 1; width = 16 * columns; mode = MOTO_MODE_80col; bpp = 1; ratio = PIXEL_TALL; break; } GFX2_Log(GFX2_DEBUG, "Map mode &H%02X row=%u line=%u (%dx%d) %d\n", map_mode, col_count, line_count, width, height, columns * height); vram_forme = malloc(columns * height); vram_couleur = malloc(columns * height); // Check extension (TO-SNAP / PPM / ???) if (length > 36) { long pos_backup; word data; pos_backup = ftell(file); fseek(file, length-2, SEEK_CUR); // go to last word of chunk Read_word_be(file, &data); GFX2_Log(GFX2_DEBUG, "%04X\n", data); switch (data) { case 0xA55A: // TO-SNAP fseek(file, -40, SEEK_CUR); // go to begin of extension Read_word_be(file, &data); // SCRMOD. 0=>40col, 1=>bm4, $40=>bm16, $80=>80col GFX2_Log(GFX2_DEBUG, "SCRMOD=&H%04X ", data); Read_word_be(file, &data); // Border color GFX2_Log(GFX2_DEBUG, "BORDER=%u ", data); Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc. GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data); if(data == 2) { mode = MOTO_MODE_bm4; bpp = 2; } for (i = 0; i < 16; i++) { Read_word_be(file, &data); // Palette entry if (data & 0x8000) data = ~data; MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data); } snprintf(context->Comment, sizeof(context->Comment), "TO-SNAP .MAP file"); break; case 0x484C: // 'HL' PPM fseek(file, -36, SEEK_CUR); // go to begin of extension for (i = 0; i < 16; i++) { Read_word_be(file, &data); // Palette entry if (data & 0x8000) data = ~data; MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data); } Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc. GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data); if(data == 2) { mode = MOTO_MODE_bm4; bpp = 2; } snprintf(context->Comment, sizeof(context->Comment), "PPM .MAP file"); break; default: snprintf(context->Comment, sizeof(context->Comment), "standard .MAP file"); } fseek(file, pos_backup, SEEK_SET); // RESET Position } i = 0; vram_current = vram_forme; end_marks = 0; while (length > 1) { byte byte1, byte2; Read_byte(file,&byte1); Read_byte(file,&byte2); length-=2; if(byte1 == 0) { if (byte2 == 0) { // end of vram stream GFX2_Log(GFX2_DEBUG, "0000 i=%d length=%ld\n", i, length); if (end_marks == 1) break; i = 0; vram_current = vram_couleur; end_marks++; } else while(byte2-- > 0 && length > 0) // copy { Read_byte(file,vram_current + i); length--; i += columns; // to the next line if (i >= columns * height) { if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col) i -= (columns * height - 1); // to the 1st line of the next column else { i -= columns * height; // back to the 1st line of the current column if (vram_current == vram_forme) // other VRAM vram_current = vram_couleur; else { vram_current = vram_forme; i++; // next column } } } } } else while(byte1-- > 0) // run length { vram_current[i] = byte2; i += columns; // to the next line if (i >= columns * height) { if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col) i -= (columns * height - 1); // to the 1st line of the next column else { i -= columns * height; // back to the 1st line of the current column if (vram_current == vram_forme) // other VRAM vram_current = vram_couleur; else { vram_current = vram_forme; i++; // next column } } } } } fclose(file); } else if(file_type == 3 || file_type == 4) { if (file_type == 4) // MO file { transpose = 0; MOTO_set_MO5_palette(context->Palette); } do { if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) { if (vram_forme) break; fclose(file); return; } // MO5/MO6 VRAM address is &H0000 // TO7/TO8/TO9 VRAM addres is &H4000 if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0)) { if (vram_forme == NULL) { vram_forme = calloc(8192, 1); Read_bytes(file, vram_forme, length); length = 0; } else if (vram_couleur == NULL) { vram_couleur = calloc(8192, 1); Read_bytes(file, vram_couleur, length); if (length >= 8032) { for (x = 0; x < 16; x++) { // 1 byte Blue (4 lower bits) // 1 byte Green (4 upper bits) / Red (4 lower bits) MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x], vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]); } if (length >= 8064) { memcpy(context->Comment, vram_couleur + 8032, 32); if (vram_couleur[8063] >= '0' && vram_couleur[8063] <= '3') mode = vram_couleur[8063] - '0'; } context->Comment[COMMENT_SIZE] = '\0'; } length = 0; } } if (length > 0) fseek(file, length, SEEK_CUR); } while(code == 0); fclose(file); switch (mode) { case MOTO_MODE_40col: // default break; case MOTO_MODE_bm4: bpp = 2; break; case MOTO_MODE_80col: bpp = 1; width = 640; ratio = PIXEL_TALL; break; case MOTO_MODE_bm16: width = 160; ratio = PIXEL_WIDE; break; } } else { char filename[MAX_PATH_CHARACTERS]; char path[MAX_PATH_CHARACTERS]; char * ext; int n_colors; vram_forme = malloc(file_size); if (vram_forme == NULL) { fclose(file); return; } if (!Read_bytes(file, vram_forme, file_size)) { free(vram_forme); fclose(file); return; } n_colors = (file_size - 8000) / 2; switch(n_colors) { case 16: bpp = 4; // 16 colors : either 40col or bm16 mode ! // select later break; case 4: bpp = 2; mode = MOTO_MODE_bm4; break; default: bpp = 1; mode = MOTO_MODE_80col; width = 640; ratio = PIXEL_TALL; } strncpy(filename, context->File_name, sizeof(filename)); filename[sizeof(filename)-1] = '\0'; ext = strrchr(filename, '.'); if (ext == NULL || ext == filename) { free(vram_forme); return; } if ((ext[-1] | 32) == 'c') { vram_couleur = vram_forme; vram_forme = NULL; ext[-1] = (ext[-1] & 32) | 'P'; } else if ((ext[-1] | 32) == 'p') { ext[-1] = (ext[-1] & 32) | 'C'; if (n_colors == 16) { mode = MOTO_MODE_bm16; width = 160; ratio = PIXEL_WIDE; } } else { free(vram_forme); return; } Get_full_filename(path, filename, context->File_directory); file = fopen(path, "rb"); if (vram_forme == NULL) { vram_forme = malloc(file_size); if (vram_forme == NULL) { free(vram_couleur); fclose(file); return; } Read_bytes(file,vram_forme,file_size); } else { vram_couleur = malloc(file_size); if (vram_couleur == NULL) { free(vram_forme); fclose(file); return; } Read_bytes(file,vram_couleur,file_size); } fclose(file); GFX2_Log(GFX2_DEBUG, "MO/TO: %s,%s file_size=%ld n_colors=%d\n", context->File_name, filename, file_size, n_colors); for (x = 0; x < n_colors; x++) { // 1 byte Blue (4 lower bits) // 1 byte Green (4 upper bits) / Red (4 lower bits) MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x], vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]); } } Pre_load(context, width, height, file_size, FORMAT_MOTO, ratio, bpp); File_error = 0; i = 0; for (y = 0; y < height; y++) { for (bx = 0; bx < columns; bx++) { byte couleur_forme; byte couleur_fond; byte forme, couleurs; forme = vram_forme[i]; if (vram_couleur) couleurs = vram_couleur[i]; else couleurs = (mode == MOTO_MODE_40col) ? 0x01 : 0x00; i++; switch(mode) { case MOTO_MODE_bm4: for (x = bx*8; x < bx*8+8; x++) { Set_pixel(context, x, y, ((forme & 0x80) >> 6) | ((couleurs & 0x80) >> 7)); forme <<= 1; couleurs <<= 1; } #if 0 // the following would be for the alternate bm4 mode for (x = bx*8; x < bx*8+4; x++) { Set_pixel(context, x, y, couleurs >> 6); couleurs <<= 2; } for (x = bx*8 + 4; x < bx*8+8; x++) { Set_pixel(context, x, y, forme >> 6); forme <<= 2; } #endif break; case MOTO_MODE_bm16: Set_pixel(context, bx*4, y, forme >> 4); Set_pixel(context, bx*4+1, y, forme & 0x0F); Set_pixel(context, bx*4+2, y, couleurs >> 4); Set_pixel(context, bx*4+3, y, couleurs & 0x0F); break; case MOTO_MODE_80col: for (x = bx*16; x < bx*16+8; x++) { Set_pixel(context, x, y, (forme & 0x80) >> 7); Set_pixel(context, x+8, y, (couleurs & 0x80) >> 7); forme <<= 1; couleurs <<= 1; } break; case MOTO_MODE_40col: default: if (transpose) { // the color plane byte is bfFFFBBB (for TO7 compatibility) // with the upper bits of both foreground (forme) and // background (fond) inverted. couleur_forme = ((couleurs & 0x78) >> 3) ^ 0x08; couleur_fond = ((couleurs & 7) | ((couleurs & 0x80) >> 4)) ^ 0x08; } else { // MO5 : the color plane byte is FFFFBBBB couleur_forme = couleurs >> 4; couleur_fond = couleurs & 0x0F; } for (x = bx*8; x < bx*8+8; x++) { Set_pixel(context, x, y, (forme & 0x80)?couleur_forme:couleur_fond); forme <<= 1; } } } } } /** * Pack a stream of byte in the format used by Thomson MO/TO MAP files. * * - 00 cc xx yy .. : encodes a "copy run" (cc = bytes to copy) * - cc xx : encodes a "repeat run" (cc > 0 : count) */ //#define MOTO_MAP_NOPACKING static unsigned int MOTO_MAP_pack(byte * packed, const byte * unpacked, unsigned int unpacked_len) { unsigned int src; unsigned int dst = 0; unsigned int count; #ifndef MOTO_MAP_NOPACKING unsigned int repeat; unsigned int i; word * counts; #endif GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack(%p, %p, %u)\n", packed, unpacked, unpacked_len); if (unpacked_len == 0) return 0; if (unpacked_len == 1) { packed[0] = 1; packed[1] = unpacked[0]; return 2; } #ifdef MOTO_MAP_NOPACKING // compression disabled src = 0; while ((unpacked_len - src) > 255) { packed[dst++] = 0; packed[dst++] = 255; memcpy(packed+dst, unpacked+src, 255); dst += 255; src += 255; } count = unpacked_len - src; packed[dst++] = 0; packed[dst++] = count; memcpy(packed+dst, unpacked+src, count); dst += count; src += count; return dst; #else counts = malloc(sizeof(word) * (unpacked_len + 1)); i = 0; repeat = (unpacked[0] == unpacked[1]); count = 2; src = 2; // 1st step : count lenght of the Copy runs and Repeat runs while (src < unpacked_len) { if (repeat) { if (unpacked[src-1] == unpacked[src]) count++; else { // flush the repeat run counts[i++] = count | 0x8000; // 0x8000 is the marker for repeat runs count = 1; repeat = 0; } } else { if (unpacked[src-1] != unpacked[src]) count++; else if (count == 1) { count++; repeat = 1; } else { // flush the copy run counts[i++] = (count-1) | (count == 2 ? 0x8000 : 0); // mark copy run of 1 as repeat of 1 count = 2; repeat = 1; } } src++; } // flush the last run counts[i++] = ((repeat || count == 1) ? 0x8000 : 0) | count; counts[i++] = 0; // end marker // check consistency of counts count = 0; for (i = 0; counts[i] != 0; i++) count += (counts[i] & ~0x8000); if (count != unpacked_len) GFX2_Log(GFX2_ERROR, "*** encoding error in MOTO_MAP_pack() *** count=%u unpacked_len=%u\n", count, unpacked_len); // output optimized packed stream // repeat run are encoded cc xx // copy run are encoded 00 cc xx xx xx xx i = 0; src = 0; while (counts[i] != 0) { while (counts[i] & 0x8000) // repeat run { count = counts[i] & ~0x8000; GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u repeat %u times %02x\n", src, i, count, unpacked[src]); while(count > 255) { packed[dst++] = 255; packed[dst++] = unpacked[src]; count -= 255; src += 255; } packed[dst++] = count; packed[dst++] = unpacked[src]; src += count; i++; } while (counts[i] != 0 && !(counts[i] & 0x8000)) // copy run { // calculate the "savings" of repeat runs between 2 copy run int savings = 0; unsigned int j; GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u copy %u bytes\n", src, i, counts[i]); for (j = i + 1; counts[j] & 0x8000; j++) // check repeat runs until the next copy run { count = counts[j] & ~0x8000; if (savings < 0 && (savings + (int)count - 2) > 0) break; savings += count - 2; // a repeat run outputs 2 bytes for count bytes of input } count = counts[i]; GFX2_Log(GFX2_DEBUG, " savings=%d i=%u j=%u (counts[j]=0x%04x)\n", savings, i, j, counts[j]); if (savings < 2 && (j > i + 1)) { unsigned int k; if (counts[j] == 0) // go to the end of stream { for (k = i + 1; k < j; k++) count += (counts[k] & ~0x8000); GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extend copy from %u to %u\n", src, counts[i], count); i = j - 1; } else { for (k = i + 1; k < j; k++) count += (counts[k] & ~0x8000); if (!(counts[j] & 0x8000)) { // merge with the next copy run (and the repeat runs between) GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u merge savings=%d\n", src, savings); i = j; counts[i] += count; continue; } else { // merge with the next few repeat runs GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extends savings=%d\n", src, savings); i = j - 1; } } } while (count > 255) { packed[dst++] = 0; packed[dst++] = 255; memcpy(packed+dst, unpacked+src, 255); dst += 255; src += 255; count -= 255; } packed[dst++] = 0; packed[dst++] = count; memcpy(packed+dst, unpacked+src, count); dst += count; src += count; i++; } } free(counts); return dst; #endif } /** * GUI window to choose Thomson MO/TO saving parameters * * @param[out] machine target machine * @param[out] format file format (0 = BIN, 1 = MAP) * @param[in,out] mode video mode @ref MOTO_Graphic_Mode */ static int Save_MOTO_window(enum MOTO_Machine_Type * machine, int * format, enum MOTO_Graphic_Mode * mode) { int button; T_Dropdown_button * machine_dd; T_Dropdown_button * format_dd; T_Dropdown_button * mode_dd; static const char * mode_list[] = { "40col", "80col", "bm4", "bm16" }; char text_info[24]; Open_window(200, 125, "Thomson MO/TO Saving"); Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1 Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2 Print_in_window(13,18,"Target Machine:",MC_Dark,MC_Light); machine_dd = Window_set_dropdown_button(10,28,110,15,100, (*mode == MOTO_MODE_40col) ? "TO7/TO7-70" : "TO9/TO8/TO9+", 1, 0, 1, LEFT_SIDE,0); // 3 if (*mode == MOTO_MODE_40col) Window_dropdown_add_item(machine_dd, MACHINE_TO7, "TO7/TO7-70"); Window_dropdown_add_item(machine_dd, MACHINE_TO8, "TO9/TO8/TO9+"); if (*mode == MOTO_MODE_40col) Window_dropdown_add_item(machine_dd, MACHINE_MO5, "MO5"); Window_dropdown_add_item(machine_dd, MACHINE_MO6, "MO6"); Print_in_window(13,46,"Format:",MC_Dark,MC_Light); format_dd = Window_set_dropdown_button(10,56,110,15,92,"BIN",1, 0, 1, LEFT_SIDE,0); // 4 Window_dropdown_add_item(format_dd, 0, "BIN"); Window_dropdown_add_item(format_dd, 1, "MAP/TO-SNAP"); Print_in_window(136,46,"Mode:",MC_Dark,MC_Light); mode_dd = Window_set_dropdown_button(136,56,54,15,44,mode_list[*mode],1, 0, 1, LEFT_SIDE,0); // 5 if (*mode == MOTO_MODE_40col) Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); if (*mode == MOTO_MODE_80col) Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); if (*mode == MOTO_MODE_40col) Window_dropdown_add_item(mode_dd, MOTO_MODE_bm4, mode_list[MOTO_MODE_bm4]); if (*mode == MOTO_MODE_bm16) Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]); Update_window_area(0,0,Window_width,Window_height); Display_cursor(); do { button = Window_clicked_button(); if (Is_shortcut(Key, 0x100+BUTTON_HELP)) { Key = 0; Window_help(BUTTON_SAVE, "THOMSON MO/TO FORMAT"); } else switch (button) { case 3: *machine = (enum MOTO_Machine_Type)Window_attribute2; break; case 4: *format = Window_attribute2; break; case 5: *mode = (enum MOTO_Graphic_Mode)Window_attribute2; break; } Hide_cursor(); //"ABCDEFGHIJKLMNOPQRSTUVW" memset(text_info, ' ', 23); text_info[23] = '\0'; if (*machine == MACHINE_TO7 || *machine == MACHINE_TO770 || *machine == MACHINE_MO5) { if (*mode != MOTO_MODE_40col) snprintf(text_info, sizeof(text_info), "%s only supports 40col", (*machine == MACHINE_MO5) ? "MO5" : "TO7"); else if (*format == 1) strncpy(text_info, "No TO-SNAP extension. ", sizeof(text_info)); else strncpy(text_info, "No palette to save. ", sizeof(text_info)); } Print_in_window(9, 80, text_info, MC_Dark, MC_Light); Display_cursor(); } while(button!=1 && button!=2); Close_window(); Display_cursor(); return button==1; } /** * Save a picture in MAP or BIN Thomson MO/TO file format. * * File format details : * http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO */ void Save_MOTO(T_IO_Context * context) { int transpose = 1; // transpose upper bits in "couleur" vram enum MOTO_Machine_Type target_machine = MACHINE_TO7; int format = 0; // 0 = BIN, 1 = MAP enum MOTO_Graphic_Mode mode; FILE * file = NULL; byte * vram_forme; byte * vram_couleur; int i, x, y, bx; word reg_prc = 0xE7C3; // PRC : TO7/8/9 0xE7C3 ; MO5/MO6 0xA7C0 byte prc_value = 0x65;// Value to write to PRC to select VRAM bank // MO5 : 0x51 word vram_address = 0x4000; // 4000 on TO7/TO8/TO9, 0000 on MO5/MO6 File_error = 1; /** * In the future we could support other resolution for .MAP * format. * And even in .BIN format, we could store less lines. */ if (context->Height != 200) { Warning_message("must be 640x200, 320x200 or 160x200"); return; } switch (context->Width) { case 160: mode = MOTO_MODE_bm16; target_machine = MACHINE_TO8; break; case 640: mode = MOTO_MODE_80col; target_machine = MACHINE_TO8; break; case 320: mode = MOTO_MODE_40col; // or bm4 break; default: Warning_message("must be 640x200, 320x200 or 160x200"); return; } if (!Save_MOTO_window(&target_machine, &format, &mode)) return; if (target_machine == MACHINE_MO5 || target_machine == MACHINE_MO6) { reg_prc = 0xA7C0; // PRC : MO5/MO6 0xA7C0 prc_value = 0x51; vram_address = 0; transpose = 0; } vram_forme = malloc(8192); vram_couleur = malloc(8192); switch (mode) { case MOTO_MODE_40col: { /** * The 40col encoding algorithm is optimized for further vertical * RLE packing. The "attibute" byte is kept as constant as possible * between adjacent blocks. */ unsigned color_freq[16]; unsigned max_freq = 0; byte previous_fond = 0, previous_forme = 0; byte most_used_color = 0; // search for most used color to prefer it as background color for (i = 0; i < 16; i++) color_freq[i] = 0; for (y = 0; y < context->Height; y++) { for (x = 0; x < context->Width; x++) { byte col = Get_pixel(context, x, y); if (col > 15) { Warning_with_format("color %u > 15 at pixel (%d,%d)", col, x, y); goto error; } color_freq[col]++; } } for (i = 0; i < 16; i++) { if (color_freq[i] > max_freq) { max_freq = color_freq[i]; most_used_color = (byte)i; // most used color } } previous_fond = most_used_color; max_freq = 0; for (i = 0; i < 16; i++) { if (i != most_used_color && color_freq[i] > max_freq) { max_freq = color_freq[i]; previous_forme = (byte)i; // second most used color } } GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme); if (target_machine == MACHINE_MO5) { /** * For MO5 we use a different 40col algorithm * to make sure the last pixel of a GPL and the first the next * are both FORME or both FOND, else we get an ugly glitch on the * EFGJ033 Gate Array MO5! */ byte forme_byte = 0; byte couleur_byte = 0x10; GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using MO5 algo\n"); for (y = 0; y < context->Height; y++) { for (bx = 0; bx < 40; bx++) { byte fond = 0xff, forme = 0xff; forme_byte &= 1; // Last bit of the previous FORME byte x = bx*8; if (forme_byte) forme = Get_pixel(context, x, y); else fond = Get_pixel(context, x, y); while (++x < bx * 8 + 8) { byte col = Get_pixel(context, x, y); forme_byte <<= 1; if (col == forme) forme_byte |= 1; else if (col != fond) { if (forme == 0xff) { forme_byte |= 1; forme = col; } else if (fond == 0xff) fond = col; else { Warning_with_format("Constraint error at pixel (%d,%d)", x, y); goto error; } } } if (forme != 0xff) couleur_byte = (forme << 4) | (couleur_byte & 0x0f); if (fond != 0xff) couleur_byte = (couleur_byte & 0xf0) | fond; vram_forme[bx+y*40] = forme_byte; vram_couleur[bx+y*40] = couleur_byte; } } } else { GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using optimized algo\n"); // encoding of each 8x1 block for (bx = 0; bx < 40; bx++) { for (y = 0; y < context->Height; y++) { byte forme_byte = 1; byte col; byte c1, c1_count = 1; byte c2 = 0xff, c2_count = 0; byte fond, forme; x = bx * 8; c1 = Get_pixel(context, x, y); while (++x < bx * 8 + 8) { forme_byte <<= 1; col = Get_pixel(context, x, y); if (col > 15) { Warning_with_format("color %d > 15 at pixel (%d,%d)", col, x, y); goto error; } if (col == c1) { forme_byte |= 1; c1_count++; } else { c2_count++; if (c2 == 0xff) c2 = col; else if (col != c2) { Warning_with_format("constraint error at pixel (%d,%d)", x, y); goto error; } } } if (c2 == 0xff) { // Only one color in the 8x1 block if (c1 == previous_fond) c2 = previous_forme; else c2 = previous_fond; } // select background color (fond) // and foreground color (forme) if (c1 == previous_fond) { fond = c1; forme = c2; forme_byte = ~forme_byte; } else if (c2 == previous_fond) { fond = c2; forme = c1; } else if (c1 == most_used_color) { fond = c1; forme = c2; forme_byte = ~forme_byte; } else if (c2 == most_used_color) { fond = c2; forme = c1; } else if (c1_count >= c2_count) { fond = c1; forme = c2; forme_byte = ~forme_byte; } else { fond = c2; forme = c1; } // write to VRAM vram_forme[bx+y*40] = forme_byte; // transpose for TO7 compatibility if (transpose) vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0; else vram_couleur[bx+y*40] = fond | (forme << 4); previous_fond = fond; previous_forme = forme; } if (transpose) { previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4; previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8; } else { previous_fond = vram_couleur[bx] & 15; previous_forme = vram_couleur[bx] >> 4; } } } } break; case MOTO_MODE_80col: for (bx = 0; bx < context->Width / 16; bx++) { for (y = 0; y < context->Height; y++) { byte val = 0; for (x = bx * 16; x < bx*16 + 8; x++) val = (val << 1) | Get_pixel(context, x, y); vram_forme[y*(context->Width/16)+bx] = val; for (; x < bx*16 + 16; x++) val = (val << 1) | Get_pixel(context, x, y); vram_couleur[y*(context->Width/16)+bx] = val; } } break; case MOTO_MODE_bm4: for (y = 0; y < context->Height; y++) { for (bx = 0; bx < context->Width / 8; bx++) { byte val1 = 0, val2 = 0, pixel; for (x = bx * 8; x < bx*8 + 8; x++) { pixel = Get_pixel(context, x, y); if (pixel > 3) { Warning_with_format("color %d > 3 at pixel (%d,%d)", pixel, x, y); goto error; } val1 = (val1 << 1) | (pixel >> 1); val2 = (val2 << 1) | (pixel & 1); } vram_forme[y*(context->Width/8)+bx] = val1; vram_couleur[y*(context->Width/8)+bx] = val2; } } break; case MOTO_MODE_bm16: for (bx = 0; bx < context->Width / 4; bx++) { for (y = 0; y < context->Height; y++) { vram_forme[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4, y) << 4) | Get_pixel(context, bx*4+1, y); vram_couleur[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4+2, y) << 4) | Get_pixel(context, bx*4+3, y); } } break; } // palette for (i = 0; i < 16; i++) { word to8color = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + i); vram_forme[8000+i*2] = to8color >> 8; vram_forme[8000+i*2+1] = to8color & 0xFF; } file = Open_file_write(context); if (file == NULL) goto error; if (format == 0) // BIN { word chunk_length; if (target_machine == MACHINE_TO7 || target_machine == MACHINE_TO770 || target_machine == MACHINE_MO5) chunk_length = 8000; // Do not save palette else { chunk_length = 8000 + 32 + 32; // data + palette + comment // Commentaire if (context->Comment[0] != '\0') strncpy((char *)vram_forme + 8032, context->Comment, 32); else snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision); // also saves the video mode vram_forme[8063] = '0' + mode; memcpy(vram_couleur + 8000, vram_forme + 8000, 64); } // Format BIN // TO8/TO9 : set LGAMOD 0xE7DC 40col=0 bm4=0x21 80col=0x2a bm16=0x7b if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value)) goto error; if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_forme)) goto error; prc_value &= 0xFE; // select color data if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value)) goto error; if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_couleur)) goto error; if (!DECB_BIN_Add_End(file, 0x0000)) goto error; } else { // format MAP with TO-SNAP extensions byte * unpacked_data; byte * packed_data; unpacked_data = malloc(16*1024); packed_data = malloc(16*1024); if (packed_data == NULL || unpacked_data == NULL) { GFX2_Log(GFX2_ERROR, "Failed to allocate 2x16kB of memory\n"); free(packed_data); free(unpacked_data); goto error; } switch (mode) { case MOTO_MODE_40col: case MOTO_MODE_bm4: packed_data[0] = 0; // mode packed_data[1] = (context->Width / 8) - 1; break; case MOTO_MODE_80col: packed_data[0] = 0x80; // mode packed_data[1] = (context->Width / 8) - 1; break; case MOTO_MODE_bm16: packed_data[0] = 0x40; // mode packed_data[1] = (context->Width / 2) - 1; break; } packed_data[2] = (context->Height / 8) - 1; // 1st step : put data to pack in a linear buffer // 2nd step : pack data i = 0; switch (mode) { case MOTO_MODE_40col: case MOTO_MODE_bm4: for (bx = 0; bx <= packed_data[1]; bx++) { for (y = 0; y < context->Height; y++) { unpacked_data[i] = vram_forme[bx + y*(packed_data[1]+1)]; unpacked_data[i+8192] = vram_couleur[bx + y*(packed_data[1]+1)]; i++; } } i = 3; i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1)); packed_data[i++] = 0; // ending of VRAM forme packing packed_data[i++] = 0; i += MOTO_MAP_pack(packed_data+i, unpacked_data + 8192, context->Height * (packed_data[1]+1)); packed_data[i++] = 0; // ending of VRAM couleur packing packed_data[i++] = 0; break; case MOTO_MODE_80col: case MOTO_MODE_bm16: for (bx = 0; bx < (packed_data[1] + 1) / 2; bx++) { for (y = 0; y < context->Height; y++) unpacked_data[i++] = vram_forme[bx + y*(packed_data[1]+1)/2]; for (y = 0; y < context->Height; y++) unpacked_data[i++] = vram_couleur[bx + y*(packed_data[1]+1)/2]; } i = 3; i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1)); packed_data[i++] = 0; // ending of VRAM forme packing packed_data[i++] = 0; packed_data[i++] = 0; // ending of VRAM couleur packing packed_data[i++] = 0; break; } if (i&1) // align packed_data[i++] = 0; if (target_machine != MACHINE_TO7 && target_machine != MACHINE_TO770 && target_machine != MACHINE_MO5) { // add TO-SNAP extension // see http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 // bytes 0-1 : Hardware video mode (value of SCRMOD 0x605F) packed_data[i++] = 0; switch (mode) { case MOTO_MODE_40col: packed_data[i++] = 0; break; case MOTO_MODE_bm4: packed_data[i++] = 0x01; break; case MOTO_MODE_80col: packed_data[i++] = 0x80; break; case MOTO_MODE_bm16: packed_data[i++] = 0x40; break; } // bytes 2-3 : Border color packed_data[i++] = 0; packed_data[i++] = 0; // bytes 4-5 : BASIC video mode (CONSOLE,,,,X) packed_data[i++] = 0; switch (mode) { case MOTO_MODE_40col: packed_data[i++] = 0; break; case MOTO_MODE_bm4: packed_data[i++] = 2; break; case MOTO_MODE_80col: packed_data[i++] = 1; break; case MOTO_MODE_bm16: packed_data[i++] = 3; break; } // bytes 6-37 : BGR palette for (x = 0; x < 16; x++) { word bgr = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + x); packed_data[i++] = bgr >> 8; packed_data[i++] = bgr & 0xff; } // bytes 38-39 : TO-SNAP signature packed_data[i++] = 0xA5; packed_data[i++] = 0x5A; } free(unpacked_data); if (!DECB_BIN_Add_Chunk(file, i, 0, packed_data) || !DECB_BIN_Add_End(file, 0x0000)) { free(packed_data); goto error; } free(packed_data); } fclose(file); File_error = 0; return; error: free(vram_forme); free(vram_couleur); if (file) fclose(file); File_error = 1; }