= 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,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<
/// 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
{
byte Manufacturer; // |_ Il font chier ces cons! Ils auraient pu
byte Version; // | mettre une vraie signature!
byte Compression; // L'image est-elle compressée?
byte Depth; // Nombre de bits pour coder un pixel (inutile puisqu'on se sert de Plane)
word X_min; // |_ Coin haut-gauche |
word Y_min; // | de l'image |_ (Crétin!)
word X_max; // |_ Coin bas-droit |
word Y_max; // | de l'image |
word X_dpi; // |_ Densité de |_ (Presque inutile parce que
word Y_dpi; // | l'image | aucun moniteur n'est pareil!)
byte Palette_16c[48]; // Palette 16 coul (inutile pour 256c) (débile!)
byte Reserved; // Ca me plait ça aussi!
byte Plane; // 4 => 16c , 1 => 256c , ...
word Bytes_per_plane_line;// Doit toujours être pair
word Palette_info; // 1 => color , 2 => Gris (ignoré à partir de la version 4)
word Screen_X; // |_ Dimensions de
word Screen_Y; // | l'écran d'origine
byte Filler[54]; // Ca... J'adore!
} T_PCX_Header;
T_PCX_Header PCX_header;
// -- Tester si un fichier est au format PCX --------------------------------
void Test_PCX(T_IO_Context * context, FILE * file)
{
(void)context;
File_error=0;
if (Read_byte(file,&(PCX_header.Manufacturer)) &&
Read_byte(file,&(PCX_header.Version)) &&
Read_byte(file,&(PCX_header.Compression)) &&
Read_byte(file,&(PCX_header.Depth)) &&
Read_word_le(file,&(PCX_header.X_min)) &&
Read_word_le(file,&(PCX_header.Y_min)) &&
Read_word_le(file,&(PCX_header.X_max)) &&
Read_word_le(file,&(PCX_header.Y_max)) &&
Read_word_le(file,&(PCX_header.X_dpi)) &&
Read_word_le(file,&(PCX_header.Y_dpi)) &&
Read_bytes(file,&(PCX_header.Palette_16c),48) &&
Read_byte(file,&(PCX_header.Reserved)) &&
Read_byte(file,&(PCX_header.Plane)) &&
Read_word_le(file,&(PCX_header.Bytes_per_plane_line)) &&
Read_word_le(file,&(PCX_header.Palette_info)) &&
Read_word_le(file,&(PCX_header.Screen_X)) &&
Read_word_le(file,&(PCX_header.Screen_Y)) &&
Read_bytes(file,&(PCX_header.Filler),54) )
{
// Vu que ce header a une signature de merde et peu significative, il
// va falloir que je teste différentes petites valeurs dont je connais
// l'intervalle. Grrr!
if ( (PCX_header.Manufacturer!=10)
|| (PCX_header.Compression>1)
|| ( (PCX_header.Depth!=1) && (PCX_header.Depth!=2) && (PCX_header.Depth!=4) && (PCX_header.Depth!=8) )
|| ( (PCX_header.Plane!=1) && (PCX_header.Plane!=2) && (PCX_header.Plane!=4) && (PCX_header.Plane!=8) && (PCX_header.Plane!=3) )
|| (PCX_header.X_maxWidth; x_pos++)
{
color=(buffer[x_pos/reduction]>>((reduction_minus_one-(x_pos%reduction))*depth)) & byte_mask;
Set_pixel(context, x_pos,y_pos,color);
}
}
// generate CGA RGBI colors.
static void Set_CGA_Color(int i, T_Components * comp)
{
int intensity = (i & 8) ? 85 : 0;
comp->R = ((i & 4) ? 170 : 0) + intensity;
if (i == 6)
comp->G = 85; // color 6 is brown instead of yellow on IBM CGA display
else
comp->G = ((i & 2) ? 170 : 0) + intensity;
comp->B = ((i & 1) ? 170 : 0) + intensity;
}
void Load_PCX(T_IO_Context * context)
{
FILE *file;
short line_size;
short real_line_size; // width de l'image corrigée
short width_read;
short x_pos;
short y_pos;
byte byte1;
byte byte2;
byte index;
dword nb_colors;
long file_size;
long position;
long image_size;
byte * buffer;
File_error=0;
if ((file=Open_file_read(context)))
{
file_size=File_length_file(file);
if (Read_byte(file,&(PCX_header.Manufacturer)) &&
Read_byte(file,&(PCX_header.Version)) &&
Read_byte(file,&(PCX_header.Compression)) &&
Read_byte(file,&(PCX_header.Depth)) &&
Read_word_le(file,&(PCX_header.X_min)) &&
Read_word_le(file,&(PCX_header.Y_min)) &&
Read_word_le(file,&(PCX_header.X_max)) &&
Read_word_le(file,&(PCX_header.Y_max)) &&
Read_word_le(file,&(PCX_header.X_dpi)) &&
Read_word_le(file,&(PCX_header.Y_dpi)) &&
Read_bytes(file,&(PCX_header.Palette_16c),48) &&
Read_byte(file,&(PCX_header.Reserved)) &&
Read_byte(file,&(PCX_header.Plane)) &&
Read_word_le(file,&(PCX_header.Bytes_per_plane_line)) &&
Read_word_le(file,&(PCX_header.Palette_info)) &&
Read_word_le(file,&(PCX_header.Screen_X)) &&
Read_word_le(file,&(PCX_header.Screen_Y)) &&
Read_bytes(file,&(PCX_header.Filler),54) )
{
context->Width=PCX_header.X_max-PCX_header.X_min+1;
context->Height=PCX_header.Y_max-PCX_header.Y_min+1;
Original_screen_X=PCX_header.Screen_X;
Original_screen_Y=PCX_header.Screen_Y;
Pre_load(context, context->Width, context->Height, file_size, FORMAT_PCX, PIXEL_SIMPLE, PCX_header.Plane * PCX_header.Depth);
if (!(PCX_header.Plane==3 && PCX_header.Depth==8))
{
if (File_error==0)
{
// On prépare la palette à accueillir les valeurs du fichier PCX
if (Config.Clear_palette)
memset(context->Palette,0,sizeof(T_Palette));
nb_colors=(dword)(1<Palette,PCX_header.Palette_16c,48);
if (nb_colors<=4)
{
// CGA !
int i;
if (PCX_header.Version < 5 // Detect if the palette is usable
|| (nb_colors == 4
&& (PCX_header.Palette_16c[6]&15) == 0
&& (PCX_header.Palette_16c[7]&15) == 0
&& (PCX_header.Palette_16c[8]&15) == 0
&& (PCX_header.Palette_16c[9]&15) == 0
&& (PCX_header.Palette_16c[10]&15) == 0
&& (PCX_header.Palette_16c[11]&15) == 0)
|| (nb_colors == 2
&& PCX_header.Palette_16c[1] == 0
&& PCX_header.Palette_16c[2] == 0))
{
// special CGA palette meaning :
if (nb_colors == 2)
{
// Background : BLACK
context->Palette[0].R=0;
context->Palette[0].G=0;
context->Palette[0].B=0;
// Foreground : 4 MSB of palette[0] is index of the CGA color to use.
i = (PCX_header.Palette_16c[0] >> 4);
if (i==0) i = 15; // Bright White by default
Set_CGA_Color(i, &context->Palette[1]);
}
else
{
// Color CGA
// background color : 4 MSB of palette[0]
Set_CGA_Color((PCX_header.Palette_16c[0] >> 4), &context->Palette[0]);
// Palette_16c[3] : 8 bits CPIx xxxx
// C bit : Color burst enabled => disable it to set 3rd palette
// P bit : palette : 0 = yellow/ 1 = white
// I bit : intensity
// CGA Palette 0 : 2 green, 4 red, 6 brown
// CGA Palette 1 : 3 cyan, 5 magenta, 7 white
// CGA 3rd palette : 3 cyan, 4 red, 7 white
// After some tests in PC Paintbrush 3.11, it looks like
// the Color burst bit is not taken into acount.
i = 2; // 2 - CGA Green
if (PCX_header.Palette_16c[3] & 0x40)
i++; // Palette 1 (3 = cyan)
if (PCX_header.Palette_16c[3] & 0x20)
i += 8; // High intensity
Set_CGA_Color(i++, &context->Palette[1]);
i++; // Skip 1 color
Set_CGA_Color(i++, &context->Palette[2]);
i++; // Skip 1 color
Set_CGA_Color(i, &context->Palette[3]);
}
}
}
// On se positionne à la fin du fichier - 769 octets pour voir s'il y
// a une palette.
if ( (PCX_header.Depth==8) && (PCX_header.Version>=5) && (file_size>(256*3+128)) )
{
fseek(file,file_size-((256*3)+1),SEEK_SET);
// On regarde s'il y a une palette après les données de l'image
if (Read_byte(file,&byte1))
if (byte1==12) // Lire la palette si c'est une image en 256 couleurs
{
int index;
// On lit la palette 256c que ces crétins ont foutue à la fin du fichier
for(index=0;index<256;index++)
if ( ! Read_byte(file,&(context->Palette[index].R))
|| ! Read_byte(file,&(context->Palette[index].G))
|| ! Read_byte(file,&(context->Palette[index].B)) )
{
File_error=2;
GFX2_Log(GFX2_ERROR, "ERROR READING PCX PALETTE ! index=%d\n", index);
break;
}
}
}
// Maintenant qu'on a lu la palette que ces crétins sont allés foutre
// à la fin, on retourne juste après le header pour lire l'image.
fseek(file,128,SEEK_SET);
if (!File_error)
{
line_size=PCX_header.Bytes_per_plane_line*PCX_header.Plane;
real_line_size=(short)PCX_header.Bytes_per_plane_line<<3;
// On se sert de données ILBM car le dessin de ligne en moins de 256
// couleurs se fait comme avec la structure ILBM.
buffer=(byte *)malloc(line_size);
// Chargement de l'image
if (PCX_header.Compression) // Image compressée
{
/*Init_lecture();*/
image_size=(long)PCX_header.Bytes_per_plane_line*context->Height;
if (PCX_header.Depth==8) // 256 couleurs (1 plan)
{
for (position=0; ((positionHeight) && (!File_error)); y_pos++)
{
for (x_pos=0; ((x_posHeight) && (!File_error);y_pos++)
{
if ((width_read=Read_bytes(file,buffer,line_size)))
{
if (PCX_header.Plane==1)
for (x_pos=0; x_posWidth;x_pos++)
Set_pixel(context, x_pos,y_pos,buffer[x_pos]);
else
{
if (PCX_header.Depth==1)
Draw_IFF_line(context, buffer, y_pos,real_line_size,PCX_header.Plane);
else
Draw_PCX_line(context, buffer, y_pos,PCX_header.Depth);
}
}
else
File_error=2;
}
}
free(buffer);
}
}
}
else
{
// Image 24 bits!!!
if (File_error==0)
{
line_size=PCX_header.Bytes_per_plane_line*3;
buffer=(byte *)malloc(line_size);
if (!PCX_header.Compression)
{
for (y_pos=0;(y_posHeight) && (!File_error);y_pos++)
{
if (Read_bytes(file,buffer,line_size))
{
for (x_pos=0; x_posWidth; x_pos++)
Set_pixel_24b(context, x_pos,y_pos,buffer[x_pos+(PCX_header.Bytes_per_plane_line*0)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*1)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*2)]);
}
else
File_error=2;
}
}
else
{
/*Init_lecture();*/
for (y_pos=0,position=0;(y_posHeight) && (!File_error);)
{
// Lecture et décompression de la ligne
if(Read_byte(file,&byte1)!=1) File_error=2;
if (!File_error)
{
if ((byte1 & 0xC0)==0xC0)
{
byte1-=0xC0; // facteur de répétition
if(Read_byte(file,&byte2)!=1) File_error=2; // octet à répéter
if (!File_error)
{
for (index=0; (index=line_size)
{
for (x_pos=0; x_posWidth; x_pos++)
Set_pixel_24b(context, x_pos,y_pos,buffer[x_pos+(PCX_header.Bytes_per_plane_line*0)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*1)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*2)]);
y_pos++;
position=0;
}
}
}
}
else
{
buffer[position++]=byte1;
if (position>=line_size)
{
for (x_pos=0; x_posWidth; x_pos++)
Set_pixel_24b(context, x_pos,y_pos,buffer[x_pos+(PCX_header.Bytes_per_plane_line*0)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*1)],buffer[x_pos+(PCX_header.Bytes_per_plane_line*2)]);
y_pos++;
position=0;
}
}
}
}
if (position!=0)
File_error=2;
/*Close_lecture();*/
}
free(buffer);
buffer = NULL;
}
}
}
else
{
File_error=1;
}
fclose(file);
}
else
File_error=1;
}
// -- Ecrire un fichier au format PCX ---------------------------------------
void Save_PCX(T_IO_Context * context)
{
FILE *file;
short line_size;
short x_pos;
short y_pos;
byte counter;
byte last_pixel;
byte pixel_read;
File_error=0;
if ((file=Open_file_write(context)))
{
setvbuf(file, NULL, _IOFBF, 64*1024);
PCX_header.Manufacturer=10;
PCX_header.Version=5;
PCX_header.Compression=1;
PCX_header.Depth=8;
PCX_header.X_min=0;
PCX_header.Y_min=0;
PCX_header.X_max=context->Width-1;
PCX_header.Y_max=context->Height-1;
PCX_header.X_dpi=0;
PCX_header.Y_dpi=0;
memcpy(PCX_header.Palette_16c,context->Palette,48);
PCX_header.Reserved=0;
PCX_header.Plane=1;
PCX_header.Bytes_per_plane_line=(context->Width&1)?context->Width+1:context->Width;
PCX_header.Palette_info=1;
PCX_header.Screen_X=Screen_width;
PCX_header.Screen_Y=Screen_height;
memset(PCX_header.Filler,0,54);
if (Write_bytes(file,&(PCX_header.Manufacturer),1) &&
Write_bytes(file,&(PCX_header.Version),1) &&
Write_bytes(file,&(PCX_header.Compression),1) &&
Write_bytes(file,&(PCX_header.Depth),1) &&
Write_word_le(file,PCX_header.X_min) &&
Write_word_le(file,PCX_header.Y_min) &&
Write_word_le(file,PCX_header.X_max) &&
Write_word_le(file,PCX_header.Y_max) &&
Write_word_le(file,PCX_header.X_dpi) &&
Write_word_le(file,PCX_header.Y_dpi) &&
Write_bytes(file,&(PCX_header.Palette_16c),48) &&
Write_bytes(file,&(PCX_header.Reserved),1) &&
Write_bytes(file,&(PCX_header.Plane),1) &&
Write_word_le(file,PCX_header.Bytes_per_plane_line) &&
Write_word_le(file,PCX_header.Palette_info) &&
Write_word_le(file,PCX_header.Screen_X) &&
Write_word_le(file,PCX_header.Screen_Y) &&
Write_bytes(file,&(PCX_header.Filler),54) )
{
line_size=PCX_header.Bytes_per_plane_line*PCX_header.Plane;
for (y_pos=0; ((y_posHeight) && (!File_error)); y_pos++)
{
pixel_read=Get_pixel(context, 0,y_pos);
// Compression et écriture de la ligne
for (x_pos=0; ((x_pos1) || (last_pixel>=0xC0) )
Write_one_byte(file,counter|0xC0);
Write_one_byte(file,last_pixel);
}
}
// Ecriture de l'octet (12) indiquant que la palette arrive
if (!File_error)
Write_one_byte(file,12);
// Ecriture de la palette
if (!File_error)
{
if (! Write_bytes(file,context->Palette,sizeof(T_Palette)))
File_error=1;
}
}
else
File_error=1;
fclose(file);
if (File_error)
Remove_file(context);
}
else
File_error=1;
}
//////////////////////////////////// SCx ////////////////////////////////////
typedef struct
{
byte Filler1[4];
word Width;
word Height;
byte Filler2;
byte Planes;
} T_SCx_Header;
// -- Tester si un fichier est au format SCx --------------------------------
void Test_SCx(T_IO_Context * context, FILE * file)
{
//byte Signature[3];
T_SCx_Header SCx_header;
(void)context;
File_error=1;
// Ouverture du fichier
// Lecture et vérification de la signature
if (Read_bytes(file,SCx_header.Filler1,4)
&& Read_word_le(file, &(SCx_header.Width))
&& Read_word_le(file, &(SCx_header.Height))
&& Read_byte(file, &(SCx_header.Filler2))
&& Read_byte(file, &(SCx_header.Planes))
)
{
if ( (!memcmp(SCx_header.Filler1,"RIX",3))
&& SCx_header.Width && SCx_header.Height)
File_error=0;
}
}
// -- Lire un fichier au format SCx -----------------------------------------
void Load_SCx(T_IO_Context * context)
{
FILE *file;
word x_pos,y_pos;
long size,real_size;
long file_size;
T_SCx_Header SCx_header;
T_Palette SCx_Palette;
byte * buffer;
File_error=0;
if ((file=Open_file_read(context)))
{
file_size=File_length_file(file);
if (Read_bytes(file,SCx_header.Filler1,4)
&& Read_word_le(file, &(SCx_header.Width))
&& Read_word_le(file, &(SCx_header.Height))
&& Read_byte(file, &(SCx_header.Filler2))
&& Read_byte(file, &(SCx_header.Planes))
)
{
Pre_load(context, SCx_header.Width,SCx_header.Height,file_size,FORMAT_SCx,PIXEL_SIMPLE,0);
if (File_error==0)
{
if (!SCx_header.Planes)
size=sizeof(T_Palette);
else
size=sizeof(T_Components)*(1<Palette,0,sizeof(T_Palette));
Palette_64_to_256(SCx_Palette);
memcpy(context->Palette,SCx_Palette,size);
context->Width=SCx_header.Width;
context->Height=SCx_header.Height;
if (!SCx_header.Planes)
{ // 256 couleurs (raw)
buffer=(byte *)malloc(context->Width);
for (y_pos=0;(y_posHeight) && (!File_error);y_pos++)
{
if (Read_bytes(file,buffer,context->Width))
for (x_pos=0; x_posWidth;x_pos++)
Set_pixel(context, x_pos,y_pos,buffer[x_pos]);
else
File_error=2;
}
}
else
{ // moins de 256 couleurs (planar)
size=((context->Width+7)>>3)*SCx_header.Planes;
real_size=(size/SCx_header.Planes)<<3;
buffer=(byte *)malloc(size);
for (y_pos=0;(y_posHeight) && (!File_error);y_pos++)
{
if (Read_bytes(file,buffer,size))
Draw_IFF_line(context, buffer, y_pos,real_size,SCx_header.Planes);
else
File_error=2;
}
}
free(buffer);
}
else
File_error=1;
}
}
else
File_error=1;
fclose(file);
}
else
File_error=1;
}
// -- Sauver un fichier au format SCx ---------------------------------------
void Save_SCx(T_IO_Context * context)
{
FILE *file;
short x_pos,y_pos;
T_SCx_Header SCx_header;
byte last_char;
last_char=strlen(context->File_name)-1;
if (context->File_name[last_char]=='?')
{
if (context->Width<=320)
context->File_name[last_char]='I';
else
{
if (context->Width<=360)
context->File_name[last_char]='Q';
else
{
if (context->Width<=640)
context->File_name[last_char]='F';
else
{
if (context->Width<=800)
context->File_name[last_char]='N';
else
context->File_name[last_char]='O';
}
}
}
}
File_error=0;
// Ouverture du fichier
if ((file=Open_file_write(context)))
{
T_Palette palette_64;
setvbuf(file, NULL, _IOFBF, 64*1024);
memcpy(palette_64,context->Palette,sizeof(T_Palette));
Palette_256_to_64(palette_64);
memcpy(SCx_header.Filler1,"RIX3",4);
SCx_header.Width=context->Width;
SCx_header.Height=context->Height;
SCx_header.Filler2=0xAF;
SCx_header.Planes=0x00;
if (Write_bytes(file,SCx_header.Filler1,4)
&& Write_word_le(file, SCx_header.Width)
&& Write_word_le(file, SCx_header.Height)
&& Write_byte(file, SCx_header.Filler2)
&& Write_byte(file, SCx_header.Planes)
&& Write_bytes(file,&palette_64,sizeof(T_Palette))
)
{
for (y_pos=0; ((y_posHeight) && (!File_error)); y_pos++)
for (x_pos=0; x_posWidth; x_pos++)
Write_one_byte(file,Get_pixel(context, x_pos,y_pos));
fclose(file);
if (File_error)
Remove_file(context);
}
else // Error d'écriture (disque plein ou protégé)
{
fclose(file);
Remove_file(context);
File_error=1;
}
}
else
{
File_error=1;
}
}
//////////////////////////////////// XPM ////////////////////////////////////
void Save_XPM(T_IO_Context* context)
{
// XPM are unix files, so use LF '\n' line endings
FILE* file;
int i,j;
byte max_color = 0;
word color_count;
File_error = 0;
file = Open_file_write(context);
if (file == NULL)
{
File_error = 1;
return;
}
setvbuf(file, NULL, _IOFBF, 64*1024);
// in case there are less colors than 256, we could
// optimize, and use only 1 character per pixel if possible
// printable characters are from 0x20 to 0x7e, minus " 0x22 and \ 0x5c
#define XPM_USABLE_CHARS (0x7f - 0x20 - 2)
for (j = 0; j < context->Height; j++)
for (i = 0; i < context->Width; i++)
{
byte value = Get_pixel(context, i, j);
if (value > max_color)
max_color = value;
}
color_count = (word)max_color + 1;
fprintf(file, "/* XPM */\nstatic char* pixmap[] = {\n");
fprintf(file, "\"%d %d %d %d\",\n", context->Width, context->Height, color_count, color_count > XPM_USABLE_CHARS ? 2 : 1);
if (color_count > XPM_USABLE_CHARS)
{
for (i = 0; i < color_count; i++)
{
if (context->Background_transparent && (i == context->Transparent_color))
fprintf(file, "\"%2.2X\tc None\",\n", i); // None is for transparent color
else
fprintf(file,"\"%2.2X\tc #%2.2x%2.2x%2.2x\",\n", i, context->Palette[i].R, context->Palette[i].G,
context->Palette[i].B);
}
for (j = 0; j < context->Height; j++)
{
fprintf(file, "\"");
for (i = 0; i < context->Width; i++)
fprintf(file, "%2.2X", Get_pixel(context, i, j));
if (j == (context->Height - 1))
fprintf(file,"\"\n");
else
fprintf(file,"\",\n");
}
}
else
{
int c;
for (i = 0; i < color_count; i++)
{
c = (i < 2) ? i + 0x20 : i + 0x21;
if (c >= 0x5c) c++;
if (context->Background_transparent && (i == context->Transparent_color))
fprintf(file, "\"%c\tc None\",\n", c); // None is for transparent color
else
fprintf(file,"\"%c\tc #%2.2x%2.2x%2.2x\",\n", c, context->Palette[i].R, context->Palette[i].G,
context->Palette[i].B);
}
for (j = 0; j < context->Height; j++)
{
fprintf(file, "\"");
for (i = 0; i < context->Width; i++)
{
c = Get_pixel(context, i, j);
c = (c < 2) ? c + 0x20 : c + 0x21;
if (c >= 0x5c) c++;
fprintf(file, "%c", c);
}
if (j == (context->Height - 1))
fprintf(file,"\"\n");
else
fprintf(file,"\",\n");
}
}
fprintf(file, "};\n");
fclose(file);
}
//////////////////////////////////// PNG ////////////////////////////////////
/**
* @defgroup PNG PNG format
* @ingroup loadsaveformats
* Portable Network Graphics
*
* We make use of libpng : http://www.libpng.org/pub/png/libpng.html
* @{
*/
#ifndef __no_pnglib__
/// Test for PNG format
///
/// The 8 byte signature at the start of file is tested
void Test_PNG(T_IO_Context * context, FILE * file)
{
byte png_header[8];
(void)context;
File_error=1;
// Lecture du header du fichier
if (Read_bytes(file,png_header,8))
{
if ( !png_sig_cmp(png_header, 0, 8))
File_error=0;
}
}
/// Callback to handle our private chunks
///
/// We have one private chunk at the moment :
/// - "crNg" which is similar to a CRNG chunk in an IFF file
static int PNG_read_unknown_chunk(png_structp ptr, png_unknown_chunkp chunk)
{
T_IO_Context * context;
// png_unknown_chunkp members:
// png_byte name[5];
// png_byte *data;
// png_size_t size;
context = (T_IO_Context *)png_get_user_chunk_ptr(ptr);
GFX2_Log(GFX2_DEBUG, "PNG private chunk '%s' :\n", chunk->name);
GFX2_LogHexDump(GFX2_DEBUG, "", chunk->data, 0, chunk->size);
if (!strcmp((const char *)chunk->name, "crNg"))
{
unsigned int i;
const byte *chunk_ptr = chunk->data;
// Should be a multiple of 6
if (chunk->size % 6)
return (-1);
for(i=0;isize/6 && i<16; i++)
{
word rate;
word flags;
byte min_col;
byte max_col;
// Rate (big-endian word)
rate = *(chunk_ptr++) << 8;
rate |= *(chunk_ptr++);
// Flags (big-endian)
flags = *(chunk_ptr++) << 8;
flags |= *(chunk_ptr++);
// Min color
min_col = *(chunk_ptr++);
// Max color
max_col = *(chunk_ptr++);
// Check validity
if (min_col != max_col)
{
// Valid cycling range
if (max_colCycle_range[i].Start=min_col;
context->Cycle_range[i].End=max_col;
context->Cycle_range[i].Inverse=(flags&2)?1:0;
context->Cycle_range[i].Speed=(flags&1) ? rate/78 : 0;
context->Color_cycles=i+1;
}
}
return (1); // >0 = success
}
return (0); /* did not recognize */
}
/// Private structure used in PNG_memory_read() and PNG_memory_write()
struct PNG_memory_buffer {
char * buffer;
unsigned long offset;
unsigned long size;
};
/// read from memory buffer
static void PNG_memory_read(png_structp png_ptr, png_bytep p, png_size_t count)
{
struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr);
GFX2_Log(GFX2_DEBUG, "PNG_memory_read(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer);
if (buffer == NULL || p == NULL)
return;
if (buffer->offset + count <= buffer->size)
{
memcpy(p, buffer->buffer + buffer->offset, count);
buffer->offset += count;
}
else
{
unsigned long available_count = buffer->size - buffer->offset;
GFX2_Log(GFX2_DEBUG, "PNG_memory_read(): only %lu bytes available\n", available_count);
if (available_count > 0)
{
memcpy(p, buffer->buffer + buffer->offset, available_count);
buffer->offset += available_count;
}
}
}
/// Read PNG format file
void Load_PNG_Sub(T_IO_Context * context, FILE * file, const char * memory_buffer, unsigned long memory_buffer_size)
{
png_structp png_ptr;
png_infop info_ptr = NULL;
// Prepare internal PNG loader
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr)
{
// Prepare internal PNG loader
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr)
{
png_byte color_type;
png_byte bit_depth;
byte bpp;
struct PNG_memory_buffer buffer;
// Setup a return point. If a pnglib loading error occurs
// in this if(), the else will be executed.
if (!setjmp(png_jmpbuf(png_ptr)))
{
// to read from memory, I need to use png_set_read_fn() instead of calling png_init_io()
if (file != NULL)
png_init_io(png_ptr, file);
else
{
buffer.buffer = (char *)memory_buffer;
buffer.offset = 8; // skip header
buffer.size = memory_buffer_size;
png_set_read_fn(png_ptr, &buffer, PNG_memory_read);
}
// Inform pnglib we already loaded the header.
png_set_sig_bytes(png_ptr, 8);
// Hook the handler for unknown chunks
png_set_read_user_chunk_fn(png_ptr, (png_voidp)context, &PNG_read_unknown_chunk);
// Load file information
png_read_info(png_ptr, info_ptr);
color_type = png_get_color_type(png_ptr,info_ptr);
bit_depth = png_get_bit_depth(png_ptr,info_ptr);
switch (color_type)
{
case PNG_COLOR_TYPE_GRAY_ALPHA:
bpp = bit_depth * 2;
break;
case PNG_COLOR_TYPE_RGB:
bpp = bit_depth * 3;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
bpp = bit_depth * 4;
break;
case PNG_COLOR_TYPE_PALETTE:
case PNG_COLOR_TYPE_GRAY:
default:
bpp = bit_depth;
}
GFX2_Log(GFX2_DEBUG, "PNG type=%u bit_depth=%u : %ubpp\n", color_type, bit_depth, bpp);
// If it's any supported file
// (Note: As of writing this, this test covers every possible
// image format of libpng)
if (color_type == PNG_COLOR_TYPE_PALETTE
|| color_type == PNG_COLOR_TYPE_GRAY
|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA
|| color_type == PNG_COLOR_TYPE_RGB
|| color_type == PNG_COLOR_TYPE_RGB_ALPHA
)
{
enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
int num_text;
png_text *text_ptr;
int unit_type;
png_uint_32 res_x;
png_uint_32 res_y;
// Comment (tEXt)
context->Comment[0]='\0'; // Clear the previous comment
if ((num_text=png_get_text(png_ptr, info_ptr, &text_ptr, NULL)))
{
while (num_text--)
{
int size = COMMENT_SIZE;
if (text_ptr[num_text].text_length > 0 && text_ptr[num_text].text_length < COMMENT_SIZE)
size = text_ptr[num_text].text_length;
if (strcmp(text_ptr[num_text].key,"Title") == 0)
{
strncpy(context->Comment, text_ptr[num_text].text, size);
context->Comment[size]='\0';
break; // Skip all others tEXt chunks
}
else if(strcmp(text_ptr[num_text].key, "Comment") == 0)
{
strncpy(context->Comment, text_ptr[num_text].text, size);
context->Comment[size]='\0';
break; // Skip all others tEXt chunks
}
}
}
// Pixel Ratio (pHYs)
if (png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type))
{
// Ignore unit, and use the X/Y ratio as a hint for
// WIDE or TALL pixels
if (res_x>0 && res_y>0)
{
if (res_y * 10 > res_x * 12) // X/Y < 1/1.2
ratio = PIXEL_WIDE;
else if (res_x * 10 > res_y * 12) // X/Y > 1.2
ratio = PIXEL_TALL;
}
}
Pre_load(context,
png_get_image_width(png_ptr, info_ptr),
png_get_image_height(png_ptr, info_ptr),
file != NULL ? File_length_file(file) : memory_buffer_size,
FORMAT_PNG, ratio, bpp);
if (File_error==0)
{
int x,y;
png_colorp palette;
int num_palette;
png_bytep * Row_pointers = NULL;
byte row_pointers_allocated = 0;
int num_trans;
png_bytep trans;
png_color_16p trans_values;
// 16-bit images
if (bit_depth == 16)
{
// Reduce to 8-bit
png_set_strip_16(png_ptr);
}
else if (bit_depth < 8)
{
// Inform libpng we want one byte per pixel,
// even though the file was less than 8bpp
png_set_packing(png_ptr);
}
// Images with alpha channel
if (color_type & PNG_COLOR_MASK_ALPHA)
{
// Tell libpng to ignore it
png_set_strip_alpha(png_ptr);
}
// Greyscale images :
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
{
// Map low bpp greyscales to full 8bit (0-255 range)
if (bit_depth < 8)
{
#if (PNG_LIBPNG_VER_MAJOR <= 1) && (PNG_LIBPNG_VER_MINOR < 4)
// Works well with png 1.2.8, but deprecated in 1.4 ...
png_set_gray_1_2_4_to_8(png_ptr);
#else
// ...where this seems to replace it:
png_set_expand_gray_1_2_4_to_8(png_ptr);
#endif
}
// Create greyscale palette
for (x=0;x<256;x++)
{
context->Palette[x].R=x;
context->Palette[x].G=x;
context->Palette[x].B=x;
}
}
else if (color_type == PNG_COLOR_TYPE_PALETTE) // Palette images
{
if (bit_depth < 8)
{
// Clear unused colors
if (Config.Clear_palette)
memset(context->Palette,0,sizeof(T_Palette));
}
// Get a pointer to the PNG palette
png_get_PLTE(png_ptr, info_ptr, &palette,
&num_palette);
// Copy all colors to the context
for (x=0;xPalette[x].R=palette[x].red;
context->Palette[x].G=palette[x].green;
context->Palette[x].B=palette[x].blue;
}
// The palette must not be freed: it is owned by libpng.
palette = NULL;
}
// Transparency (tRNS)
if (png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values))
{
if (color_type == PNG_COLOR_TYPE_PALETTE && trans!=NULL)
{
int i;
for (i=0; iTransparent_color = i;
context->Background_transparent = 1;
break;
}
}
}
else if ((color_type == PNG_COLOR_TYPE_GRAY
|| color_type == PNG_COLOR_TYPE_RGB) && trans_values!=NULL)
{
// In this case, num_trans is supposed to be "1",
// and trans_values[0] contains the reference color
// (RGB triplet) that counts as transparent.
// Ideally, we should reserve this color in the palette,
// (so it's not merged and averaged with a neighbor one)
// and after creating the optimized palette, find its
// index and mark it transparent.
// Current implementation: ignore.
}
}
context->Width=png_get_image_width(png_ptr,info_ptr);
context->Height=png_get_image_height(png_ptr,info_ptr);
png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
// Allocate row pointers
Row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * context->Height);
row_pointers_allocated = 0;
/* read file */
if (!setjmp(png_jmpbuf(png_ptr)))
{
if (color_type == PNG_COLOR_TYPE_GRAY
|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA
|| color_type == PNG_COLOR_TYPE_PALETTE
)
{
// 8bpp
for (y=0; yHeight; y++)
Row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));
row_pointers_allocated = 1;
png_read_image(png_ptr, Row_pointers);
for (y=0; yHeight; y++)
for (x=0; xWidth; x++)
Set_pixel(context, x, y, Row_pointers[y][x]);
}
else
{
switch (context->Type)
{
case CONTEXT_PREVIEW:
// 24bpp
// It's a preview
// Unfortunately we need to allocate loads of memory
for (y=0; yHeight; y++)
Row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));
row_pointers_allocated = 1;
png_read_image(png_ptr, Row_pointers);
for (y=0; yHeight; y++)
for (x=0; xWidth; x++)
Set_pixel_24b(context, x, y, Row_pointers[y][x*3],Row_pointers[y][x*3+1],Row_pointers[y][x*3+2]);
break;
case CONTEXT_MAIN_IMAGE:
case CONTEXT_BRUSH:
case CONTEXT_SURFACE:
// It's loading an actual image
// We'll save memory and time by writing directly into
// our pre-allocated 24bit buffer
for (y=0; yHeight; y++)
Row_pointers[y] = (png_byte*) (&(context->Buffer_image_24b[y * context->Width]));
png_read_image(png_ptr, Row_pointers);
break;
case CONTEXT_PALETTE:
case CONTEXT_PREVIEW_PALETTE:
// No pixels to draw in a palette!
break;
}
}
}
else
File_error=2;
/* cleanup heap allocation */
if (row_pointers_allocated)
{
for (y=0; yHeight; y++) {
free(Row_pointers[y]);
Row_pointers[y] = NULL;
}
}
free(Row_pointers);
Row_pointers = NULL;
}
else
File_error=2;
}
else
// Unsupported image type
File_error=1;
}
else
File_error=1;
}
else
File_error=1;
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
}
}
/// Read PNG format files
///
/// just read/test the header and call Load_PNG_Sub()
void Load_PNG(T_IO_Context * context)
{
FILE *file;
byte png_header[8];
File_error=0;
if ((file=Open_file_read(context)))
{
// Load header (8 first bytes)
if (Read_bytes(file,png_header,8))
{
// Do we recognize a png file signature ?
if ( !png_sig_cmp(png_header, 0, 8))
Load_PNG_Sub(context, file, NULL, 0);
else
File_error=1;
}
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;
}
/// Write to memory buffer
static void PNG_memory_write(png_structp png_ptr, png_bytep p, png_size_t count)
{
struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr);
GFX2_Log(GFX2_DEBUG, "PNG_memory_write(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer);
if (buffer->size < buffer->offset + count)
{
char * tmp = realloc(buffer->buffer, buffer->offset + count + 1024);
if (tmp == NULL)
{
GFX2_Log(GFX2_ERROR, "PNG_memory_write() Failed to allocate %u bytes of memory\n", buffer->offset + count + 1024);
File_error = 1;
return;
}
buffer->buffer = tmp;
buffer->size = buffer->offset + count + 1024;
}
memcpy(buffer->buffer + buffer->offset, p, count);
buffer->offset += count;
}
/// do nothing
static void PNG_memory_flush(png_structp png_ptr)
{
struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr);
GFX2_Log(GFX2_DEBUG, "PNG_memory_flush(%p) (io_ptr=%p)\n", png_ptr, buffer);
}
/// Save a PNG to file or memory
/// @param context the IO context
/// @param file the FILE to write to or NULL to write to memory
/// @param buffer will receive a malloc'ed buffer if writting to memory
/// @param buffer_size will receive the PNG size in memory
void Save_PNG_Sub(T_IO_Context * context, FILE * file, char * * buffer, unsigned long * buffer_size)
{
static png_bytep * Row_pointers = NULL;
int y;
byte * pixel_ptr;
png_structp png_ptr;
png_infop info_ptr;
png_unknown_chunk crng_chunk;
byte cycle_data[16*6]; // Storage for color-cycling data, referenced by crng_chunk
struct PNG_memory_buffer memory_buffer;
/* initialisation */
if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))
&& (info_ptr = png_create_info_struct(png_ptr)))
{
if (!setjmp(png_jmpbuf(png_ptr)))
{
if (file != NULL)
png_init_io(png_ptr, file);
else
{
// to write to memory, use png_set_write_fn() instead of calling png_init_io()
memset(&memory_buffer, 0, sizeof(memory_buffer));
png_set_write_fn(png_ptr, &memory_buffer, PNG_memory_write, PNG_memory_flush);
}
/* en-tete */
if (!setjmp(png_jmpbuf(png_ptr)))
{
png_set_IHDR(png_ptr, info_ptr, context->Width, context->Height,
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(png_ptr, info_ptr, (png_colorp)context->Palette, 256);
{
// Commentaires texte PNG
// Cette partie est optionnelle
png_text text_ptr[2] = {
#ifdef PNG_iTXt_SUPPORTED
{-1, "Software", "Grafx2", 6, 0, NULL, NULL},
{-1, "Title", NULL, 0, 0, NULL, NULL}
#else
{-1, "Software", "Grafx2", 6},
{-1, "Title", NULL, 0}
#endif
};
int nb_text_chunks=1;
if (context->Comment[0])
{
text_ptr[1].text=context->Comment;
text_ptr[1].text_length=strlen(context->Comment);
nb_text_chunks=2;
}
png_set_text(png_ptr, info_ptr, text_ptr, nb_text_chunks);
}
if (context->Background_transparent)
{
// Transparency
byte opacity[256];
// Need to fill a segment with '255', up to the transparent color
// which will have a 0. This piece of data (1 to 256 bytes)
// will be stored in the file.
memset(opacity, 255,context->Transparent_color);
opacity[context->Transparent_color]=0;
png_set_tRNS(png_ptr, info_ptr, opacity, (int)1 + context->Transparent_color,0);
}
// if using PNG_RESOLUTION_METER, unit is in dot per meter.
// 72 DPI = 2835, 600 DPI = 23622
// with PNG_RESOLUTION_UNKNOWN, it is arbitrary
switch(Pixel_ratio)
{
case PIXEL_WIDE:
case PIXEL_WIDE2:
png_set_pHYs(png_ptr, info_ptr, 1, 2, PNG_RESOLUTION_UNKNOWN);
break;
case PIXEL_TALL:
case PIXEL_TALL2:
png_set_pHYs(png_ptr, info_ptr, 2, 1, PNG_RESOLUTION_UNKNOWN);
break;
case PIXEL_TALL3:
png_set_pHYs(png_ptr, info_ptr, 4, 3, PNG_RESOLUTION_UNKNOWN);
break;
default:
break;
}
// Write cycling colors
if (context->Color_cycles)
{
// Save a chunk called 'crNg'
// The case is selected by the following rules from PNG standard:
// char 1: non-mandatory = lowercase
// char 2: private (not standard) = lowercase
// char 3: reserved = always uppercase
// char 4: can be copied by editors = lowercase
// First, turn our nice structure into byte array
// (just to avoid padding in structures)
byte *chunk_ptr = cycle_data;
int i;
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
// Big end of Rate
*(chunk_ptr++) = (context->Cycle_range[i].Speed*78) >> 8;
// Low end of Rate
*(chunk_ptr++) = (context->Cycle_range[i].Speed*78) & 0xFF;
// Big end of Flags
*(chunk_ptr++) = (flags) >> 8;
// Low end of Flags
*(chunk_ptr++) = (flags) & 0xFF;
// Min color
*(chunk_ptr++) = context->Cycle_range[i].Start;
// Max color
*(chunk_ptr++) = context->Cycle_range[i].End;
}
// Build one unknown_chuck structure
memcpy(crng_chunk.name, "crNg",5);
crng_chunk.data=cycle_data;
crng_chunk.size=context->Color_cycles*6;
crng_chunk.location=PNG_HAVE_PLTE;
// Give it to libpng
png_set_unknown_chunks(png_ptr, info_ptr, &crng_chunk, 1);
// libpng seems to ignore the location I provided earlier.
png_set_unknown_chunk_location(png_ptr, info_ptr, 0, PNG_HAVE_PLTE);
}
png_write_info(png_ptr, info_ptr);
/* ecriture des pixels de l'image */
Row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * context->Height);
pixel_ptr = context->Target_address;
for (y=0; yHeight; y++)
Row_pointers[y] = (png_byte*)(pixel_ptr+y*context->Pitch);
if (!setjmp(png_jmpbuf(png_ptr)))
{
png_write_image(png_ptr, Row_pointers);
/* cloture png */
if (!setjmp(png_jmpbuf(png_ptr)))
{
png_write_end(png_ptr, NULL);
}
else
File_error=1;
}
else
File_error=1;
}
else
File_error=1;
}
else
{
File_error=1;
}
png_destroy_write_struct(&png_ptr, &info_ptr);
}
else
File_error=1;
if (Row_pointers)
free(Row_pointers);
if (memory_buffer.buffer)
{
*buffer = memory_buffer.buffer;
*buffer_size = memory_buffer.offset;
}
}
/// Save a PNG file
void Save_PNG(T_IO_Context * context)
{
FILE *file;
File_error = 0;
file = Open_file_write(context);
if (file != NULL)
{
Save_PNG_Sub(context, file, NULL, NULL);
fclose(file);
// remove the file if there was an error
if (File_error)
Remove_file(context);
}
else
File_error = 1;
}
#endif // __no_pnglib__
/** @} */