diff --git a/src/Makefile b/src/Makefile index 84fcd73d..d1f8c940 100644 --- a/src/Makefile +++ b/src/Makefile @@ -810,7 +810,7 @@ OBJS = main.o init.o graph.o $(APIOBJ) misc.o special.o \ transform.o pversion.o factory.o $(PLATFORMOBJ) \ loadsave.o loadsavefuncs.o \ pngformat.o motoformats.o stformats.o c64formats.o cpcformats.o \ - ifformat.o msxformats.o packbits.o \ + ifformat.o msxformats.o packbits.o giformat.o \ fileformats.o miscfileformats.o libraw2crtc.o \ brush_ops.o buttons_effects.o layers.o \ oldies.o tiles.o colorred.o unicode.o gfx2surface.o \ @@ -823,7 +823,7 @@ TESTSOBJS = $(patsubst %.c,%.o,$(wildcard tests/*.c)) \ miscfileformats.o fileformats.o oldies.o libraw2crtc.o \ loadsavefuncs.o packbits.o tifformat.o c64load.o 6502.o \ pngformat.o motoformats.o stformats.o c64formats.o cpcformats.o \ - ifformat.o msxformats.o \ + ifformat.o msxformats.o giformat.o \ op_c.o colorred.o \ unicode.o \ io.o realpath.o version.o pversion.o \ diff --git a/src/fileformats.c b/src/fileformats.c index cfd1807e..3051bad5 100644 --- a/src/fileformats.c +++ b/src/fileformats.c @@ -56,7 +56,6 @@ #include "global.h" #include "loadsave.h" #include "loadsavefuncs.h" -#include "misc.h" #include "struct.h" #include "io.h" #include "pages.h" @@ -1806,1347 +1805,6 @@ void Save_ICO(T_IO_Context * context) /** @} */ -//////////////////////////////////// GIF //////////////////////////////////// -/** - * @defgroup GIF GIF format - * @ingroup loadsaveformats - * Graphics Interchange Format - * - * The GIF format uses LZW compression and stores indexed color pictures - * up to 256 colors. It has the ability to store several pictures in the same - * file : GrafX2 takes advantage of this feature for storing layered images - * and animations. - * - * GrafX2 implements GIF89a : - * https://www.w3.org/Graphics/GIF/spec-gif89a.txt - * - * @{ - */ - -/// Logical Screen Descriptor Block -typedef struct -{ - word Width; ///< Width of the complete image area - word Height; ///< Height of the complete image area - byte Resol; ///< Informations about the resolution (and other) - byte Backcol; ///< Proposed background color - byte Aspect; ///< Informations about aspect ratio. Ratio = (Aspect + 15) / 64 -} T_GIF_LSDB; - -/// Image Descriptor Block -typedef struct -{ - word Pos_X; ///< X offset where the image should be pasted - word Pos_Y; ///< Y offset where the image should be pasted - word Image_width; ///< Width of image - word Image_height; ///< Height of image - byte Indicator; ///< Misc image information - byte Nb_bits_pixel; ///< Nb de bits par pixel -} T_GIF_IDB; - -/// Graphic Control Extension -typedef struct -{ - byte Block_identifier; ///< 0x21 - byte Function; ///< 0xF9 - byte Block_size; ///< 4 - byte Packed_fields; ///< 11100000 : Reserved - ///< 00011100 : Disposal method - ///< 00000010 : User input flag - ///< 00000001 : Transparent flag - word Delay_time; ///< Time for this frame to stay displayed - byte Transparent_color; ///< Which color index acts as transparent - word Block_terminator; ///< 0x00 -} T_GIF_GCE; - -enum DISPOSAL_METHOD -{ - DISPOSAL_METHOD_UNDEFINED = 0, - DISPOSAL_METHOD_DO_NOT_DISPOSE = 1, - DISPOSAL_METHOD_RESTORE_BGCOLOR = 2, - DISPOSAL_METHOD_RESTORE_PREVIOUS = 3, -}; - - -/// Test if a file is GIF format -void Test_GIF(T_IO_Context * context, FILE * file) -{ - char signature[6]; - - (void)context; - File_error=1; - - if (Read_bytes(file,signature,6)) - { - /// checks if the signature (6 first bytes) is either GIF87a or GIF89a - if ((!memcmp(signature,"GIF87a",6))||(!memcmp(signature,"GIF89a",6))) - File_error=0; - } -} - - -// -- Lire un fichier au format GIF ----------------------------------------- - -typedef struct { - word nb_bits; ///< bits for a code - word remainder_bits; ///< available bits in @ref last_byte field - byte remainder_byte; ///< Remaining bytes in current block - word current_code; ///< current code (generally the one just read) - byte last_byte; ///< buffer byte for reading bits for codes - word pos_X; ///< Current coordinates - word pos_Y; - word interlaced; ///< interlaced flag - word pass; ///< current pass in interlaced decoding - word stop; ///< Stop flag (end of picture) -} T_GIF_context; - - -/// Reads the next code (GIF.nb_bits bits) -static word GIF_get_next_code(FILE * GIF_file, T_GIF_context * gif) -{ - word nb_bits_to_process = gif->nb_bits; - word nb_bits_processed = 0; - word current_nb_bits; - - gif->current_code = 0; - - while (nb_bits_to_process) - { - if (gif->remainder_bits == 0) // Il ne reste plus de bits... - { - // Lire l'octet suivant: - - // Si on a atteint la fin du bloc de Raster Data - if (gif->remainder_byte == 0) - { - // Lire l'octet nous donnant la taille du bloc de Raster Data suivant - if(Read_byte(GIF_file, &gif->remainder_byte)!=1) - { - File_error=2; - return 0; - } - if (gif->remainder_byte == 0) // still nothing ? That is the end data block - { - File_error = 2; - GFX2_Log(GFX2_WARNING, "GIF 0 sized data block\n"); - return gif->current_code; - } - } - if(Read_byte(GIF_file,&gif->last_byte)!=1) - { - File_error = 2; - GFX2_Log(GFX2_ERROR, "GIF failed to load data byte\n"); - return 0; - } - gif->remainder_byte--; - gif->remainder_bits=8; - } - - current_nb_bits=(nb_bits_to_process<=gif->remainder_bits)?nb_bits_to_process:gif->remainder_bits; - - gif->current_code |= (gif->last_byte & ((1<last_byte >>= current_nb_bits; - nb_bits_processed += current_nb_bits; - nb_bits_to_process -= current_nb_bits; - gif->remainder_bits -= current_nb_bits; - } - - return gif->current_code; -} - -/// Put a new pixel -static void GIF_new_pixel(T_IO_Context * context, T_GIF_context * gif, T_GIF_IDB *idb, int is_transparent, byte color) -{ - if (!is_transparent || color!=context->Transparent_color) - Set_pixel(context, idb->Pos_X+gif->pos_X, idb->Pos_Y+gif->pos_Y,color); - - gif->pos_X++; - - if (gif->pos_X >= idb->Image_width) - { - gif->pos_X=0; - - if (!gif->interlaced) - { - gif->pos_Y++; - if (gif->pos_Y >= idb->Image_height) - gif->stop = 1; - } - else - { - switch (gif->pass) - { - case 0 : - case 1 : gif->pos_Y+=8; - break; - case 2 : gif->pos_Y+=4; - break; - default: gif->pos_Y+=2; - } - - if (gif->pos_Y >= idb->Image_height) - { - switch(++(gif->pass)) - { - case 1 : gif->pos_Y=4; - break; - case 2 : gif->pos_Y=2; - break; - case 3 : gif->pos_Y=1; - break; - case 4 : gif->stop = 1; - } - } - } - } -} - - -/// Load GIF file -void Load_GIF(T_IO_Context * context) -{ - FILE *GIF_file; - int image_mode = -1; - char signature[6]; - - word * alphabet_stack; // Pile de décodage d'une chaîne - word * alphabet_prefix; // Table des préfixes des codes - word * alphabet_suffix; // Table des suffixes des codes - word alphabet_free; // Position libre dans l'alphabet - word alphabet_max; // Nombre d'entrées possibles dans l'alphabet - word alphabet_stack_pos; // Position dans la pile de décodage d'un chaîne - - T_GIF_context GIF; - T_GIF_LSDB LSDB; - T_GIF_IDB IDB; - T_GIF_GCE GCE; - - word nb_colors; // Nombre de couleurs dans l'image - word color_index; // index de traitement d'une couleur - byte size_to_read; // Nombre de données à lire (divers) - byte block_identifier; // Code indicateur du type de bloc en cours - byte initial_nb_bits; // Nb de bits au début du traitement LZW - word special_case=0; // Mémoire pour le cas spécial - word old_code=0; // Code précédent - word byte_read; // Sauvegarde du code en cours de lecture - word value_clr; // Valeur <=> Clear tables - word value_eof; // Valeur <=> End d'image - long file_size; - int number_LID; // Nombre d'images trouvées dans le fichier - int current_layer = 0; - int last_delay = 0; - byte is_transparent = 0; - byte is_looping=0; - enum PIXEL_RATIO ratio; - byte disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR; - - byte previous_disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR; - word previous_width=0; - word previous_height=0; - word previous_pos_x=0; - word previous_pos_y=0; - - /////////////////////////////////////////////////// FIN DES DECLARATIONS // - - - number_LID=0; - - if ((GIF_file=Open_file_read(context))) - { - file_size=File_length_file(GIF_file); - if ( (Read_bytes(GIF_file,signature,6)) && - ( (memcmp(signature,"GIF87a",6)==0) || - (memcmp(signature,"GIF89a",6)==0) ) ) - { - - // Allocation de mémoire pour les tables & piles de traitement: - alphabet_stack =(word *)malloc(4096*sizeof(word)); - alphabet_prefix=(word *)malloc(4096*sizeof(word)); - alphabet_suffix=(word *)malloc(4096*sizeof(word)); - - if (Read_word_le(GIF_file,&(LSDB.Width)) - && Read_word_le(GIF_file,&(LSDB.Height)) - && Read_byte(GIF_file,&(LSDB.Resol)) - && Read_byte(GIF_file,&(LSDB.Backcol)) - && Read_byte(GIF_file,&(LSDB.Aspect)) - ) - { - // Lecture du Logical Screen Descriptor Block réussie: - - Original_screen_X=LSDB.Width; - Original_screen_Y=LSDB.Height; - - ratio=PIXEL_SIMPLE; // (49 + 15) / 64 = 1:1 - if (LSDB.Aspect != 0) { - if (LSDB.Aspect < 25) // (17 + 15) / 64 = 1:2 - ratio=PIXEL_TALL; - else if (LSDB.Aspect < 41) // (33 + 15) / 64 = 3:4 - ratio=PIXEL_TALL3; - else if (LSDB.Aspect > 82) // (113 + 15) / 64 = 2:1 - ratio=PIXEL_WIDE; - } - - Pre_load(context, LSDB.Width,LSDB.Height,file_size,FORMAT_GIF,ratio,(LSDB.Resol&7)+1); - - // Palette globale dispo = (LSDB.Resol and $80) - // Profondeur de couleur =((LSDB.Resol and $70) shr 4)+1 - // Nombre de bits/pixel = (LSDB.Resol and $07)+1 - // Ordre de Classement = (LSDB.Aspect and $80) - - nb_colors=(1 << ((LSDB.Resol & 0x07)+1)); - if (LSDB.Resol & 0x80) - { - // Palette globale dispo: - - if (Config.Clear_palette) - memset(context->Palette,0,sizeof(T_Palette)); - - // Load the palette - for(color_index=0;color_indexPalette[color_index].R)); - Read_byte(GIF_file,&(context->Palette[color_index].G)); - Read_byte(GIF_file,&(context->Palette[color_index].B)); - } - } - - // On lit un indicateur de block - Read_byte(GIF_file,&block_identifier); - while (block_identifier!=0x3B && !File_error) - { - switch (block_identifier) - { - case 0x21: // Bloc d'extension - { - byte function_code; - // Lecture du code de fonction: - Read_byte(GIF_file,&function_code); - // Lecture de la taille du bloc: - Read_byte(GIF_file,&size_to_read); - while (size_to_read!=0 && !File_error) - { - switch(function_code) - { - case 0xFE: // Comment Block Extension - // On récupère le premier commentaire non-vide, - // on jette les autres. - if (context->Comment[0]=='\0') - { - int nb_char_to_keep = MIN(size_to_read, COMMENT_SIZE); - - Read_bytes(GIF_file,context->Comment,nb_char_to_keep); - context->Comment[nb_char_to_keep+1]='\0'; - // Si le commentaire etait trop long, on fait avance-rapide - // sur la suite. - if (size_to_read>nb_char_to_keep) - fseek(GIF_file,size_to_read-nb_char_to_keep,SEEK_CUR); - } - // Lecture de la taille du bloc suivant: - Read_byte(GIF_file,&size_to_read); - break; - case 0xF9: // Graphics Control Extension - // Prévu pour la transparence - if ( Read_byte(GIF_file,&(GCE.Packed_fields)) - && Read_word_le(GIF_file,&(GCE.Delay_time)) - && Read_byte(GIF_file,&(GCE.Transparent_color))) - { - previous_disposal_method = disposal_method; - disposal_method = (GCE.Packed_fields >> 2) & 7; - last_delay = GCE.Delay_time; - context->Transparent_color= GCE.Transparent_color; - is_transparent = GCE.Packed_fields & 1; - GFX2_Log(GFX2_DEBUG, "GIF Graphics Control Extension : transp=%d (color #%u) delay=%ums disposal_method=%d\n", is_transparent, GCE.Transparent_color, 10*GCE.Delay_time, disposal_method); - if (number_LID == 0) - context->Background_transparent = is_transparent; - is_transparent &= is_looping; - } - else - File_error=2; - // Lecture de la taille du bloc suivant: - Read_byte(GIF_file,&size_to_read); - break; - - case 0xFF: // Application Extension - // Normally, always a 11-byte block - if (size_to_read == 0x0B) - { - char aeb[0x0B]; - Read_bytes(GIF_file,aeb, 0x0B); - GFX2_Log(GFX2_DEBUG, "GIF extension \"%.11s\"\n", aeb); - if (File_error) - ; - else if (!memcmp(aeb,"NETSCAPE2.0",0x0B)) - { - is_looping=1; - // The well-known Netscape extension. - // Load as an animation - Set_image_mode(context, IMAGE_MODE_ANIMATION); - // Skip sub-block - do - { - if (! Read_byte(GIF_file,&size_to_read)) - File_error=1; - fseek(GIF_file,size_to_read,SEEK_CUR); - } while (!File_error && size_to_read!=0); - } - else if (!memcmp(aeb,"GFX2PATH\x00\x00\x00",0x0B)) - { - // Original file path - Read_byte(GIF_file,&size_to_read); - if (!File_error && size_to_read > 0) - { - free(context->Original_file_directory); - context->Original_file_directory = malloc(size_to_read); - Read_bytes(GIF_file, context->Original_file_directory, size_to_read); - Read_byte(GIF_file, &size_to_read); - if (!File_error && size_to_read > 0) - { - free(context->Original_file_name); - context->Original_file_name = malloc(size_to_read); - Read_bytes(GIF_file, context->Original_file_name, size_to_read); - Read_byte(GIF_file, &size_to_read); // Normally 0 - } - } - } - else if (!memcmp(aeb,"CRNG\0\0\0\0" "1.0",0x0B)) - { - // Color animation. Similar to a CRNG chunk in IFF file format. - word rate; - word flags; - byte min_col; - byte max_col; - // - Read_byte(GIF_file,&size_to_read); - for(;size_to_read>0 && !File_error;size_to_read-=6) - { - if ( (Read_word_be(GIF_file,&rate)) - && (Read_word_be(GIF_file,&flags)) - && (Read_byte(GIF_file,&min_col)) - && (Read_byte(GIF_file,&max_col))) - { - if (min_col != max_col) - { - // Valid cycling range - if (max_colCycle_range[context->Color_cycles].Start=min_col; - context->Cycle_range[context->Color_cycles].End=max_col; - context->Cycle_range[context->Color_cycles].Inverse=(flags&2)?1:0; - context->Cycle_range[context->Color_cycles].Speed=(flags&1)?rate/78:0; - - context->Color_cycles++; - } - } - else - { - File_error=1; - } - } - // Read end-of-block delimiter - if (!File_error) - Read_byte(GIF_file,&size_to_read); - if (size_to_read!=0) - File_error=1; - } - else if (0 == memcmp(aeb, "GFX2MODE", 8)) - { - Read_byte(GIF_file,&size_to_read); - if (size_to_read > 0) - { // read the image mode. We'll set it after having loaded all layers. - char * label = malloc((size_t)size_to_read + 1); - Read_bytes(GIF_file, label, size_to_read); - label[size_to_read] = '\0'; - image_mode = Constraint_mode_from_label(label); - GFX2_Log(GFX2_DEBUG, " mode = %s (%d)\n", label, image_mode); - free(label); - Read_byte(GIF_file,&size_to_read); - // be future proof, skip following sub-blocks : - while (size_to_read!=0 && !File_error) - { - if (fseek(GIF_file,size_to_read,SEEK_CUR) < 0) - File_error = 1; - if (!Read_byte(GIF_file,&size_to_read)) - File_error = 1; - } - } - } - else - { - // Unknown extension, skip. - Read_byte(GIF_file,&size_to_read); - while (size_to_read!=0 && !File_error) - { - if (fseek(GIF_file,size_to_read,SEEK_CUR) < 0) - File_error = 1; - if (!Read_byte(GIF_file,&size_to_read)) - File_error = 1; - } - } - } - else - { - fseek(GIF_file,size_to_read,SEEK_CUR); - // Lecture de la taille du bloc suivant: - Read_byte(GIF_file,&size_to_read); - } - break; - - default: - // On saute le bloc: - fseek(GIF_file,size_to_read,SEEK_CUR); - // Lecture de la taille du bloc suivant: - Read_byte(GIF_file,&size_to_read); - break; - } - } - } - break; - case 0x2C: // Local Image Descriptor - { - if (number_LID!=0) - { - // This a second layer/frame, or more. - // Attempt to add a layer to current image - current_layer++; - Set_loading_layer(context, current_layer); - if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) - { - // Copy the content of previous layer. - 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); - } - else - { - Fill_canvas(context, is_transparent ? context->Transparent_color : LSDB.Backcol); - } - } - else - { - // First frame/layer, fill canvas with backcolor - Fill_canvas(context, is_transparent ? context->Transparent_color : LSDB.Backcol); - } - // Duration was set in the previously loaded GCE - Set_frame_duration(context, last_delay*10); - number_LID++; - - // lecture de 10 derniers octets - if ( Read_word_le(GIF_file,&(IDB.Pos_X)) - && Read_word_le(GIF_file,&(IDB.Pos_Y)) - && Read_word_le(GIF_file,&(IDB.Image_width)) - && Read_word_le(GIF_file,&(IDB.Image_height)) - && Read_byte(GIF_file,&(IDB.Indicator)) - && IDB.Image_width && IDB.Image_height) - { - GFX2_Log(GFX2_DEBUG, "GIF Image descriptor %u Pos (%u,%u) %ux%u %s%slocal palette(%ubpp)\n", - number_LID, IDB.Pos_X, IDB.Pos_Y, IDB.Image_width, IDB.Image_height, - (IDB.Indicator & 0x40) ? "interlaced " : "", (IDB.Indicator & 0x80) ? "" : "no ", - (IDB.Indicator & 7) + 1); - // Palette locale dispo = (IDB.Indicator and $80) - // Image entrelacée = (IDB.Indicator and $40) - // Ordre de classement = (IDB.Indicator and $20) - // Nombre de bits/pixel = (IDB.Indicator and $07)+1 (si palette locale dispo) - - if (IDB.Indicator & 0x80) - { - // Palette locale dispo - - if (Config.Clear_palette) - memset(context->Palette,0,sizeof(T_Palette)); - - nb_colors=(1 << ((IDB.Indicator & 0x07)+1)); - // Load the palette - for(color_index=0;color_indexPalette[color_index].R)); - Read_byte(GIF_file,&(context->Palette[color_index].G)); - Read_byte(GIF_file,&(context->Palette[color_index].B)); - } - - } - if (number_LID!=1) - { - // This a second layer/frame, or more. - if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) - { - // Need to clear previous image to back-color. - if (previous_disposal_method==DISPOSAL_METHOD_RESTORE_BGCOLOR) - { - int y; - for (y=0; yPages->Image[Main.current_layer].Pixels - + (previous_pos_y+y)* Main.backups->Pages->Width+previous_pos_x, - is_transparent ? context->Transparent_color : LSDB.Backcol, - previous_width); - } - } - } - previous_height=IDB.Image_height; - previous_width=IDB.Image_width; - previous_pos_x=IDB.Pos_X; - previous_pos_y=IDB.Pos_Y; - - File_error=0; - if (!Read_byte(GIF_file,&(initial_nb_bits))) - File_error=1; - - value_clr =(1< alphabet_free) - { - GFX2_Log(GFX2_INFO, "Load_GIF() Invalid code %u (should be <=%u)\n", GIF.current_code, alphabet_free); - File_error=2; - break; - } - else if (GIF.current_code != value_clr) - { - byte_read = GIF.current_code; - if (alphabet_free == GIF.current_code) - { - GIF.current_code=old_code; - alphabet_stack[alphabet_stack_pos++]=special_case; - } - - while (GIF.current_code > value_clr) - { - if (GIF.current_code >= 4096) - { - GFX2_Log(GFX2_ERROR, "Load_GIF() GIF.current_code = %u >= 4096\n", GIF.current_code); - File_error = 2; - break; - } - alphabet_stack[alphabet_stack_pos++] = alphabet_suffix[GIF.current_code]; - GIF.current_code = alphabet_prefix[GIF.current_code]; - } - - special_case = alphabet_stack[alphabet_stack_pos++] = GIF.current_code; - - do - GIF_new_pixel(context, &GIF, &IDB, is_transparent, alphabet_stack[--alphabet_stack_pos]); - while (alphabet_stack_pos!=0); - - alphabet_prefix[alphabet_free ]=old_code; - alphabet_suffix[alphabet_free++]=GIF.current_code; - old_code=byte_read; - - if (alphabet_free>alphabet_max) - { - if (GIF.nb_bits<12) - alphabet_max =((1 << (++GIF.nb_bits))-1); - } - } - else // Clear code - { - GIF.nb_bits = initial_nb_bits + 1; - alphabet_max = ((1 << GIF.nb_bits)-1); - alphabet_free = (1<= value_clr) - { - GFX2_Log(GFX2_INFO, "Load_GIF() Invalid code %u just after clear (=%u)!\n", - GIF.current_code, value_clr); - File_error = 2; - break; - } - old_code = GIF.current_code; - GIF_new_pixel(context, &GIF, &IDB, is_transparent, GIF.current_code); - } - } - - if (File_error == 2 && GIF.pos_X == 0 && GIF.pos_Y == IDB.Image_height) - File_error=0; - - if (File_error >= 0 && !GIF.stop) - File_error=2; - - // No need to read more than one frame in animation preview mode - if (context->Type == CONTEXT_PREVIEW && is_looping) - { - goto early_exit; - } - // Same with brush - if (context->Type == CONTEXT_BRUSH && is_looping) - { - goto early_exit; - } - - } // Le fichier contenait un IDB - else - File_error=2; - } - default: - break; - } - // Lecture du code de fonction suivant: - if (!Read_byte(GIF_file,&block_identifier)) - File_error=2; - } - - // set the mode that have been read previously. - if (image_mode > 0) - Set_image_mode(context, image_mode); - } // Le fichier contenait un LSDB - else - File_error=1; - - early_exit: - - // Libération de la mémoire utilisée par les tables & piles de traitement: - free(alphabet_suffix); - free(alphabet_prefix); - free(alphabet_stack); - alphabet_suffix = alphabet_prefix = alphabet_stack = NULL; - } // Le fichier contenait au moins la signature GIF87a ou GIF89a - else - File_error=1; - - fclose(GIF_file); - - } // Le fichier était ouvrable - else - File_error=1; -} - - -// -- Sauver un fichier au format GIF --------------------------------------- - -/// Flush the buffer -static void GIF_empty_buffer(FILE * file, T_GIF_context *gif, byte * GIF_buffer) -{ - word index; - - if (gif->remainder_byte) - { - GIF_buffer[0] = gif->remainder_byte; - - for (index = 0; index <= gif->remainder_byte; index++) - Write_one_byte(file, GIF_buffer[index]); - - gif->remainder_byte=0; - } -} - -/// Write a code (GIF_nb_bits bits) -static void GIF_set_code(FILE * GIF_file, T_GIF_context * gif, byte * GIF_buffer, word Code) -{ - word nb_bits_to_process = gif->nb_bits; - word nb_bits_processed =0; - word current_nb_bits; - - while (nb_bits_to_process) - { - current_nb_bits = (nb_bits_to_process <= (8-gif->remainder_bits)) ? - nb_bits_to_process: (8-gif->remainder_bits); - - gif->last_byte |= (Code & ((1<remainder_bits; - Code>>=current_nb_bits; - gif->remainder_bits +=current_nb_bits; - nb_bits_processed +=current_nb_bits; - nb_bits_to_process-=current_nb_bits; - - if (gif->remainder_bits==8) // Il ne reste plus de bits à coder sur l'octet courant - { - // Ecrire l'octet à balancer: - GIF_buffer[++(gif->remainder_byte)] = gif->last_byte; - - // Si on a atteint la fin du bloc de Raster Data - if (gif->remainder_byte==255) - // On doit vider le buffer qui est maintenant plein - GIF_empty_buffer(GIF_file, gif, GIF_buffer); - - gif->last_byte=0; - gif->remainder_bits=0; - } - } -} - - -/// Read the next pixel -static byte GIF_next_pixel(T_IO_Context *context, T_GIF_context *gif, T_GIF_IDB *idb) -{ - byte temp; - - temp = Get_pixel(context, gif->pos_X, gif->pos_Y); - - if (++gif->pos_X >= (idb->Image_width + idb->Pos_X)) - { - gif->pos_X = idb->Pos_X; - if (++gif->pos_Y >= (idb->Image_height + idb->Pos_Y)) - gif->stop = 1; - } - - return temp; -} - - -/// Save a GIF file -void Save_GIF(T_IO_Context * context) -{ - FILE * GIF_file; - byte GIF_buffer[256]; // buffer d'écriture de bloc de données compilées - - word * alphabet_prefix; // Table des préfixes des codes - word * alphabet_suffix; // Table des suffixes des codes - word * alphabet_daughter; // Table des chaînes filles (plus longues) - word * alphabet_sister; // Table des chaînes soeurs (même longueur) - word alphabet_free; // Position libre dans l'alphabet - word alphabet_max; // Nombre d'entrées possibles dans l'alphabet - word start; // Code précédent (sert au linkage des chaînes) - int descend; // Booléen "On vient de descendre" - - T_GIF_context GIF; - T_GIF_LSDB LSDB; - T_GIF_IDB IDB; - - - byte block_identifier; // Code indicateur du type de bloc en cours - word current_string; // Code de la chaîne en cours de traitement - byte current_char; // Caractère à coder - word index; // index de recherche de chaîne - int current_layer; - - word clear; // LZW clear code - word eof; // End of image code - - /////////////////////////////////////////////////// FIN DES DECLARATIONS // - - File_error=0; - - if ((GIF_file=Open_file_write(context))) - { - setvbuf(GIF_file, NULL, _IOFBF, 64*1024); - - // On écrit la signature du fichier - if (Write_bytes(GIF_file,"GIF89a",6)) - { - // La signature du fichier a été correctement écrite. - - // Allocation de mémoire pour les tables - alphabet_prefix=(word *)malloc(4096*sizeof(word)); - alphabet_suffix=(word *)malloc(4096*sizeof(word)); - alphabet_daughter =(word *)malloc(4096*sizeof(word)); - alphabet_sister =(word *)malloc(4096*sizeof(word)); - - // On initialise le LSDB du fichier - if (Config.Screen_size_in_GIF) - { - LSDB.Width=Screen_width; - LSDB.Height=Screen_height; - } - else - { - LSDB.Width=context->Width; - LSDB.Height=context->Height; - } - LSDB.Resol = 0xF7; // Global palette of 256 entries, 256 color image - // 0xF7 = 1111 0111 - // = Global Color Table Flag 1 Bit - // Color Resolution 3 Bits - // Sort Flag 1 Bit - // Size of Global Color Table 3 Bits - LSDB.Backcol=context->Transparent_color; - switch(context->Ratio) - { - case PIXEL_TALL: - case PIXEL_TALL2: - LSDB.Aspect = 17; // 1:2 = 2:4 - break; - case PIXEL_TALL3: - LSDB.Aspect = 33; // 3:4 - break; - case PIXEL_WIDE: - case PIXEL_WIDE2: - LSDB.Aspect = 113; // 2:1 = 4:2 - break; - default: - LSDB.Aspect = 0; // undefined, which is most frequent. - // 49 would be 1:1 ratio - } - - // On sauve le LSDB dans le fichier - - if (Write_word_le(GIF_file,LSDB.Width) && - Write_word_le(GIF_file,LSDB.Height) && - Write_byte(GIF_file,LSDB.Resol) && - Write_byte(GIF_file,LSDB.Backcol) && - Write_byte(GIF_file,LSDB.Aspect) ) - { - // Le LSDB a été correctement écrit. - int i; - // On sauve la palette - for(i=0;i<256 && !File_error;i++) - { - if (!Write_byte(GIF_file,context->Palette[i].R) - ||!Write_byte(GIF_file,context->Palette[i].G) - ||!Write_byte(GIF_file,context->Palette[i].B)) - File_error=1; - } - if (!File_error) - { - // La palette a été correctement écrite. - - /// - "Netscape" animation extension : - ///
-          ///   0x21       Extension Label
-          ///   0xFF       Application Extension Label
-          ///   0x0B       Block Size
-          ///   "NETSCAPE" Application Identifier (8 bytes)
-          ///   "2.0"      Application Authentication Code (3 bytes)
-          ///   0x03       Sub-block Data Size
-          ///   0xLL       01 to loop
-          ///   0xSSSS     (little endian) number of loops, 0 means infinite loop
-          ///   0x00 Block terminator 
- /// see http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension - if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) - { - if (context->Nb_layers>1) - Write_bytes(GIF_file,"\x21\xFF\x0BNETSCAPE2.0\x03\x01\x00\x00\x00",19); - } - else if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode > IMAGE_MODE_ANIMATION) - { - /// - GrafX2 extension to store ::IMAGE_MODES : - ///
-            ///   0x21       Extension Label
-            ///   0xFF       Application Extension Label
-            ///   0x0B       Block Size
-            ///   "GFX2MODE" Application Identifier (8 bytes)
-            ///   "2.6"      Application Authentication Code (3 bytes)
-            ///   0xll       Sub-block Data Size
-            ///   string     label
-            ///   0x00 Block terminator 
- /// @see Constraint_mode_label() - const char * label = Constraint_mode_label(Main.backups->Pages->Image_mode); - if (label != NULL) - { - size_t len = strlen(label); - // Write extension for storing IMAGE_MODE - Write_byte(GIF_file,0x21); // Extension Introducer - Write_byte(GIF_file,0xff); // Extension Label - Write_byte(GIF_file, 11); // Block size - Write_bytes(GIF_file, "GFX2MODE2.6", 11); // Application Identifier + Appl. Authentication Code - Write_byte(GIF_file, (byte)len); // Block size - Write_bytes(GIF_file, label, len); // Data - Write_byte(GIF_file, 0); // Block terminator - } - } - - // Ecriture du commentaire - if (context->Comment[0]) - { - Write_bytes(GIF_file,"\x21\xFE",2); - Write_byte(GIF_file, (byte)strlen(context->Comment)); - Write_bytes(GIF_file,context->Comment,strlen(context->Comment)+1); - } - /// - "CRNG" Color cycing extension : - ///
-          ///   0x21       Extension Label
-          ///   0xFF       Application Extension Label
-          ///   0x0B       Block Size
-          ///   "CRNG\0\0\0\0" "CRNG" Application Identifier (8 bytes)
-          ///   "1.0"      Application Authentication Code (3 bytes)
-          ///   0xll       Sub-block Data Size (6 bytes per color cycle)
-          ///   For each color cycle :
-          ///     0xRRRR   (big endian) Rate
-          ///     0xFFFF   (big endian) Flags
-          ///     0xSS     start (lower color index)
-          ///     0xEE     end (higher color index)
-          ///   0x00       Block terminator 
- if (context->Color_cycles) - { - int i; - - Write_bytes(GIF_file,"\x21\xff\x0B" "CRNG\0\0\0\0" "1.0",14); - Write_byte(GIF_file,context->Color_cycles*6); - for (i=0; iColor_cycles; i++) - { - word flags=0; - flags|= context->Cycle_range[i].Speed?1:0; // Cycling or not - flags|= context->Cycle_range[i].Inverse?2:0; // Inverted - - Write_word_be(GIF_file,context->Cycle_range[i].Speed*78); // Rate - Write_word_be(GIF_file,flags); // Flags - Write_byte(GIF_file,context->Cycle_range[i].Start); // Min color - Write_byte(GIF_file,context->Cycle_range[i].End); // Max color - } - Write_byte(GIF_file,0); - } - - // Loop on all layers - for (current_layer=0; - current_layer < context->Nb_layers && !File_error; - current_layer++) - { - // Write a Graphic Control Extension - T_GIF_GCE GCE; - byte disposal_method; - - Set_saving_layer(context, current_layer); - - GCE.Block_identifier = 0x21; - GCE.Function = 0xF9; - GCE.Block_size=4; - - if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) - { - // Animation frame - int duration; - if(context->Background_transparent) - disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR; - else - disposal_method = DISPOSAL_METHOD_DO_NOT_DISPOSE; - GCE.Packed_fields=(disposal_method<<2)|(context->Background_transparent); - duration=Get_frame_duration(context)/10; - GCE.Delay_time=duration<0xFFFF?duration:0xFFFF; - } - else - { - // Layered image or brush - disposal_method = DISPOSAL_METHOD_DO_NOT_DISPOSE; - if (current_layer==0) - GCE.Packed_fields=(disposal_method<<2)|(context->Background_transparent); - else - GCE.Packed_fields=(disposal_method<<2)|(1); - GCE.Delay_time=5; // Duration 5/100s (minimum viable value for current web browsers) - if (current_layer == context->Nb_layers -1) - GCE.Delay_time=0xFFFF; // Infinity (10 minutes) - } - GCE.Transparent_color=context->Transparent_color; - GCE.Block_terminator=0x00; - - if (Write_byte(GIF_file,GCE.Block_identifier) - && Write_byte(GIF_file,GCE.Function) - && Write_byte(GIF_file,GCE.Block_size) - && Write_byte(GIF_file,GCE.Packed_fields) - && Write_word_le(GIF_file,GCE.Delay_time) - && Write_byte(GIF_file,GCE.Transparent_color) - && Write_byte(GIF_file,GCE.Block_terminator) - ) - { - byte temp, max = 0; - - IDB.Pos_X=0; - IDB.Pos_Y=0; - IDB.Image_width=context->Width; - IDB.Image_height=context->Height; - if(current_layer > 0) - { - word min_X, max_X, min_Y, max_Y; - // find bounding box of changes for Animated GIFs - min_X = min_Y = 0xffff; - max_X = max_Y = 0; - for(GIF.pos_Y = 0; GIF.pos_Y < context->Height; GIF.pos_Y++) { - for(GIF.pos_X = 0; GIF.pos_X < context->Width; GIF.pos_X++) { - if (GIF.pos_X >= min_X && GIF.pos_X <= max_X && GIF.pos_Y >= min_Y && GIF.pos_Y <= max_Y) - continue; // already in the box - if(disposal_method == DISPOSAL_METHOD_DO_NOT_DISPOSE) - { - // if that pixel has same value in previous layer, no need to save it - Set_saving_layer(context, current_layer - 1); - temp = Get_pixel(context, GIF.pos_X, GIF.pos_Y); - Set_saving_layer(context, current_layer); - if(temp == Get_pixel(context, GIF.pos_X, GIF.pos_Y)) - continue; - } - if (disposal_method == DISPOSAL_METHOD_RESTORE_BGCOLOR - || context->Background_transparent - || Main.backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) - { - // if that pixel is Backcol, no need to save it - if (LSDB.Backcol == Get_pixel(context, GIF.pos_X, GIF.pos_Y)) - continue; - } - if(GIF.pos_X < min_X) min_X = GIF.pos_X; - if(GIF.pos_X > max_X) max_X = GIF.pos_X; - if(GIF.pos_Y < min_Y) min_Y = GIF.pos_Y; - if(GIF.pos_Y > max_Y) max_Y = GIF.pos_Y; - } - } - if((min_X <= max_X) && (min_Y <= max_Y)) - { - IDB.Pos_X = min_X; - IDB.Pos_Y = min_Y; - IDB.Image_width = max_X + 1 - min_X; - IDB.Image_height = max_Y + 1 - min_Y; - } - else - { - // if no pixel changes, store a 1 pixel image - IDB.Image_width = 1; - IDB.Image_height = 1; - } - } - - // look for the maximum pixel value - // to decide how many bit per pixel are needed. - for(GIF.pos_Y = IDB.Pos_Y; GIF.pos_Y < IDB.Image_height + IDB.Pos_Y; GIF.pos_Y++) { - for(GIF.pos_X = IDB.Pos_X; GIF.pos_X < IDB.Image_width + IDB.Pos_X; GIF.pos_X++) { - temp=Get_pixel(context, GIF.pos_X, GIF.pos_Y); - if(temp > max) max = temp; - } - } - IDB.Nb_bits_pixel=2; // Find the minimum bpp value to fit all pixels - while((int)max >= (1 << IDB.Nb_bits_pixel)) { - IDB.Nb_bits_pixel++; - } - GFX2_Log(GFX2_DEBUG, "GIF image #%d %ubits (%u,%u) %ux%u\n", - current_layer, IDB.Nb_bits_pixel, IDB.Pos_X, IDB.Pos_Y, - IDB.Image_width, IDB.Image_height); - - // On va écrire un block indicateur d'IDB et l'IDB du fichier - block_identifier=0x2C; - IDB.Indicator=0x07; // Image non entrelacée, pas de palette locale. - clear = 1 << IDB.Nb_bits_pixel; // Clear Code - eof = clear + 1; // End of Picture Code - - if ( Write_byte(GIF_file,block_identifier) && - Write_word_le(GIF_file,IDB.Pos_X) && - Write_word_le(GIF_file,IDB.Pos_Y) && - Write_word_le(GIF_file,IDB.Image_width) && - Write_word_le(GIF_file,IDB.Image_height) && - Write_byte(GIF_file,IDB.Indicator) && - Write_byte(GIF_file,IDB.Nb_bits_pixel)) - { - // Le block indicateur d'IDB et l'IDB ont étés correctements - // écrits. - - GIF.pos_X=IDB.Pos_X; - GIF.pos_Y=IDB.Pos_Y; - GIF.last_byte=0; - GIF.remainder_bits=0; - GIF.remainder_byte=0; - -#define GIF_INVALID_CODE (65535) - index=GIF_INVALID_CODE; - File_error=0; - GIF.stop=0; - - // Réintialisation de la table: - alphabet_free=clear + 2; // 258 for 8bpp - GIF.nb_bits =IDB.Nb_bits_pixel + 1; // 9 for 8 bpp - alphabet_max =clear+clear-1; // 511 for 8bpp - GIF_set_code(GIF_file, &GIF, GIF_buffer, clear); //256 for 8bpp - for (start=0;start<4096;start++) - { - alphabet_daughter[start] = GIF_INVALID_CODE; - alphabet_sister[start] = GIF_INVALID_CODE; - } - - ////////////////////////////////////////////// COMPRESSION LZW // - - start=current_string=GIF_next_pixel(context, &GIF, &IDB); - descend=1; - - while ((!GIF.stop) && (!File_error)) - { - current_char=GIF_next_pixel(context, &GIF, &IDB); - - // look for (current_string,current_char) in the alphabet - while ( (index != GIF_INVALID_CODE) && - ( (current_string!=alphabet_prefix[index]) || - (current_char !=alphabet_suffix[index]) ) ) - { - descend=0; - start=index; - index=alphabet_sister[index]; - } - - if (index != GIF_INVALID_CODE) - { - // (current_string,current_char) == (alphabet_prefix,alphabet_suffix)[index] - // We have found (current_string,current_char) in the alphabet - // at the index position. So go on and prepare for then next character - - descend=1; - start=current_string=index; - index=alphabet_daughter[index]; - } - else - { - // (current_string,current_char) was not found in the alphabet - // so write current_string to the Gif stream - GIF_set_code(GIF_file, &GIF, GIF_buffer, current_string); - - if(alphabet_free < 4096) { - // link current_string and the new one - if (descend) - alphabet_daughter[start]=alphabet_free; - else - alphabet_sister[start]=alphabet_free; - - // add (current_string,current_char) to the alphabet - alphabet_prefix[alphabet_free]=current_string; - alphabet_suffix[alphabet_free]=current_char; - alphabet_free++; - } - - if (alphabet_free >= 4096) - { - // clear alphabet - GIF_set_code(GIF_file, &GIF, GIF_buffer, clear); // 256 for 8bpp - alphabet_free=clear+2; // 258 for 8bpp - GIF.nb_bits =IDB.Nb_bits_pixel + 1; // 9 for 8bpp - alphabet_max =clear+clear-1; // 511 for 8bpp - for (start=0;start<4096;start++) - { - alphabet_daughter[start] = GIF_INVALID_CODE; - alphabet_sister[start] = GIF_INVALID_CODE; - } - } - else if (alphabet_free>alphabet_max+1) - { - // On augmente le nb de bits - - GIF.nb_bits++; - alphabet_max = (1< alphabet_max+1) && (GIF.nb_bits < 12)) - { - GIF.nb_bits++; - alphabet_max = (1 << GIF.nb_bits) - 1; - } - } - - GIF_set_code(GIF_file, &GIF, GIF_buffer, eof); // 257 for 8bpp // Code de End d'image - if (GIF.remainder_bits!=0) - { - // Write last byte (this is an incomplete byte) - GIF_buffer[++GIF.remainder_byte]=GIF.last_byte; - GIF.last_byte=0; - GIF.remainder_bits=0; - } - GIF_empty_buffer(GIF_file, &GIF, GIF_buffer); // On envoie les dernières données du buffer GIF dans le buffer KM - - // On écrit un \0 - if (! Write_byte(GIF_file,'\x00')) - File_error=1; - } - - } // On a pu écrire l'IDB - else - File_error=1; - } - else - File_error=1; - } - - // After writing all layers - if (!File_error) - { - /// - If requested, write a specific extension for storing - /// original file path. - /// This is used by the backup system. - /// The format is : - ///
-            ///   0x21       Extension Label
-            ///   0xFF       Application Extension Label
-            ///   0x0B       Block Size
-            ///   "GFX2PATH" "GFX2PATH" Application Identifier (8 bytes)
-            ///   "\0\0\0"   Application Authentication Code (3 bytes)
-            ///   0xll       Sub-block Data Size : path size (including null)
-            ///   "..path.." path (null-terminated)
-            ///   0xll       Sub-block Data Size : filename size (including null)
-            ///   "..file.." file name (null-terminated)
-            ///   0x00       Block terminator 
- if (context->Original_file_name != NULL - && context->Original_file_directory != NULL) - { - long name_size = 1+strlen(context->Original_file_name); - long dir_size = 1+strlen(context->Original_file_directory); - if (name_size<256 && dir_size<256) - { - if (! Write_bytes(GIF_file,"\x21\xFF\x0BGFX2PATH\x00\x00\x00", 14) - || ! Write_byte(GIF_file,dir_size) - || ! Write_bytes(GIF_file, context->Original_file_directory, dir_size) - || ! Write_byte(GIF_file,name_size) - || ! Write_bytes(GIF_file, context->Original_file_name, name_size) - || ! Write_byte(GIF_file,0)) - File_error=1; - } - } - - // On écrit un GIF TERMINATOR, exigé par SVGA et SEA. - if (! Write_byte(GIF_file,'\x3B')) - File_error=1; - } - - } // On a pu écrire la palette - else - File_error=1; - - } // On a pu écrire le LSDB - else - File_error=1; - - // Libération de la mémoire utilisée par les tables - free(alphabet_sister); - free(alphabet_daughter); - free(alphabet_suffix); - free(alphabet_prefix); - - } // On a pu écrire la signature du fichier - else - File_error=1; - - fclose(GIF_file); - if (File_error) - Remove_file(context); - - } // On a pu ouvrir le fichier en écriture - else - File_error=1; - -} - -/* @} */ - - //////////////////////////////////// PCX //////////////////////////////////// typedef struct { diff --git a/src/giformat.c b/src/giformat.c new file mode 100644 index 00000000..92bbacd1 --- /dev/null +++ b/src/giformat.c @@ -0,0 +1,1382 @@ +/* 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 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 giformats.c +/// Saving and loading GIF + +#include +#include +#include "struct.h" +#include "global.h" +#include "oldies.h" +#include "io.h" +#include "loadsave.h" +#include "loadsavefuncs.h" +#include "gfx2mem.h" +#include "gfx2log.h" + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +/** + * @defgroup GIF GIF format + * @ingroup loadsaveformats + * Graphics Interchange Format + * + * The GIF format uses LZW compression and stores indexed color pictures + * up to 256 colors. It has the ability to store several pictures in the same + * file : GrafX2 takes advantage of this feature for storing layered images + * and animations. + * + * GrafX2 implements GIF89a : + * https://www.w3.org/Graphics/GIF/spec-gif89a.txt + * + * @{ + */ + +/// Logical Screen Descriptor Block +typedef struct +{ + word Width; ///< Width of the complete image area + word Height; ///< Height of the complete image area + byte Resol; ///< Informations about the resolution (and other) + byte Backcol; ///< Proposed background color + byte Aspect; ///< Informations about aspect ratio. Ratio = (Aspect + 15) / 64 +} T_GIF_LSDB; + +/// Image Descriptor Block +typedef struct +{ + word Pos_X; ///< X offset where the image should be pasted + word Pos_Y; ///< Y offset where the image should be pasted + word Image_width; ///< Width of image + word Image_height; ///< Height of image + byte Indicator; ///< Misc image information + byte Nb_bits_pixel; ///< Nb de bits par pixel +} T_GIF_IDB; + +/// Graphic Control Extension +typedef struct +{ + byte Block_identifier; ///< 0x21 + byte Function; ///< 0xF9 + byte Block_size; ///< 4 + byte Packed_fields; ///< 11100000 : Reserved + ///< 00011100 : Disposal method + ///< 00000010 : User input flag + ///< 00000001 : Transparent flag + word Delay_time; ///< Time for this frame to stay displayed + byte Transparent_color; ///< Which color index acts as transparent + word Block_terminator; ///< 0x00 +} T_GIF_GCE; + +enum DISPOSAL_METHOD +{ + DISPOSAL_METHOD_UNDEFINED = 0, + DISPOSAL_METHOD_DO_NOT_DISPOSE = 1, + DISPOSAL_METHOD_RESTORE_BGCOLOR = 2, + DISPOSAL_METHOD_RESTORE_PREVIOUS = 3, +}; + + +/// Test if a file is GIF format +void Test_GIF(T_IO_Context * context, FILE * file) +{ + char signature[6]; + + (void)context; + File_error=1; + + if (Read_bytes(file,signature,6)) + { + /// checks if the signature (6 first bytes) is either GIF87a or GIF89a + if ((!memcmp(signature,"GIF87a",6))||(!memcmp(signature,"GIF89a",6))) + File_error=0; + } +} + + +// -- Lire un fichier au format GIF ----------------------------------------- + +typedef struct { + word nb_bits; ///< bits for a code + word remainder_bits; ///< available bits in @ref last_byte field + byte remainder_byte; ///< Remaining bytes in current block + word current_code; ///< current code (generally the one just read) + byte last_byte; ///< buffer byte for reading bits for codes + word pos_X; ///< Current coordinates + word pos_Y; + word interlaced; ///< interlaced flag + word pass; ///< current pass in interlaced decoding + word stop; ///< Stop flag (end of picture) +} T_GIF_context; + + +/// Reads the next code (GIF.nb_bits bits) +static word GIF_get_next_code(FILE * GIF_file, T_GIF_context * gif) +{ + word nb_bits_to_process = gif->nb_bits; + word nb_bits_processed = 0; + word current_nb_bits; + + gif->current_code = 0; + + while (nb_bits_to_process) + { + if (gif->remainder_bits == 0) // Il ne reste plus de bits... + { + // Lire l'octet suivant: + + // Si on a atteint la fin du bloc de Raster Data + if (gif->remainder_byte == 0) + { + // Lire l'octet nous donnant la taille du bloc de Raster Data suivant + if(Read_byte(GIF_file, &gif->remainder_byte)!=1) + { + File_error=2; + return 0; + } + if (gif->remainder_byte == 0) // still nothing ? That is the end data block + { + File_error = 2; + GFX2_Log(GFX2_WARNING, "GIF 0 sized data block\n"); + return gif->current_code; + } + } + if(Read_byte(GIF_file,&gif->last_byte)!=1) + { + File_error = 2; + GFX2_Log(GFX2_ERROR, "GIF failed to load data byte\n"); + return 0; + } + gif->remainder_byte--; + gif->remainder_bits=8; + } + + current_nb_bits=(nb_bits_to_process<=gif->remainder_bits)?nb_bits_to_process:gif->remainder_bits; + + gif->current_code |= (gif->last_byte & ((1<last_byte >>= current_nb_bits; + nb_bits_processed += current_nb_bits; + nb_bits_to_process -= current_nb_bits; + gif->remainder_bits -= current_nb_bits; + } + + return gif->current_code; +} + +/// Put a new pixel +static void GIF_new_pixel(T_IO_Context * context, T_GIF_context * gif, T_GIF_IDB *idb, int is_transparent, byte color) +{ + if (!is_transparent || color!=context->Transparent_color) + Set_pixel(context, idb->Pos_X+gif->pos_X, idb->Pos_Y+gif->pos_Y,color); + + gif->pos_X++; + + if (gif->pos_X >= idb->Image_width) + { + gif->pos_X=0; + + if (!gif->interlaced) + { + gif->pos_Y++; + if (gif->pos_Y >= idb->Image_height) + gif->stop = 1; + } + else + { + switch (gif->pass) + { + case 0 : + case 1 : gif->pos_Y+=8; + break; + case 2 : gif->pos_Y+=4; + break; + default: gif->pos_Y+=2; + } + + if (gif->pos_Y >= idb->Image_height) + { + switch(++(gif->pass)) + { + case 1 : gif->pos_Y=4; + break; + case 2 : gif->pos_Y=2; + break; + case 3 : gif->pos_Y=1; + break; + case 4 : gif->stop = 1; + } + } + } + } +} + + +/// Load GIF file +void Load_GIF(T_IO_Context * context) +{ + FILE *GIF_file; + int image_mode = -1; + char signature[6]; + + word * alphabet_stack; // Pile de décodage d'une chaîne + word * alphabet_prefix; // Table des préfixes des codes + word * alphabet_suffix; // Table des suffixes des codes + word alphabet_free; // Position libre dans l'alphabet + word alphabet_max; // Nombre d'entrées possibles dans l'alphabet + word alphabet_stack_pos; // Position dans la pile de décodage d'un chaîne + + T_GIF_context GIF; + T_GIF_LSDB LSDB; + T_GIF_IDB IDB; + T_GIF_GCE GCE; + + word nb_colors; // Nombre de couleurs dans l'image + word color_index; // index de traitement d'une couleur + byte size_to_read; // Nombre de données à lire (divers) + byte block_identifier; // Code indicateur du type de bloc en cours + byte initial_nb_bits; // Nb de bits au début du traitement LZW + word special_case=0; // Mémoire pour le cas spécial + word old_code=0; // Code précédent + word byte_read; // Sauvegarde du code en cours de lecture + word value_clr; // Valeur <=> Clear tables + word value_eof; // Valeur <=> End d'image + long file_size; + int number_LID; // Nombre d'images trouvées dans le fichier + int current_layer = 0; + int last_delay = 0; + byte is_transparent = 0; + byte is_looping=0; + enum PIXEL_RATIO ratio; + byte disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR; + + byte previous_disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR; + word previous_width=0; + word previous_height=0; + word previous_pos_x=0; + word previous_pos_y=0; + + /////////////////////////////////////////////////// FIN DES DECLARATIONS // + + + number_LID=0; + + if ((GIF_file=Open_file_read(context))) + { + file_size=File_length_file(GIF_file); + if ( (Read_bytes(GIF_file,signature,6)) && + ( (memcmp(signature,"GIF87a",6)==0) || + (memcmp(signature,"GIF89a",6)==0) ) ) + { + + // Allocation de mémoire pour les tables & piles de traitement: + alphabet_stack = (word *)GFX2_malloc(4096*sizeof(word)); + alphabet_prefix = (word *)GFX2_malloc(4096*sizeof(word)); + alphabet_suffix = (word *)GFX2_malloc(4096*sizeof(word)); + + if (Read_word_le(GIF_file,&(LSDB.Width)) + && Read_word_le(GIF_file,&(LSDB.Height)) + && Read_byte(GIF_file,&(LSDB.Resol)) + && Read_byte(GIF_file,&(LSDB.Backcol)) + && Read_byte(GIF_file,&(LSDB.Aspect)) + ) + { + // Lecture du Logical Screen Descriptor Block réussie: + + Original_screen_X=LSDB.Width; + Original_screen_Y=LSDB.Height; + + ratio=PIXEL_SIMPLE; // (49 + 15) / 64 = 1:1 + if (LSDB.Aspect != 0) { + if (LSDB.Aspect < 25) // (17 + 15) / 64 = 1:2 + ratio=PIXEL_TALL; + else if (LSDB.Aspect < 41) // (33 + 15) / 64 = 3:4 + ratio=PIXEL_TALL3; + else if (LSDB.Aspect > 82) // (113 + 15) / 64 = 2:1 + ratio=PIXEL_WIDE; + } + + Pre_load(context, LSDB.Width,LSDB.Height,file_size,FORMAT_GIF,ratio,(LSDB.Resol&7)+1); + + // Palette globale dispo = (LSDB.Resol and $80) + // Profondeur de couleur =((LSDB.Resol and $70) shr 4)+1 + // Nombre de bits/pixel = (LSDB.Resol and $07)+1 + // Ordre de Classement = (LSDB.Aspect and $80) + + nb_colors=(1 << ((LSDB.Resol & 0x07)+1)); + if (LSDB.Resol & 0x80) + { + // Palette globale dispo: + + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + + // Load the palette + for(color_index=0;color_indexPalette[color_index].R)); + Read_byte(GIF_file,&(context->Palette[color_index].G)); + Read_byte(GIF_file,&(context->Palette[color_index].B)); + } + } + + // On lit un indicateur de block + Read_byte(GIF_file,&block_identifier); + while (block_identifier!=0x3B && !File_error) + { + switch (block_identifier) + { + case 0x21: // Bloc d'extension + { + byte function_code; + // Lecture du code de fonction: + Read_byte(GIF_file,&function_code); + // Lecture de la taille du bloc: + Read_byte(GIF_file,&size_to_read); + while (size_to_read!=0 && !File_error) + { + switch(function_code) + { + case 0xFE: // Comment Block Extension + // On récupère le premier commentaire non-vide, + // on jette les autres. + if (context->Comment[0]=='\0') + { + int nb_char_to_keep = MIN(size_to_read, COMMENT_SIZE); + + Read_bytes(GIF_file,context->Comment,nb_char_to_keep); + context->Comment[nb_char_to_keep+1]='\0'; + // Si le commentaire etait trop long, on fait avance-rapide + // sur la suite. + if (size_to_read>nb_char_to_keep) + fseek(GIF_file,size_to_read-nb_char_to_keep,SEEK_CUR); + } + // Lecture de la taille du bloc suivant: + Read_byte(GIF_file,&size_to_read); + break; + case 0xF9: // Graphics Control Extension + // Prévu pour la transparence + if ( Read_byte(GIF_file,&(GCE.Packed_fields)) + && Read_word_le(GIF_file,&(GCE.Delay_time)) + && Read_byte(GIF_file,&(GCE.Transparent_color))) + { + previous_disposal_method = disposal_method; + disposal_method = (GCE.Packed_fields >> 2) & 7; + last_delay = GCE.Delay_time; + context->Transparent_color= GCE.Transparent_color; + is_transparent = GCE.Packed_fields & 1; + GFX2_Log(GFX2_DEBUG, "GIF Graphics Control Extension : transp=%d (color #%u) delay=%ums disposal_method=%d\n", is_transparent, GCE.Transparent_color, 10*GCE.Delay_time, disposal_method); + if (number_LID == 0) + context->Background_transparent = is_transparent; + is_transparent &= is_looping; + } + else + File_error=2; + // Lecture de la taille du bloc suivant: + Read_byte(GIF_file,&size_to_read); + break; + + case 0xFF: // Application Extension + // Normally, always a 11-byte block + if (size_to_read == 0x0B) + { + char aeb[0x0B]; + Read_bytes(GIF_file,aeb, 0x0B); + GFX2_Log(GFX2_DEBUG, "GIF extension \"%.11s\"\n", aeb); + if (File_error) + ; + else if (!memcmp(aeb,"NETSCAPE2.0",0x0B)) + { + is_looping=1; + // The well-known Netscape extension. + // Load as an animation + Set_image_mode(context, IMAGE_MODE_ANIMATION); + // Skip sub-block + do + { + if (! Read_byte(GIF_file,&size_to_read)) + File_error=1; + fseek(GIF_file,size_to_read,SEEK_CUR); + } while (!File_error && size_to_read!=0); + } + else if (!memcmp(aeb,"GFX2PATH\x00\x00\x00",0x0B)) + { + // Original file path + Read_byte(GIF_file,&size_to_read); + if (!File_error && size_to_read > 0) + { + free(context->Original_file_directory); + context->Original_file_directory = GFX2_malloc(size_to_read); + Read_bytes(GIF_file, context->Original_file_directory, size_to_read); + Read_byte(GIF_file, &size_to_read); + if (!File_error && size_to_read > 0) + { + free(context->Original_file_name); + context->Original_file_name = GFX2_malloc(size_to_read); + Read_bytes(GIF_file, context->Original_file_name, size_to_read); + Read_byte(GIF_file, &size_to_read); // Normally 0 + } + } + } + else if (!memcmp(aeb,"CRNG\0\0\0\0" "1.0",0x0B)) + { + // Color animation. Similar to a CRNG chunk in IFF file format. + word rate; + word flags; + byte col1; + byte col2; + // + Read_byte(GIF_file,&size_to_read); + for(;size_to_read>0 && !File_error;size_to_read-=6) + { + if ( (Read_word_be(GIF_file, &rate)) + && (Read_word_be(GIF_file, &flags)) + && (Read_byte(GIF_file, &col1)) + && (Read_byte(GIF_file, &col2))) + { + if (col1 != col2) + { + // Valid cycling range + context->Cycle_range[context->Color_cycles].Start = MIN(col1, col2); + context->Cycle_range[context->Color_cycles].End = MAX(col1, col2); + context->Cycle_range[context->Color_cycles].Inverse = (flags&2)?1:0; + context->Cycle_range[context->Color_cycles].Speed = (flags&1)?rate/78:0; + + context->Color_cycles++; + } + } + else + { + File_error=1; + } + } + // Read end-of-block delimiter + if (!File_error) + Read_byte(GIF_file,&size_to_read); + if (size_to_read!=0) + File_error=1; + } + else if (0 == memcmp(aeb, "GFX2MODE", 8)) + { + Read_byte(GIF_file,&size_to_read); + if (size_to_read > 0) + { // read the image mode. We'll set it after having loaded all layers. + char * label = GFX2_malloc((size_t)size_to_read + 1); + Read_bytes(GIF_file, label, size_to_read); + label[size_to_read] = '\0'; + image_mode = Constraint_mode_from_label(label); + GFX2_Log(GFX2_DEBUG, " mode = %s (%d)\n", label, image_mode); + free(label); + Read_byte(GIF_file,&size_to_read); + // be future proof, skip following sub-blocks : + while (size_to_read!=0 && !File_error) + { + if (fseek(GIF_file,size_to_read,SEEK_CUR) < 0) + File_error = 1; + if (!Read_byte(GIF_file,&size_to_read)) + File_error = 1; + } + } + } + else + { + // Unknown extension, skip. + Read_byte(GIF_file,&size_to_read); + while (size_to_read!=0 && !File_error) + { + if (fseek(GIF_file,size_to_read,SEEK_CUR) < 0) + File_error = 1; + if (!Read_byte(GIF_file,&size_to_read)) + File_error = 1; + } + } + } + else + { + fseek(GIF_file,size_to_read,SEEK_CUR); + // Lecture de la taille du bloc suivant: + Read_byte(GIF_file,&size_to_read); + } + break; + + default: + // On saute le bloc: + fseek(GIF_file,size_to_read,SEEK_CUR); + // Lecture de la taille du bloc suivant: + Read_byte(GIF_file,&size_to_read); + break; + } + } + } + break; + case 0x2C: // Local Image Descriptor + { + if (number_LID!=0) + { + // This a second layer/frame, or more. + // Attempt to add a layer to current image + current_layer++; + Set_loading_layer(context, current_layer); + if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) + { + // Copy the content of previous layer. + 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); + } + else + { + Fill_canvas(context, is_transparent ? context->Transparent_color : LSDB.Backcol); + } + } + else + { + // First frame/layer, fill canvas with backcolor + Fill_canvas(context, is_transparent ? context->Transparent_color : LSDB.Backcol); + } + // Duration was set in the previously loaded GCE + Set_frame_duration(context, last_delay*10); + number_LID++; + + // lecture de 10 derniers octets + if ( Read_word_le(GIF_file,&(IDB.Pos_X)) + && Read_word_le(GIF_file,&(IDB.Pos_Y)) + && Read_word_le(GIF_file,&(IDB.Image_width)) + && Read_word_le(GIF_file,&(IDB.Image_height)) + && Read_byte(GIF_file,&(IDB.Indicator)) + && IDB.Image_width && IDB.Image_height) + { + GFX2_Log(GFX2_DEBUG, "GIF Image descriptor %u Pos (%u,%u) %ux%u %s%slocal palette(%ubpp)\n", + number_LID, IDB.Pos_X, IDB.Pos_Y, IDB.Image_width, IDB.Image_height, + (IDB.Indicator & 0x40) ? "interlaced " : "", (IDB.Indicator & 0x80) ? "" : "no ", + (IDB.Indicator & 7) + 1); + // Palette locale dispo = (IDB.Indicator and $80) + // Image entrelacée = (IDB.Indicator and $40) + // Ordre de classement = (IDB.Indicator and $20) + // Nombre de bits/pixel = (IDB.Indicator and $07)+1 (si palette locale dispo) + + if (IDB.Indicator & 0x80) + { + // Palette locale dispo + + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + + nb_colors=(1 << ((IDB.Indicator & 0x07)+1)); + // Load the palette + for(color_index=0;color_indexPalette[color_index].R)); + Read_byte(GIF_file,&(context->Palette[color_index].G)); + Read_byte(GIF_file,&(context->Palette[color_index].B)); + } + + } + if (number_LID!=1) + { + // This a second layer/frame, or more. + if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) + { + // Need to clear previous image to back-color. + if (previous_disposal_method==DISPOSAL_METHOD_RESTORE_BGCOLOR) + { + int y; + for (y=0; yPages->Image[Main.current_layer].Pixels + + (previous_pos_y+y)* Main.backups->Pages->Width+previous_pos_x, + is_transparent ? context->Transparent_color : LSDB.Backcol, + previous_width); + } + } + } + previous_height=IDB.Image_height; + previous_width=IDB.Image_width; + previous_pos_x=IDB.Pos_X; + previous_pos_y=IDB.Pos_Y; + + File_error=0; + if (!Read_byte(GIF_file,&(initial_nb_bits))) + File_error=1; + + value_clr =(1< alphabet_free) + { + GFX2_Log(GFX2_INFO, "Load_GIF() Invalid code %u (should be <=%u)\n", GIF.current_code, alphabet_free); + File_error=2; + break; + } + else if (GIF.current_code != value_clr) + { + byte_read = GIF.current_code; + if (alphabet_free == GIF.current_code) + { + GIF.current_code=old_code; + alphabet_stack[alphabet_stack_pos++]=special_case; + } + + while (GIF.current_code > value_clr) + { + if (GIF.current_code >= 4096) + { + GFX2_Log(GFX2_ERROR, "Load_GIF() GIF.current_code = %u >= 4096\n", GIF.current_code); + File_error = 2; + break; + } + alphabet_stack[alphabet_stack_pos++] = alphabet_suffix[GIF.current_code]; + GIF.current_code = alphabet_prefix[GIF.current_code]; + } + + special_case = alphabet_stack[alphabet_stack_pos++] = GIF.current_code; + + do + GIF_new_pixel(context, &GIF, &IDB, is_transparent, alphabet_stack[--alphabet_stack_pos]); + while (alphabet_stack_pos!=0); + + alphabet_prefix[alphabet_free ]=old_code; + alphabet_suffix[alphabet_free++]=GIF.current_code; + old_code=byte_read; + + if (alphabet_free>alphabet_max) + { + if (GIF.nb_bits<12) + alphabet_max =((1 << (++GIF.nb_bits))-1); + } + } + else // Clear code + { + GIF.nb_bits = initial_nb_bits + 1; + alphabet_max = ((1 << GIF.nb_bits)-1); + alphabet_free = (1<= value_clr) + { + GFX2_Log(GFX2_INFO, "Load_GIF() Invalid code %u just after clear (=%u)!\n", + GIF.current_code, value_clr); + File_error = 2; + break; + } + old_code = GIF.current_code; + GIF_new_pixel(context, &GIF, &IDB, is_transparent, GIF.current_code); + } + } + + if (File_error == 2 && GIF.pos_X == 0 && GIF.pos_Y == IDB.Image_height) + File_error=0; + + if (File_error >= 0 && !GIF.stop) + File_error=2; + + // No need to read more than one frame in animation preview mode + if (context->Type == CONTEXT_PREVIEW && is_looping) + { + goto early_exit; + } + // Same with brush + if (context->Type == CONTEXT_BRUSH && is_looping) + { + goto early_exit; + } + + } // Le fichier contenait un IDB + else + File_error=2; + } + default: + break; + } + // Lecture du code de fonction suivant: + if (!Read_byte(GIF_file,&block_identifier)) + File_error=2; + } + + // set the mode that have been read previously. + if (image_mode > 0) + Set_image_mode(context, image_mode); + } // Le fichier contenait un LSDB + else + File_error=1; + + early_exit: + + // Libération de la mémoire utilisée par les tables & piles de traitement: + free(alphabet_suffix); + free(alphabet_prefix); + free(alphabet_stack); + alphabet_suffix = alphabet_prefix = alphabet_stack = NULL; + } // Le fichier contenait au moins la signature GIF87a ou GIF89a + else + File_error=1; + + fclose(GIF_file); + + } // Le fichier était ouvrable + else + File_error=1; +} + + +// -- Sauver un fichier au format GIF --------------------------------------- + +/// Flush the buffer +static void GIF_empty_buffer(FILE * file, T_GIF_context *gif, byte * GIF_buffer) +{ + word index; + + if (gif->remainder_byte) + { + GIF_buffer[0] = gif->remainder_byte; + + for (index = 0; index <= gif->remainder_byte; index++) + Write_one_byte(file, GIF_buffer[index]); + + gif->remainder_byte=0; + } +} + +/// Write a code (GIF_nb_bits bits) +static void GIF_set_code(FILE * GIF_file, T_GIF_context * gif, byte * GIF_buffer, word Code) +{ + word nb_bits_to_process = gif->nb_bits; + word nb_bits_processed =0; + word current_nb_bits; + + while (nb_bits_to_process) + { + current_nb_bits = (nb_bits_to_process <= (8-gif->remainder_bits)) ? + nb_bits_to_process: (8-gif->remainder_bits); + + gif->last_byte |= (Code & ((1<remainder_bits; + Code>>=current_nb_bits; + gif->remainder_bits +=current_nb_bits; + nb_bits_processed +=current_nb_bits; + nb_bits_to_process-=current_nb_bits; + + if (gif->remainder_bits==8) // Il ne reste plus de bits à coder sur l'octet courant + { + // Ecrire l'octet à balancer: + GIF_buffer[++(gif->remainder_byte)] = gif->last_byte; + + // Si on a atteint la fin du bloc de Raster Data + if (gif->remainder_byte==255) + // On doit vider le buffer qui est maintenant plein + GIF_empty_buffer(GIF_file, gif, GIF_buffer); + + gif->last_byte=0; + gif->remainder_bits=0; + } + } +} + + +/// Read the next pixel +static byte GIF_next_pixel(T_IO_Context *context, T_GIF_context *gif, T_GIF_IDB *idb) +{ + byte temp; + + temp = Get_pixel(context, gif->pos_X, gif->pos_Y); + + if (++gif->pos_X >= (idb->Image_width + idb->Pos_X)) + { + gif->pos_X = idb->Pos_X; + if (++gif->pos_Y >= (idb->Image_height + idb->Pos_Y)) + gif->stop = 1; + } + + return temp; +} + + +/// Save a GIF file +void Save_GIF(T_IO_Context * context) +{ + FILE * GIF_file; + byte GIF_buffer[256]; // buffer d'écriture de bloc de données compilées + + word * alphabet_prefix; // Table des préfixes des codes + word * alphabet_suffix; // Table des suffixes des codes + word * alphabet_daughter; // Table des chaînes filles (plus longues) + word * alphabet_sister; // Table des chaînes soeurs (même longueur) + word alphabet_free; // Position libre dans l'alphabet + word alphabet_max; // Nombre d'entrées possibles dans l'alphabet + word start; // Code précédent (sert au linkage des chaînes) + int descend; // Booléen "On vient de descendre" + + T_GIF_context GIF; + T_GIF_LSDB LSDB; + T_GIF_IDB IDB; + + + byte block_identifier; // Code indicateur du type de bloc en cours + word current_string; // Code de la chaîne en cours de traitement + byte current_char; // Caractère à coder + word index; // index de recherche de chaîne + int current_layer; + + word clear; // LZW clear code + word eof; // End of image code + + /////////////////////////////////////////////////// FIN DES DECLARATIONS // + + File_error=0; + + if ((GIF_file=Open_file_write(context))) + { + setvbuf(GIF_file, NULL, _IOFBF, 64*1024); + + // On écrit la signature du fichier + if (Write_bytes(GIF_file,"GIF89a",6)) + { + // La signature du fichier a été correctement écrite. + + // Allocation de mémoire pour les tables + alphabet_prefix = (word *)GFX2_malloc(4096*sizeof(word)); + alphabet_suffix = (word *)GFX2_malloc(4096*sizeof(word)); + alphabet_daughter = (word *)GFX2_malloc(4096*sizeof(word)); + alphabet_sister = (word *)GFX2_malloc(4096*sizeof(word)); + + // On initialise le LSDB du fichier + if (Config.Screen_size_in_GIF) + { + LSDB.Width=Screen_width; + LSDB.Height=Screen_height; + } + else + { + LSDB.Width=context->Width; + LSDB.Height=context->Height; + } + LSDB.Resol = 0xF7; // Global palette of 256 entries, 256 color image + // 0xF7 = 1111 0111 + // = Global Color Table Flag 1 Bit + // Color Resolution 3 Bits + // Sort Flag 1 Bit + // Size of Global Color Table 3 Bits + LSDB.Backcol=context->Transparent_color; + switch(context->Ratio) + { + case PIXEL_TALL: + case PIXEL_TALL2: + LSDB.Aspect = 17; // 1:2 = 2:4 + break; + case PIXEL_TALL3: + LSDB.Aspect = 33; // 3:4 + break; + case PIXEL_WIDE: + case PIXEL_WIDE2: + LSDB.Aspect = 113; // 2:1 = 4:2 + break; + default: + LSDB.Aspect = 0; // undefined, which is most frequent. + // 49 would be 1:1 ratio + } + + // On sauve le LSDB dans le fichier + + if (Write_word_le(GIF_file,LSDB.Width) && + Write_word_le(GIF_file,LSDB.Height) && + Write_byte(GIF_file,LSDB.Resol) && + Write_byte(GIF_file,LSDB.Backcol) && + Write_byte(GIF_file,LSDB.Aspect) ) + { + // Le LSDB a été correctement écrit. + int i; + // On sauve la palette + for(i=0;i<256 && !File_error;i++) + { + if (!Write_byte(GIF_file,context->Palette[i].R) + ||!Write_byte(GIF_file,context->Palette[i].G) + ||!Write_byte(GIF_file,context->Palette[i].B)) + File_error=1; + } + if (!File_error) + { + // La palette a été correctement écrite. + + /// - "Netscape" animation extension : + ///
+          ///   0x21       Extension Label
+          ///   0xFF       Application Extension Label
+          ///   0x0B       Block Size
+          ///   "NETSCAPE" Application Identifier (8 bytes)
+          ///   "2.0"      Application Authentication Code (3 bytes)
+          ///   0x03       Sub-block Data Size
+          ///   0xLL       01 to loop
+          ///   0xSSSS     (little endian) number of loops, 0 means infinite loop
+          ///   0x00 Block terminator 
+ /// see http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension + if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) + { + if (context->Nb_layers>1) + Write_bytes(GIF_file,"\x21\xFF\x0BNETSCAPE2.0\x03\x01\x00\x00\x00",19); + } + else if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode > IMAGE_MODE_ANIMATION) + { + /// - GrafX2 extension to store ::IMAGE_MODES : + ///
+            ///   0x21       Extension Label
+            ///   0xFF       Application Extension Label
+            ///   0x0B       Block Size
+            ///   "GFX2MODE" Application Identifier (8 bytes)
+            ///   "2.6"      Application Authentication Code (3 bytes)
+            ///   0xll       Sub-block Data Size
+            ///   string     label
+            ///   0x00 Block terminator 
+ /// @see Constraint_mode_label() + const char * label = Constraint_mode_label(Main.backups->Pages->Image_mode); + if (label != NULL) + { + size_t len = strlen(label); + // Write extension for storing IMAGE_MODE + Write_byte(GIF_file,0x21); // Extension Introducer + Write_byte(GIF_file,0xff); // Extension Label + Write_byte(GIF_file, 11); // Block size + Write_bytes(GIF_file, "GFX2MODE2.6", 11); // Application Identifier + Appl. Authentication Code + Write_byte(GIF_file, (byte)len); // Block size + Write_bytes(GIF_file, label, len); // Data + Write_byte(GIF_file, 0); // Block terminator + } + } + + // Ecriture du commentaire + if (context->Comment[0]) + { + Write_bytes(GIF_file,"\x21\xFE",2); + Write_byte(GIF_file, (byte)strlen(context->Comment)); + Write_bytes(GIF_file,context->Comment,strlen(context->Comment)+1); + } + /// - "CRNG" Color cycing extension : + ///
+          ///   0x21       Extension Label
+          ///   0xFF       Application Extension Label
+          ///   0x0B       Block Size
+          ///   "CRNG\0\0\0\0" "CRNG" Application Identifier (8 bytes)
+          ///   "1.0"      Application Authentication Code (3 bytes)
+          ///   0xll       Sub-block Data Size (6 bytes per color cycle)
+          ///   For each color cycle :
+          ///     0xRRRR   (big endian) Rate
+          ///     0xFFFF   (big endian) Flags
+          ///     0xSS     start (lower color index)
+          ///     0xEE     end (higher color index)
+          ///   0x00       Block terminator 
+ if (context->Color_cycles) + { + int i; + + Write_bytes(GIF_file,"\x21\xff\x0B" "CRNG\0\0\0\0" "1.0",14); + Write_byte(GIF_file,context->Color_cycles*6); + for (i=0; iColor_cycles; i++) + { + word flags=0; + flags|= context->Cycle_range[i].Speed?1:0; // Cycling or not + flags|= context->Cycle_range[i].Inverse?2:0; // Inverted + + Write_word_be(GIF_file,context->Cycle_range[i].Speed*78); // Rate + Write_word_be(GIF_file,flags); // Flags + Write_byte(GIF_file,context->Cycle_range[i].Start); // Min color + Write_byte(GIF_file,context->Cycle_range[i].End); // Max color + } + Write_byte(GIF_file,0); + } + + // Loop on all layers + for (current_layer=0; + current_layer < context->Nb_layers && !File_error; + current_layer++) + { + // Write a Graphic Control Extension + T_GIF_GCE GCE; + byte disposal_method; + + Set_saving_layer(context, current_layer); + + GCE.Block_identifier = 0x21; + GCE.Function = 0xF9; + GCE.Block_size=4; + + if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) + { + // Animation frame + int duration; + if(context->Background_transparent) + disposal_method = DISPOSAL_METHOD_RESTORE_BGCOLOR; + else + disposal_method = DISPOSAL_METHOD_DO_NOT_DISPOSE; + GCE.Packed_fields=(disposal_method<<2)|(context->Background_transparent); + duration=Get_frame_duration(context)/10; + GCE.Delay_time=duration<0xFFFF?duration:0xFFFF; + } + else + { + // Layered image or brush + disposal_method = DISPOSAL_METHOD_DO_NOT_DISPOSE; + if (current_layer==0) + GCE.Packed_fields=(disposal_method<<2)|(context->Background_transparent); + else + GCE.Packed_fields=(disposal_method<<2)|(1); + GCE.Delay_time=5; // Duration 5/100s (minimum viable value for current web browsers) + if (current_layer == context->Nb_layers -1) + GCE.Delay_time=0xFFFF; // Infinity (10 minutes) + } + GCE.Transparent_color=context->Transparent_color; + GCE.Block_terminator=0x00; + + if (Write_byte(GIF_file,GCE.Block_identifier) + && Write_byte(GIF_file,GCE.Function) + && Write_byte(GIF_file,GCE.Block_size) + && Write_byte(GIF_file,GCE.Packed_fields) + && Write_word_le(GIF_file,GCE.Delay_time) + && Write_byte(GIF_file,GCE.Transparent_color) + && Write_byte(GIF_file,GCE.Block_terminator) + ) + { + byte temp, max = 0; + + IDB.Pos_X=0; + IDB.Pos_Y=0; + IDB.Image_width=context->Width; + IDB.Image_height=context->Height; + if(current_layer > 0) + { + word min_X, max_X, min_Y, max_Y; + // find bounding box of changes for Animated GIFs + min_X = min_Y = 0xffff; + max_X = max_Y = 0; + for(GIF.pos_Y = 0; GIF.pos_Y < context->Height; GIF.pos_Y++) { + for(GIF.pos_X = 0; GIF.pos_X < context->Width; GIF.pos_X++) { + if (GIF.pos_X >= min_X && GIF.pos_X <= max_X && GIF.pos_Y >= min_Y && GIF.pos_Y <= max_Y) + continue; // already in the box + if(disposal_method == DISPOSAL_METHOD_DO_NOT_DISPOSE) + { + // if that pixel has same value in previous layer, no need to save it + Set_saving_layer(context, current_layer - 1); + temp = Get_pixel(context, GIF.pos_X, GIF.pos_Y); + Set_saving_layer(context, current_layer); + if(temp == Get_pixel(context, GIF.pos_X, GIF.pos_Y)) + continue; + } + if (disposal_method == DISPOSAL_METHOD_RESTORE_BGCOLOR + || context->Background_transparent + || Main.backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) + { + // if that pixel is Backcol, no need to save it + if (LSDB.Backcol == Get_pixel(context, GIF.pos_X, GIF.pos_Y)) + continue; + } + if(GIF.pos_X < min_X) min_X = GIF.pos_X; + if(GIF.pos_X > max_X) max_X = GIF.pos_X; + if(GIF.pos_Y < min_Y) min_Y = GIF.pos_Y; + if(GIF.pos_Y > max_Y) max_Y = GIF.pos_Y; + } + } + if((min_X <= max_X) && (min_Y <= max_Y)) + { + IDB.Pos_X = min_X; + IDB.Pos_Y = min_Y; + IDB.Image_width = max_X + 1 - min_X; + IDB.Image_height = max_Y + 1 - min_Y; + } + else + { + // if no pixel changes, store a 1 pixel image + IDB.Image_width = 1; + IDB.Image_height = 1; + } + } + + // look for the maximum pixel value + // to decide how many bit per pixel are needed. + for(GIF.pos_Y = IDB.Pos_Y; GIF.pos_Y < IDB.Image_height + IDB.Pos_Y; GIF.pos_Y++) { + for(GIF.pos_X = IDB.Pos_X; GIF.pos_X < IDB.Image_width + IDB.Pos_X; GIF.pos_X++) { + temp=Get_pixel(context, GIF.pos_X, GIF.pos_Y); + if(temp > max) max = temp; + } + } + IDB.Nb_bits_pixel=2; // Find the minimum bpp value to fit all pixels + while((int)max >= (1 << IDB.Nb_bits_pixel)) { + IDB.Nb_bits_pixel++; + } + GFX2_Log(GFX2_DEBUG, "GIF image #%d %ubits (%u,%u) %ux%u\n", + current_layer, IDB.Nb_bits_pixel, IDB.Pos_X, IDB.Pos_Y, + IDB.Image_width, IDB.Image_height); + + // On va écrire un block indicateur d'IDB et l'IDB du fichier + block_identifier=0x2C; + IDB.Indicator=0x07; // Image non entrelacée, pas de palette locale. + clear = 1 << IDB.Nb_bits_pixel; // Clear Code + eof = clear + 1; // End of Picture Code + + if ( Write_byte(GIF_file,block_identifier) && + Write_word_le(GIF_file,IDB.Pos_X) && + Write_word_le(GIF_file,IDB.Pos_Y) && + Write_word_le(GIF_file,IDB.Image_width) && + Write_word_le(GIF_file,IDB.Image_height) && + Write_byte(GIF_file,IDB.Indicator) && + Write_byte(GIF_file,IDB.Nb_bits_pixel)) + { + // Le block indicateur d'IDB et l'IDB ont étés correctements + // écrits. + + GIF.pos_X=IDB.Pos_X; + GIF.pos_Y=IDB.Pos_Y; + GIF.last_byte=0; + GIF.remainder_bits=0; + GIF.remainder_byte=0; + +#define GIF_INVALID_CODE (65535) + index=GIF_INVALID_CODE; + File_error=0; + GIF.stop=0; + + // Réintialisation de la table: + alphabet_free=clear + 2; // 258 for 8bpp + GIF.nb_bits =IDB.Nb_bits_pixel + 1; // 9 for 8 bpp + alphabet_max =clear+clear-1; // 511 for 8bpp + GIF_set_code(GIF_file, &GIF, GIF_buffer, clear); //256 for 8bpp + for (start=0;start<4096;start++) + { + alphabet_daughter[start] = GIF_INVALID_CODE; + alphabet_sister[start] = GIF_INVALID_CODE; + } + + ////////////////////////////////////////////// COMPRESSION LZW // + + start=current_string=GIF_next_pixel(context, &GIF, &IDB); + descend=1; + + while ((!GIF.stop) && (!File_error)) + { + current_char=GIF_next_pixel(context, &GIF, &IDB); + + // look for (current_string,current_char) in the alphabet + while ( (index != GIF_INVALID_CODE) && + ( (current_string!=alphabet_prefix[index]) || + (current_char !=alphabet_suffix[index]) ) ) + { + descend=0; + start=index; + index=alphabet_sister[index]; + } + + if (index != GIF_INVALID_CODE) + { + // (current_string,current_char) == (alphabet_prefix,alphabet_suffix)[index] + // We have found (current_string,current_char) in the alphabet + // at the index position. So go on and prepare for then next character + + descend=1; + start=current_string=index; + index=alphabet_daughter[index]; + } + else + { + // (current_string,current_char) was not found in the alphabet + // so write current_string to the Gif stream + GIF_set_code(GIF_file, &GIF, GIF_buffer, current_string); + + if(alphabet_free < 4096) { + // link current_string and the new one + if (descend) + alphabet_daughter[start]=alphabet_free; + else + alphabet_sister[start]=alphabet_free; + + // add (current_string,current_char) to the alphabet + alphabet_prefix[alphabet_free]=current_string; + alphabet_suffix[alphabet_free]=current_char; + alphabet_free++; + } + + if (alphabet_free >= 4096) + { + // clear alphabet + GIF_set_code(GIF_file, &GIF, GIF_buffer, clear); // 256 for 8bpp + alphabet_free=clear+2; // 258 for 8bpp + GIF.nb_bits =IDB.Nb_bits_pixel + 1; // 9 for 8bpp + alphabet_max =clear+clear-1; // 511 for 8bpp + for (start=0;start<4096;start++) + { + alphabet_daughter[start] = GIF_INVALID_CODE; + alphabet_sister[start] = GIF_INVALID_CODE; + } + } + else if (alphabet_free>alphabet_max+1) + { + // On augmente le nb de bits + + GIF.nb_bits++; + alphabet_max = (1< alphabet_max+1) && (GIF.nb_bits < 12)) + { + GIF.nb_bits++; + alphabet_max = (1 << GIF.nb_bits) - 1; + } + } + + GIF_set_code(GIF_file, &GIF, GIF_buffer, eof); // 257 for 8bpp // Code de End d'image + if (GIF.remainder_bits!=0) + { + // Write last byte (this is an incomplete byte) + GIF_buffer[++GIF.remainder_byte]=GIF.last_byte; + GIF.last_byte=0; + GIF.remainder_bits=0; + } + GIF_empty_buffer(GIF_file, &GIF, GIF_buffer); // On envoie les dernières données du buffer GIF dans le buffer KM + + // On écrit un \0 + if (! Write_byte(GIF_file,'\x00')) + File_error=1; + } + + } // On a pu écrire l'IDB + else + File_error=1; + } + else + File_error=1; + } + + // After writing all layers + if (!File_error) + { + /// - If requested, write a specific extension for storing + /// original file path. + /// This is used by the backup system. + /// The format is : + ///
+            ///   0x21       Extension Label
+            ///   0xFF       Application Extension Label
+            ///   0x0B       Block Size
+            ///   "GFX2PATH" "GFX2PATH" Application Identifier (8 bytes)
+            ///   "\0\0\0"   Application Authentication Code (3 bytes)
+            ///   0xll       Sub-block Data Size : path size (including null)
+            ///   "..path.." path (null-terminated)
+            ///   0xll       Sub-block Data Size : filename size (including null)
+            ///   "..file.." file name (null-terminated)
+            ///   0x00       Block terminator 
+ if (context->Original_file_name != NULL + && context->Original_file_directory != NULL) + { + long name_size = 1+strlen(context->Original_file_name); + long dir_size = 1+strlen(context->Original_file_directory); + if (name_size<256 && dir_size<256) + { + if (! Write_bytes(GIF_file,"\x21\xFF\x0BGFX2PATH\x00\x00\x00", 14) + || ! Write_byte(GIF_file,dir_size) + || ! Write_bytes(GIF_file, context->Original_file_directory, dir_size) + || ! Write_byte(GIF_file,name_size) + || ! Write_bytes(GIF_file, context->Original_file_name, name_size) + || ! Write_byte(GIF_file,0)) + File_error=1; + } + } + + // On écrit un GIF TERMINATOR, exigé par SVGA et SEA. + if (! Write_byte(GIF_file,'\x3B')) + File_error=1; + } + + } // On a pu écrire la palette + else + File_error=1; + + } // On a pu écrire le LSDB + else + File_error=1; + + // Libération de la mémoire utilisée par les tables + free(alphabet_sister); + free(alphabet_daughter); + free(alphabet_suffix); + free(alphabet_prefix); + + } // On a pu écrire la signature du fichier + else + File_error=1; + + fclose(GIF_file); + if (File_error) + Remove_file(context); + + } // On a pu ouvrir le fichier en écriture + else + File_error=1; + +} + +/* @} */