= 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<daughter[current_char];
start = current_string = current_char;
descend = 1;
}
}
if (!File_error)
{
// Write the last code (before EOF)
GIF_set_code(GIF_file, &GIF, GIF_buffer, current_string);
// we need to update alphabet->free / GIF.nb_bits here because
// the decoder will update them after each code,
// so in very rare cases there might be a problem if we
// don't do it.
// see http://pulkomandy.tk/projects/GrafX2/ticket/125
if(alphabet->free < 4096)
{
alphabet->free++;
if ((alphabet->free > 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);
} // 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;
}
/** @} */