/* vim:expandtab:ts=2 sw=2: */ /* Grafx2 - The Ultimate 256-color bitmap paint program Copyright 2018-2019 Thomas Bernard Copyright 2011 Pawel Góralski Copyright 2009 Petter Lindquist Copyright 2008 Yves Rizoud Copyright 2008 Franck Charlet Copyright 2007-2011 Adrien Destugues Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) Grafx2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. Grafx2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grafx2; if not, see */ ///@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 #define strdup _strdup #endif #include "global.h" #include "io.h" #include "loadsave.h" #include "loadsavefuncs.h" #include "misc.h" #include "struct.h" #include "windows.h" #include "oldies.h" #include "fileformats.h" #include "gfx2mem.h" #include "gfx2log.h" //////////////////////////////////// PAL //////////////////////////////////// // // -- Test wether a file is in PAL format -------------------------------- void Test_PAL(T_IO_Context * context, FILE * file) { char buffer[32]; long file_size; (void)context; File_error = 1; file_size = File_length_file(file); // First check for GrafX2 legacy palette format. The simplest one, 768 bytes // of RGB data. It is a raw dump of the T_Palette structure. There is no // header at all, so we check for the file size. if (file_size == sizeof(T_Palette)) File_error = 0; else if (file_size > 8) { // Bigger (or smaller ?) files may be in other formats. These have an // header, so look for it. if (!Read_bytes(file, buffer, 8)) return; if (strncmp(buffer,"JASC-PAL",8) == 0) { // JASC file format, used by Paint Shop Pro and GIMP. This is also the // one used for saving, as it brings greater interoperability. File_error = 0; } else if(strncmp(buffer,"RIFF", 4) == 0) { // Microsoft RIFF file // This is a data container (similar to IFF). We only check the first // chunk header, and give up if that's not a palette. fseek(file, 8, SEEK_SET); if (!Read_bytes(file, buffer, 8)) return; if (strncmp(buffer, "PAL data", 8) == 0) { File_error = 0; } } } } /// Test for GPL (Gimp Palette) file format void Test_GPL(T_IO_Context * context, FILE * file) { char buffer[16]; long file_size; (void)context; File_error = 1; file_size = File_length_file(file); if (file_size > 33) { // minimum header length == 33 // "GIMP Palette" == 12 if (!Read_bytes(file, buffer, 12)) return; if (strncmp(buffer,"GIMP Palette",12) == 0) File_error = 0; } } /// skip the padding before a space-padded field. static int skip_padding(FILE *file, int max_chars) { byte b; int chars_read = 0; do { if (chars_read == max_chars) return chars_read; // eof if (!Read_byte(file, &b)) return chars_read; chars_read++; } while (b == ' '); fseek(file, -1, SEEK_CUR); return chars_read; } /// Load GPL (Gimp Palette) file format void Load_GPL(T_IO_Context * context) { FILE *file; char buffer[256]; File_error = 1; file = Open_file_read(context); if (file == NULL) return; if (!Read_byte_line(file, buffer, sizeof(buffer))) return; if (memcmp(buffer,"GIMP Palette",12) == 0) { int i, r, g, b, columns; size_t len; // Name: xxxxx if (!Read_byte_line(file, buffer, sizeof(buffer))) return; len = strlen(buffer); while (len > 0) { len--; if (buffer[len] == '\r' || buffer[len] == '\n') buffer[len] = '\0'; } GFX2_Log(GFX2_DEBUG, "GPL %s\n", buffer); if (0 == memcmp(buffer, "Name: ", 6)) snprintf(context->Comment, sizeof(context->Comment), "GPL: %s", buffer + 6); // Columns: 16 if (fscanf(file, "Columns: %d", &columns) != 1) return; Read_byte_line(file, buffer, sizeof(buffer)); /// @todo set grafx2 columns setting to match. // # for (i = 0; i < 256; i++) { for (;;) { // skip comments int c = getc(file); if (c == '#') { if (!Read_byte_line(file, buffer, sizeof(buffer))) return; GFX2_Log(GFX2_DEBUG, "comment: %s", buffer); } else { fseek(file, -1, SEEK_CUR); break; } } skip_padding(file, 32); if (fscanf(file, "%d", &r) != 1) break; skip_padding(file, 32); if (fscanf(file, "%d", &g) != 1) break; skip_padding(file, 32); if (fscanf(file, "%d\t", &b) != 1) break; if (!Read_byte_line(file, buffer, sizeof(buffer))) break; len = strlen(buffer); while (len > 1) { len--; if (buffer[len] == '\r' || buffer[len] == '\n') buffer[len] = '\0'; } /// @todo analyze color names to build shade table GFX2_Log(GFX2_DEBUG, "GPL: %3d: RGB(%3d,%3d,%3d) %s\n", i, r,g,b, buffer); context->Palette[i].R = r; context->Palette[i].G = g; context->Palette[i].B = b; } if (i > 0) // at least one color was read File_error = 0; } else File_error = 2; // close the file fclose(file); } /// Save GPL (Gimp Palette) file format void Save_GPL (T_IO_Context * context) { // Gimp is a unix program, so use Unix file endings (LF aka '\n') FILE *file; file = Open_file_write(context); if (file != NULL ) { int i; File_error = 0; fprintf (file, "GIMP Palette\n"); fprintf (file, "Name: %s\n", context->File_name); // TODO: use actual columns value fprintf (file, "Columns: %d\n#\n", 16); for (i = 0; i < 256 && File_error==0; i++) { // TODO: build names from shade table data if (fprintf(file,"%d %d %d\tUntitled\n",context->Palette[i].R, context->Palette[i].G, context->Palette[i].B) <= 0) File_error=1; } fclose(file); if (File_error) Remove_file(context); } else { // unable to open output file, nothing saved. File_error=1; } } // -- Lire un fichier au format PAL ----------------------------------------- void Load_PAL(T_IO_Context * context) { FILE *file; // Fichier du fichier File_error=0; // Ouverture du fichier if ((file=Open_file_read(context))) { long file_size = File_length_file(file); // Le fichier ne peut être au format PAL que si sa taille vaut 768 octets if (file_size == sizeof(T_Palette)) { T_Palette palette_64; // Pre_load(context, ?); // Pas possible... pas d'image... // Lecture du fichier dans context->Palette if (Read_bytes(file, palette_64, sizeof(T_Palette))) { Palette_64_to_256(palette_64); memcpy(context->Palette, palette_64, sizeof(T_Palette)); } else File_error = 2; } else { char buffer[16]; if (!Read_bytes(file, buffer, 8)) { File_error = 2; fclose(file); return; } buffer[8] = '\0'; if (strncmp(buffer,"JASC-PAL",8) == 0) { int i, n, r, g, b; i = fscanf(file, "%d",&n); if(i != 1 || n != 100) { File_error = 2; fclose(file); return; } // Read color count if (fscanf(file, "%d",&n) == 1) { for (i = 0; i < n; i++) { if (fscanf(file, "%d %d %d",&r, &g, &b) == 3) { context->Palette[i].R = r; context->Palette[i].G = g; context->Palette[i].B = b; } else File_error = 2; } } else File_error = 2; } else if(strncmp(buffer, "RIFF", 4) == 0) { // Microsoft RIFF format. fseek(file, 8, SEEK_SET); Read_bytes(file, buffer, 8); if (strncmp(buffer, "PAL data", 8) == 0) { word color_count; word i = 0; fseek(file, 22, SEEK_SET); if (!Read_word_le(file, &color_count)) File_error = 2; else for(i = 0; i < color_count && File_error == 0; i++) { byte colors[4]; if (!Read_bytes(file, colors, 4)) File_error = 2; context->Palette[i].R = colors[0]; context->Palette[i].G = colors[1]; context->Palette[i].B = colors[2]; } } else File_error = 2; } else File_error = 2; } fclose(file); } else // Si on n'a pas réussi à ouvrir le fichier, alors il y a eu une erreur File_error=1; } // -- Sauver un fichier au format PAL --------------------------------------- void Save_PAL(T_IO_Context * context) { // JASC-PAL is a DOS/Windows format, so use CRLF line endings "\r\n" FILE *file; File_error=0; // Open output file if ((file=Open_file_write(context)) != NULL) { int i; setvbuf(file, NULL, _IOFBF, 64*1024); if (fputs("JASC-PAL\r\n0100\r\n256\r\n", file)==EOF) File_error=1; for (i = 0; i < 256 && File_error==0; i++) { if (fprintf(file,"%d %d %d\r\n",context->Palette[i].R, context->Palette[i].G, context->Palette[i].B) <= 0) File_error=1; } fclose(file); if (File_error) Remove_file(context); } else { // unable to open output file, nothing saved. File_error=1; } } //////////////////////////////////// PKM //////////////////////////////////// typedef struct { char Ident[3]; // String "PKM" } byte Method; // Compression method // 0 = per-line compression (c)KM // others = unknown at the moment byte Recog1; // Recognition byte 1 byte Recog2; // Recognition byte 2 word Width; // Image width word Height; // Image height T_Palette Palette;// RGB Palette 256*3, on a 1-64 scale for each component word Jump; // Size of the jump between header and image: // Used to insert a comment } T_PKM_Header; // -- Tester si un fichier est au format PKM -------------------------------- void Test_PKM(T_IO_Context * context, FILE * file) { T_PKM_Header header; (void)context; File_error=1; // Lecture du header du fichier if (Read_bytes(file,&header.Ident,3) && Read_byte(file,&header.Method) && Read_byte(file,&header.Recog1) && Read_byte(file,&header.Recog2) && Read_word_le(file,&header.Width) && Read_word_le(file,&header.Height) && Read_bytes(file,&header.Palette,sizeof(T_Palette)) && Read_word_le(file,&header.Jump)) { // On regarde s'il y a la signature PKM suivie de la méthode 0. // La constante "PKM" étant un chaîne, elle se termine toujours par 0. // Donc pas la peine de s'emm...er à regarder si la méthode est à 0. if ( (!memcmp(&header,"PKM",4)) && header.Width && header.Height) File_error=0; } } // -- Lire un fichier au format PKM ----------------------------------------- void Load_PKM(T_IO_Context * context) { FILE *file; // Fichier du fichier T_PKM_Header header; byte color; byte temp_byte; word len; word index; dword Compteur_de_pixels; dword Compteur_de_donnees_packees; dword image_size; dword Taille_pack; long file_size; File_error=0; if ((file=Open_file_read(context))) { file_size=File_length_file(file); if (Read_bytes(file,&header.Ident,3) && Read_byte(file,&header.Method) && Read_byte(file,&header.Recog1) && Read_byte(file,&header.Recog2) && Read_word_le(file,&header.Width) && Read_word_le(file,&header.Height) && Read_bytes(file,&header.Palette,sizeof(T_Palette)) && Read_word_le(file,&header.Jump)) { context->Comment[0]='\0'; // On efface le commentaire if (header.Jump) { index=0; while ( (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 GFX2_Log(GFX2_DEBUG, "PKM original screen %dx%d\n", (int)Original_screen_X, (int)Original_screen_Y); } else File_error=2; } else File_error=2; break; case 2 : // color de transparence if (Read_byte(file,&temp_byte)) { if (temp_byte==1) { index++; if (! Read_byte(file,&Back_color)) File_error=2; } else File_error=2; } else File_error=2; break; default: if (Read_byte(file,&temp_byte)) { index+=temp_byte; if (fseek(file,temp_byte,SEEK_CUR)) File_error=2; } else File_error=2; } } else File_error=2; } if ( (!File_error) && (index!=header.Jump) ) File_error=2; } /*Init_lecture();*/ if (!File_error) { Pre_load(context, header.Width,header.Height,file_size,FORMAT_PKM,PIXEL_SIMPLE,0); if (File_error==0) { context->Width=header.Width; context->Height=header.Height; image_size=(dword)(context->Width*context->Height); // Palette lue en 64 memcpy(context->Palette,header.Palette,sizeof(T_Palette)); Palette_64_to_256(context->Palette); Compteur_de_donnees_packees=0; Compteur_de_pixels=0; // Header size is 780 Taille_pack=(file_size)-780-header.Jump; // Boucle de décompression: while ( (Compteur_de_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; } /////////////////////////////////// FLI/FLC ///////////////////////////////// typedef struct { dword size; /* Size of FLIC including this header */ word type; /* File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ... */ word frames; /* Number of frames in first segment */ word width; /* FLIC width in pixels */ word height; /* FLIC height in pixels */ word depth; /* Bits per pixel (usually 8) */ word flags; /* Set to zero or to three */ dword speed; /* Delay between frames */ word reserved1; /* Set to zero */ dword created; /* Date of FLIC creation (FLC only) */ dword creator; /* Serial number or compiler id (FLC only) */ dword updated; /* Date of FLIC update (FLC only) */ dword updater; /* Serial number (FLC only), see creator */ word aspect_dx; /* Width of square rectangle (FLC only) */ word aspect_dy; /* Height of square rectangle (FLC only) */ word ext_flags; /* EGI: flags for specific EGI extensions */ word keyframes; /* EGI: key-image frequency */ word totalframes; /* EGI: total number of frames (segments) */ dword req_memory; /* EGI: maximum chunk size (uncompressed) */ word max_regions; /* EGI: max. number of regions in a CHK_REGION chunk */ word transp_num; /* EGI: number of transparent levels */ byte reserved2[24]; /* Set to zero */ dword oframe1; /* Offset to frame 1 (FLC only) */ dword oframe2; /* Offset to frame 2 (FLC only) */ byte reserved3[40]; /* Set to zero */ } T_FLIC_Header; static void Load_FLI_Header(FILE * file, T_FLIC_Header * header) { if (!(Read_dword_le(file,&header->size) && Read_word_le(file,&header->type) && Read_word_le(file,&header->frames) && Read_word_le(file,&header->width) && Read_word_le(file,&header->height) && Read_word_le(file,&header->depth) && Read_word_le(file,&header->flags) && Read_dword_le(file,&header->speed) && Read_word_le(file,&header->reserved1) && Read_dword_le(file,&header->created) && Read_dword_le(file,&header->creator) && Read_dword_le(file,&header->updated) && Read_dword_le(file,&header->updater) && Read_word_le(file,&header->aspect_dx) && Read_word_le(file,&header->aspect_dy) && Read_word_le(file,&header->ext_flags) && Read_word_le(file,&header->keyframes) && Read_word_le(file,&header->totalframes) && Read_dword_le(file,&header->req_memory) && Read_word_le(file,&header->max_regions) && Read_word_le(file,&header->transp_num) && Read_bytes(file,header->reserved2,24) && Read_dword_le(file,&header->oframe1) && Read_dword_le(file,&header->oframe2) && Read_bytes(file,header->reserved2,40) )) { File_error=1; } } /** * Test for the Autodesk Animator FLI/FLC format. * * Not to be confused with Commodore 64 FLI. */ void Test_FLI(T_IO_Context * context, FILE * file) { T_FLIC_Header header; (void)context; File_error=0; Load_FLI_Header(file, &header); if (File_error != 0) return; switch (header.type) { case 0xAF11: // standard FLI case 0xAF12: // FLC (8bpp) #if 0 case 0xAF30: // Huffman or BWT compression case 0xAF31: // frame shift compression case 0xAF44: // bpp != 8 #endif File_error=0; break; default: File_error=1; } } /** * Load file in the Autodesk Animator FLI/FLC format. * * Not to be confused with Commodore 64 FLI. */ void Load_FLI(T_IO_Context * context) { FILE * file; unsigned long file_size; T_FLIC_Header header; dword chunk_size; word chunk_type; word sub_chunk_count, sub_chunk_index; dword sub_chunk_size; word sub_chunk_type; word frame_delay, frame_width, frame_height; int current_frame = 0; file = Open_file_read(context); if (file == NULL) { File_error=1; return; } File_error=0; file_size = File_length_file(file); Load_FLI_Header(file, &header); if (File_error != 0) { fclose(file); return; } if (header.size == 12) { // special "magic carpet" format header.depth = 8; header.speed = 66; // about 15fps fseek(file, 12, SEEK_SET); } else if (file_size != header.size) GFX2_Log(GFX2_WARNING, "Load_FLI(): file size mismatch in header %lu != %u\n", file_size, header.size); if (header.speed == 0) { if (header.type == 0xAF11) // FLI header.speed = 1; // 1/70th seconds else header.speed = 10; // 10ms } while (File_error == 0 && Read_dword_le(file,&chunk_size) && Read_word_le(file,&chunk_type)) { chunk_size -= 6; switch (chunk_type) { case 0xf1fa: // FRAME Read_word_le(file, &sub_chunk_count); Read_word_le(file, &frame_delay); fseek(file, 2, SEEK_CUR); Read_word_le(file, &frame_width); Read_word_le(file, &frame_height); if (frame_width == 0) frame_width = header.width; if (frame_height == 0) frame_height = header.height; if (frame_delay == 0) frame_delay = header.speed; chunk_size -= 10; if (current_frame == 0) { Pre_load(context, header.width,header.height,file_size,FORMAT_FLI,PIXEL_SIMPLE,header.depth); Set_image_mode(context, IMAGE_MODE_ANIMATION); if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); } else { Set_loading_layer(context, current_frame); if (context->Type == CONTEXT_MAIN_IMAGE && Get_image_mode(context) == IMAGE_MODE_ANIMATION) { // Copy the content of previous frame memcpy( Main.backups->Pages->Image[Main.current_layer].Pixels, Main.backups->Pages->Image[Main.current_layer-1].Pixels, Main.backups->Pages->Width*Main.backups->Pages->Height); } } if (header.type == 0xAF11) // FLI Set_frame_duration(context, (frame_delay * 100) / 7); // 1/70th sec else Set_frame_duration(context, frame_delay); // msec current_frame++; for (sub_chunk_index = 0; sub_chunk_index < sub_chunk_count; sub_chunk_index++) { if (!(Read_dword_le(file,&sub_chunk_size) && Read_word_le(file,&sub_chunk_type))) File_error = 1; else { chunk_size -= sub_chunk_size; sub_chunk_size -= 6; if (sub_chunk_type == 0x04 || sub_chunk_type == 0x0b) // color map { word packet_count; int i = 0; sub_chunk_size -= 2; if (!Read_word_le(file, &packet_count)) File_error = 1; else while (packet_count-- > 0 && File_error == 0) { byte skip, count; if (!(Read_byte(file, &skip) && Read_byte(file, &count))) File_error = 1; else { sub_chunk_size -= 2; i += skip; // count 0 means 256 do { byte r, g, b; if (!(Read_byte(file, &r) && Read_byte(file, &g) && Read_byte(file, &b))) { File_error = 1; break; } if (sub_chunk_type == 0x0b || header.size == 12) // 6bit per color { r = (r << 2) | (r >> 4); g = (g << 2) | (g >> 4); b = (b << 2) | (b >> 4); } context->Palette[i].R = r; context->Palette[i].G = g; context->Palette[i].B = b; i++; sub_chunk_size -= 3; } while (--count != 0); } } } else if (sub_chunk_type == 0x0f) // full frame RLE { word x, y; for (y = 0; y < frame_height && File_error == 0; y++) { byte count, data; Read_byte(file, &count); // packet count, but dont rely on it sub_chunk_size--; for (x = 0; x < frame_width; ) { if (!Read_byte(file, &count)) { File_error = 1; break; } sub_chunk_size--; if ((count & 0x80) == 0) { if (!Read_byte(file, &data)) // repeat data count times { File_error = 1; break; } sub_chunk_size--; while (count-- > 0 && x < frame_width) Set_pixel(context, x++, y, data); } else while (count++ != 0 && x < frame_width) // copy count bytes { if (!Read_byte(file, &data)) { File_error = 1; break; } Set_pixel(context, x++, y, data); sub_chunk_size--; } } } if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE) { // load only 1st frame in preview fclose(file); return; } } else if (sub_chunk_type == 0x0c) // delta image, RLE { word x, y, line_count; Read_word_le(file, &y); Read_word_le(file, &line_count); sub_chunk_size -= 4; while (sub_chunk_size > 0 && line_count > 0 && File_error == 0) { byte packet_count; x = 0; if (!Read_byte(file, &packet_count)) File_error = 1; else { sub_chunk_size--; while (packet_count-- > 0 && File_error == 0) { byte skip, count, data; if (!(Read_byte(file, &skip) && Read_byte(file, &count))) File_error = 1; else { sub_chunk_size -= 2; x += skip; if (count & 0x80) { Read_byte(file, &data); sub_chunk_size--; while (count++ != 0) Set_pixel(context, x++, y, data); } else while (count-- > 0) { Read_byte(file, &data); sub_chunk_size--; Set_pixel(context, x++, y, data); } } } } y++; line_count--; } } else if (sub_chunk_type == 0x07) // FLC delta image { word opcode, y, line_count; y = 0; Read_word_le(file, &line_count); sub_chunk_size -= 2; while (line_count > 0) { Read_word_le(file, &opcode); sub_chunk_size -= 2; if ((opcode & 0xc000) == 0x0000) // packet count { word x = 0; while (opcode-- > 0) { byte skip, count, data1, data2; if (!(Read_byte(file, &skip) && Read_byte(file, &count))) File_error = 1; else { sub_chunk_size -= 2; x += skip; if (count & 0x80) { Read_byte(file, &data1); Read_byte(file, &data2); sub_chunk_size -= 2; while (count++ != 0) { Set_pixel(context, x++, y, data1); Set_pixel(context, x++, y, data2); } } else while (count-- > 0) { Read_byte(file, &data1); Set_pixel(context, x++, y, data1); Read_byte(file, &data2); Set_pixel(context, x++, y, data2); sub_chunk_size -= 2; } } } y++; line_count--; } else if ((opcode & 0xc000) == 0xc000) // line skip { y -= opcode; } else if ((opcode & 0xc000) == 0x8000) // last byte { Set_pixel(context, frame_width - 1, y, opcode & 0xff); } else { GFX2_Log(GFX2_WARNING, "Unsupported opcode %04x\n", opcode); File_error = 2; break; } } } if (sub_chunk_size > 0) { fseek(file, sub_chunk_size, SEEK_CUR); } } } break; default: // skip GFX2_Log(GFX2_WARNING, "Load_FLI(): unrecognized chunk %04x\n", chunk_type); } if (chunk_size > 0 && header.size != 12) { fseek(file, chunk_size, SEEK_CUR); } } fclose(file); } /////////////////////////////// Apple II Files ////////////////////////////// /** * Test for an Apple II HGR or DHGR raw file */ void Test_HGR(T_IO_Context * context, FILE * file) { long file_size; (void)context; File_error = 1; file_size = File_length_file(file); if (file_size == 8192) // HGR File_error = 0; else if(file_size == 16384) // DHGR File_error = 0; } /** * Load HGR (280x192) or DHGR (560x192) Apple II pictures * * Creates 2 layers : * 1. Monochrome * 2. Color */ void Load_HGR(T_IO_Context * context) { unsigned long file_size; FILE * file; byte * vram[2]; int bank; int x, y; int is_dhgr = 0; file = Open_file_read(context); if (file == NULL) { File_error = 1; return; } file_size = File_length_file(file); if (file_size == 16384) is_dhgr = 1; vram[0] = GFX2_malloc(8192); Read_bytes(file, vram[0], 8192); if (is_dhgr) { vram[1] = GFX2_malloc(8192); Read_bytes(file, vram[1], 8192); } else vram[1] = NULL; fclose(file); if (Config.Clear_palette) memset(context->Palette,0,sizeof(T_Palette)); if (is_dhgr) { DHGR_set_palette(context->Palette); Pre_load(context, 560, 192, file_size, FORMAT_HGR, PIXEL_TALL, 4); } else { HGR_set_palette(context->Palette); Pre_load(context, 280, 192, file_size, FORMAT_HGR, PIXEL_SIMPLE, 2); } for (y = 0; y < 192; y++) { byte palette = 0, color = 0; byte previous_palette = 0; // palette for the previous pixel pair int column, i; int offset = ((y & 7) << 10) + ((y & 070) << 4) + ((y >> 6) * 40); x = 0; for (column = 0; column < 40; column++) for (bank = 0; bank <= is_dhgr; bank++) { byte b = vram[bank][offset+column]; if (!is_dhgr) palette = (b & 0x80) ? 4 : 0; else palette = (b & 0x80) ? 0 : 16; for (i = 0; i < 7; i++) { if (context->Type == CONTEXT_MAIN_IMAGE) { // monochrome Set_loading_layer(context, 0); Set_pixel(context, x, y, ((b & 1) * (is_dhgr ? 15 : 3)) + palette); Set_loading_layer(context, 1); } // color color = (color << 1) | (b & 1); if (is_dhgr) { if ((x & 3) == 0) previous_palette = palette; // what is important is the value when the 1st bit was read... /// emulate "chat mauve" DHGR mixed mode. /// see http://boutillon.free.fr/Underground/Anim_Et_Graph/Extasie_Chat_Mauve_Reloaded/Extasie_Chat_Mauve_Reloaded.html if (previous_palette) // BW Set_pixel(context, x, y, ((b & 1) * 15) + palette); else if ((x & 3) == 3) { Set_pixel(context, x - 3, y, (color & 15) + palette); Set_pixel(context, x - 2, y, (color & 15) + palette); Set_pixel(context, x - 1, y, (color & 15) + palette); Set_pixel(context, x, y, (color & 15) + palette); } } else { /// HGR emulation following the behaviour of a "Le Chat Mauve" /// RVB adapter for the Apple //c. /// Within the bit stream, the color of the middle pixel is :
/// /// 111 \
/// 110 }- white
/// 011 /
/// 010 \ _ color
/// 101 /
/// 000 \
/// 001 }- black
/// 100 /
///
/// Color depends on the selected palette for the current byte /// and the position of the pixel (odd or even). if ((color & 3) == 3) // 11 => white { Set_pixel(context, x - 1, y, 3 + previous_palette); Set_pixel(context, x, y, 3 + palette); } else if ((color & 1) == 0) // 0 => black Set_pixel(context, x, y, palette); else // 01 => color { if ((color & 7) == 5) // 101 Set_pixel(context, x - 1, y, 2 - (x & 1) + previous_palette); Set_pixel(context, x, y, 2 - (x & 1) + palette); } previous_palette = palette; } b >>= 1; x++; } } } // show hidden data in HOLES for (y = 0; y < 64; y++) for (bank = 0; bank < 1; bank++) { byte b = 0; for (x = 0; x < 8; x++) b |= vram[bank][x + (y << 7) + 120]; if (b != 0) GFX2_LogHexDump(GFX2_DEBUG, bank ? "AUX " : "MAIN", vram[bank], (y << 7) + 120, 8); } free(vram[0]); free(vram[1]); File_error = 0; Set_image_mode(context, is_dhgr ? IMAGE_MODE_DHGR : IMAGE_MODE_HGR); } /** * Save HGR (280x192) or DHGR (560x192) Apple II pictures * * The data saved is the "monochrome" data from layer 1 */ void Save_HGR(T_IO_Context * context) { FILE * file; byte * vram[2]; int bank; int x, y; int is_dhgr = 0; File_error = 1; if (context->Height != 192 || (context->Width != 280 && context->Width != 560)) { Warning_message("Picture must be 280x192 (HGR) or 560x192 (DHGR)"); return; } if (context->Width == 560) is_dhgr = 1; file = Open_file_write(context); if (file == NULL) return; vram[0] = calloc(8192, 1); if (vram[0] == NULL) { fclose(file); return; } if (is_dhgr) { vram[1] = calloc(8192, 1); if (vram[1] == NULL) { free(vram[0]); fclose(file); return; } } else vram[1] = NULL; Set_saving_layer(context, 0); // "monochrome" layer for (y = 0; y < 192; y++) { int i, column = 0; int offset = ((y & 7) << 10) + ((y & 070) << 4) + ((y >> 6) * 40); x = 0; bank = 0; while (x < context->Width) { byte b; if (is_dhgr) b = (Get_pixel(context, x, y) & 16) ? 0 : 0x80; else b = (Get_pixel(context, x, y) & 4) ? 0x80 : 0; for (i = 0; i < 7; i++) { b = b | ((Get_pixel(context, x++, y) & 1) << i); } vram[bank][offset + column] = b; if (is_dhgr) { if (++bank > 1) { bank = 0; column++; } } else column++; } } if (Write_bytes(file, vram[0], 8192)) { if (is_dhgr) { if (Write_bytes(file, vram[1], 8192)) File_error = 0; } else File_error = 0; } free(vram[0]); free(vram[1]); fclose(file); } ///////////////////////////// HP-48 Grob Files //////////////////////////// /** * HP48 addresses are 20bits (5 nibbles) * offset is in nibble (half byte) */ static dword Read_HP48Address(const byte * buffer, int offset) { dword data = 0; int i = 4; do { byte nibble; nibble = buffer[(offset + i) >> 1]; if ((offset + i) & 1) nibble >>= 4; nibble &= 15; data = (data << 4) | nibble; } while (i-- > 0); return data; } /** * Test for a HP-48 Grob file */ void Test_GRB(T_IO_Context * context, FILE * file) { byte buffer[18]; unsigned long file_size; dword prologue, size, width, height; (void)context; File_error = 1; file_size = File_length_file(file); if (!Read_bytes(file, buffer, 18)) return; if(memcmp(buffer, "HPHP48-R", 8) != 0) return; prologue = Read_HP48Address(buffer+8, 0); size = Read_HP48Address(buffer+8, 5); GFX2_Log(GFX2_DEBUG, "HP48 File detected. %lu bytes prologue %05x %u nibbles\n", file_size, prologue, size); if (prologue != 0x02b1e) return; height = Read_HP48Address(buffer+8, 10); width = Read_HP48Address(buffer+8, 15); GFX2_Log(GFX2_DEBUG, " Grob dimensions : %ux%u\n", width, height); if ((file_size - 8) < ((size + 5) / 2)) return; if (file_size < (18 + ((width + 7) >> 3) * height)) return; File_error = 0; } void Load_GRB(T_IO_Context * context) { byte buffer[18]; byte * bitplane[4]; unsigned long file_size; dword prologue, size, width, height; byte bp, bpp; FILE * file; unsigned x, y; File_error = 1; file = Open_file_read(context); if (file == NULL) return; file_size = File_length_file(file); if (!Read_bytes(file, buffer, 18)) { fclose(file); return; } prologue = Read_HP48Address(buffer+8, 0); size = Read_HP48Address(buffer+8, 5); height = Read_HP48Address(buffer+8, 10); width = Read_HP48Address(buffer+8, 15); if (height >= 256) bpp = 4; else if (height >= 192) bpp = 3; else if (height >= 128) bpp = 2; else bpp = 1; GFX2_Log(GFX2_DEBUG, "HP48: %05X size=%u %ux%u\n", prologue, size, width, height); File_error = 0; Pre_load(context, width, height/bpp, file_size, FORMAT_GRB, PIXEL_SIMPLE, bpp); if (File_error == 0) { dword bytes_per_plane = ((width + 7) >> 3) * (height/bpp); dword offset = 0; if (Config.Clear_palette) memset(context->Palette, 0, sizeof(T_Palette)); for (x = 0; x < ((unsigned)1 << bpp); x++) { context->Palette[x].R = context->Palette[x].G = (x * 255) / ((1 << bpp) - 1); context->Palette[x].B = 127; } // Load all bit planes for (bp = 0; bp < bpp; bp++) { bitplane[bp] = GFX2_malloc(bytes_per_plane); if (bitplane[bp]) { if (!Read_bytes(file, bitplane[bp], bytes_per_plane)) File_error = 1; } } // set pixels for (y = 0; y < (height/bpp) && File_error == 0; y++) { for (x = 0; x < width; x++) { byte b = 0; for (bp = 0; bp < bpp; bp++) b |= ((bitplane[bp][offset] >> (x & 7)) & 1) << bp; // invert because 1 is a black pixel on HP-48 LCD display Set_pixel(context, x, y, b ^ ((1 << bpp) - 1)); if ((x & 7) == 7) offset++; } if ((x & 7) != 7) offset++; } // Free bit planes for (bp = 0; bp < bpp; bp++) free(bitplane[bp]); } fclose(file); }