/* vim:expandtab:ts=2 sw=2:
*/
/* Grafx2 - The Ultimate 256-color bitmap paint program
Copyright 2018 Thomas Bernard
Copyright 2011 Pawel Góralski
Copyright 2010 Alexander Filyanov
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
*/
#include
#include
#include
#include
#ifndef _MSC_VER
#include
#endif
#if defined(WIN32)
#include
#if defined(_MSC_VER)
#define strdup _strdup
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#endif
#endif
#include
#if defined(USE_SDL) || defined(USE_SDL2)
#include
#include
#include
#endif
#ifndef __no_pnglib__
#include
#endif
#include "gfx2log.h"
#include "gfx2mem.h"
#include "buttons.h"
#include "const.h"
#include "errors.h"
#include "global.h"
#include "keycodes.h"
#include "io.h"
#include "loadsave.h"
#include "loadsavefuncs.h"
#include "misc.h"
#include "osdep.h"
#include "graph.h"
#include "op_c.h"
#include "pages.h"
#include "palette.h"
#if defined(USE_SDL) || defined(USE_SDL2)
#include "sdlscreen.h"
#endif
#include "screen.h"
#include "struct.h"
#include "windows.h"
#include "engine.h"
#include "brush.h"
#include "setup.h"
#include "filesel.h"
#include "unicode.h"
#include "fileformats.h"
#include "bitcount.h"
#if defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11))
#include "input.h"
#if defined(USE_X11)
extern Display * X11_display;
extern Window X11_window;
#endif
#endif
#if defined(__macosx__)
const void * get_tiff_paste_board(unsigned long * size);
int set_tiff_paste_board(const void * tiff, unsigned long size);
#endif
#if defined(USE_SDL) || defined(USE_SDL2)
// -- SDL_Image -------------------------------------------------------------
// (TGA, BMP, PNM, XPM, XCF, PCX, GIF, JPG, TIF, IFF, PNG, ICO)
void Load_SDL_Image(T_IO_Context *);
#endif
// -- Recoil ----------------------------------------------------------------
// 8bits and 16bits computer graphics
#ifndef NORECOIL
void Load_Recoil_Image(T_IO_Context *);
#endif
// clipboard
static void Load_ClipBoard_Image(T_IO_Context *);
static void Save_ClipBoard_Image(T_IO_Context *);
// ENUM Name TestFunc LoadFunc SaveFunc PalOnly Comment Layers Ext Exts
const T_Format File_formats[] = {
{FORMAT_ALL_IMAGES, "(all)", NULL, NULL, NULL, 0, 0, 0, "",
"gif;png;bmp;2bp;pcx;pkm;iff;lbm;ilbm;sham;ham;ham6;ham8;acbm;pic;anim;img;sci;scq;scf;scn;sco;cel;"
"pi1;pc1;pi2;pc2;pi3;pc3;neo;tny;tn1;tn2;tn3;tn4;ca1;ca2;ca3;"
"c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;prg;pmg;rpm;"
"gpx;"
"cpc;scr;win;pph;cm5;go1;"
"hgr;dhgr;"
"grb;grob;"
"sc2;"
"tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff;ico;ic2;cur;info;flc;bin;map"},
{FORMAT_ALL_PALETTES, "(pal)", NULL, NULL, NULL, 1, 0, 0, "", "kcf;pal;gpl"},
{FORMAT_ALL_FILES, "(*.*)", NULL, NULL, NULL, 0, 0, 0, "", "*"},
{FORMAT_GIF, " gif", Test_GIF, Load_GIF, Save_GIF, 0, 1, 1, "gif", "gif"},
#ifndef __no_pnglib__
{FORMAT_PNG, " png", Test_PNG, Load_PNG, Save_PNG, 0, 1, 0, "png", "png"},
#endif
{FORMAT_BMP, " bmp", Test_BMP, Load_BMP, Save_BMP, 0, 0, 0, "bmp", "bmp;2bp"},
{FORMAT_PCX, " pcx", Test_PCX, Load_PCX, Save_PCX, 0, 0, 0, "pcx", "pcx"},
{FORMAT_PKM, " pkm", Test_PKM, Load_PKM, Save_PKM, 0, 1, 0, "pkm", "pkm"},
{FORMAT_LBM, " lbm", Test_LBM, Load_IFF, Save_IFF, 0, 1, 0, "iff", "iff;lbm;ilbm;sham;ham;ham6;ham8;anim"},
{FORMAT_PBM, " pbm", Test_PBM, Load_IFF, Save_IFF, 0, 1, 0, "iff", "iff;pbm;lbm"},
{FORMAT_ACBM," acbm",Test_ACBM,Load_IFF, NULL, 0, 1, 0, "iff", "iff;pic;acbm"},
{FORMAT_IMG, " img", Test_IMG, Load_IMG, Save_IMG, 0, 0, 0, "img", "img"},
{FORMAT_SCx, " sc?", Test_SCx, Load_SCx, Save_SCx, 0, 0, 0, "sc?", "sci;scq;scf;scn;sco"},
{FORMAT_PI1, " pi1", Test_PI1, Load_PI1, Save_PI1, 0, 0, 0, "pi1", "pi1;pi2;pi3"},
{FORMAT_PC1, " pc1", Test_PC1, Load_PC1, Save_PC1, 0, 0, 0, "pc1", "pc1;pc2;pc3"},
{FORMAT_CA1, " ca1", Test_CA1, Load_CA1, Save_CA1, 0, 0, 0, "ca1", "ca1;ca2;ca3"},
{FORMAT_TNY, " tny", Test_TNY, Load_TNY, Save_TNY, 0, 0, 0, "tny", "tny;tn1;tn2;tn3;tn4"},
{FORMAT_CEL, " cel", Test_CEL, Load_CEL, Save_CEL, 0, 0, 0, "cel", "cel"},
{FORMAT_NEO, " neo", Test_NEO, Load_NEO, Save_NEO, 0, 0, 0, "neo", "neo"},
{FORMAT_KCF, " kcf", Test_KCF, Load_KCF, Save_KCF, 1, 0, 0, "kcf", "kcf"},
{FORMAT_PAL, " pal", Test_PAL, Load_PAL, Save_PAL, 1, 0, 0, "pal", "pal"},
{FORMAT_GPL, " gpl", Test_GPL, Load_GPL, Save_GPL, 1, 0, 0, "gpl", "gpl"},
{FORMAT_C64, " c64", Test_C64, Load_C64, Save_C64, 0, 1, 1, "c64",
"c64;p64;a64;pi;rp;aas;art;dd;iph;ipt;hpc;ocp;koa;koala;fli;bml;cdu;pmg;rpm"},
{FORMAT_PRG, " prg", Test_PRG, Load_PRG, Save_PRG, 0, 0, 1, "prg", "prg"},
{FORMAT_GPX, " gpx", Test_GPX, Load_GPX, NULL, 0, 0, 0, "gpx", "gpx"},
{FORMAT_SCR, " cpc", Test_SCR, Load_SCR, Save_SCR, 0, 0, 0, "scr", "cpc;scr;win"},
{FORMAT_CM5, " cm5", Test_CM5, Load_CM5, Save_CM5, 0, 0, 1, "cm5", "cm5"},
{FORMAT_PPH, " pph", Test_PPH, Load_PPH, Save_PPH, 0, 0, 1, "pph", "pph"},
{FORMAT_GOS, " go1", Test_GOS, Load_GOS, Save_GOS, 0, 0, 0, "go1", "go1"},
{FORMAT_XPM, " xpm", NULL, NULL, Save_XPM, 0, 0, 0, "xpm", "xpm"},
{FORMAT_ICO, " ico", Test_ICO, Load_ICO, Save_ICO, 0, 0, 0, "ico", "ico;ic2;cur"},
{FORMAT_INFO," info",Test_INFO,Load_INFO,NULL, 0, 0, 0, "info", "info"},
{FORMAT_FLI, " flc", Test_FLI, Load_FLI, NULL, 0, 0, 0, "flc", "flc;fli;dat"},
{FORMAT_MOTO," moto",Test_MOTO,Load_MOTO,Save_MOTO,0, 1, 0, "bin", "bin;map"},
{FORMAT_MSX, " msx", Test_MSX, Load_MSX, Save_MSX, 0, 0, 0, "sc2", "sc2"},
{FORMAT_HGR, " hgr", Test_HGR, Load_HGR, Save_HGR, 0, 0, 1, "hgr", "hgr;dhgr;bin"},
#ifndef __no_tifflib__
{FORMAT_TIFF," tiff",Test_TIFF,Load_TIFF,Save_TIFF,0, 1, 1, "tif", "tif;tiff"},
#endif
{FORMAT_GRB, " grb", Test_GRB, Load_GRB, NULL, 0, 0, 0, "grb", "grb;grob"},
{FORMAT_MISC,"misc.",NULL, NULL, NULL, 0, 0, 0, "", "tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff"},
};
/// Total number of known file formats
unsigned int Nb_known_formats(void)
{
return sizeof(File_formats)/sizeof(File_formats[0]);
}
/// Set the color of a pixel (on load)
void Set_pixel(T_IO_Context *context, short x_pos, short y_pos, byte color)
{
// Clipping
if ((x_pos>=context->Width) || (y_pos>=context->Height))
return;
switch (context->Type)
{
// Chargement des pixels dans l'écran principal
case CONTEXT_MAIN_IMAGE:
Pixel_in_current_screen(x_pos,y_pos,color);
break;
// Chargement des pixels dans la brosse
case CONTEXT_BRUSH:
//Pixel_in_brush(x_pos,y_pos,color);
*(context->Buffer_image + y_pos * context->Pitch + x_pos)=color;
break;
// Chargement des pixels dans la preview
case CONTEXT_PREVIEW:
// Skip pixels of transparent index if :
// it's a layer above the first one
if (color == context->Transparent_color && context->Current_layer > 0)
break;
if (((x_pos % context->Preview_factor_X)==0) && ((y_pos % context->Preview_factor_Y)==0))
{
// Tag the color as 'used'
context->Preview_usage[color]=1;
// Store pixel
if (context->Ratio == PIXEL_WIDE &&
Pixel_ratio != PIXEL_WIDE &&
Pixel_ratio != PIXEL_WIDE2)
{
context->Preview_bitmap[x_pos/context->Preview_factor_X*2 + (y_pos/context->Preview_factor_Y)*PREVIEW_WIDTH*Menu_factor_X]=color;
context->Preview_bitmap[x_pos/context->Preview_factor_X*2+1 + (y_pos/context->Preview_factor_Y)*PREVIEW_WIDTH*Menu_factor_X]=color;
}
else if (context->Ratio == PIXEL_TALL &&
Pixel_ratio != PIXEL_TALL &&
Pixel_ratio != PIXEL_TALL2 &&
Pixel_ratio != PIXEL_TALL3)
{
context->Preview_bitmap[x_pos/context->Preview_factor_X + (y_pos/context->Preview_factor_Y*2)*PREVIEW_WIDTH*Menu_factor_X]=color;
context->Preview_bitmap[x_pos/context->Preview_factor_X + (y_pos/context->Preview_factor_Y*2+1)*PREVIEW_WIDTH*Menu_factor_X]=color;
}
else
context->Preview_bitmap[x_pos/context->Preview_factor_X + (y_pos/context->Preview_factor_Y)*PREVIEW_WIDTH*Menu_factor_X]=color;
}
break;
// Load pixels into a Surface
case CONTEXT_SURFACE:
if (x_pos>=0 && y_pos>=0 && x_posSurface->w && y_posSurface->h)
Set_GFX2_Surface_pixel(context->Surface, x_pos, y_pos, color);
break;
case CONTEXT_PALETTE:
case CONTEXT_PREVIEW_PALETTE:
break;
}
}
void Fill_canvas(T_IO_Context *context, byte color)
{
switch (context->Type)
{
case CONTEXT_PREVIEW:
if (context->Current_layer!=0)
return;
memset(context->Preview_bitmap, color, PREVIEW_WIDTH*PREVIEW_HEIGHT*Menu_factor_X*Menu_factor_Y);
break;
case CONTEXT_MAIN_IMAGE:
memset(
Main.backups->Pages->Image[Main.current_layer].Pixels,
color,
Main.backups->Pages->Width*Main.backups->Pages->Height);
break;
case CONTEXT_BRUSH:
memset(context->Buffer_image, color, (long)context->Height*context->Pitch);
break;
case CONTEXT_SURFACE:
break;
case CONTEXT_PALETTE:
case CONTEXT_PREVIEW_PALETTE:
break;
}
}
/// Chargement des pixels dans le buffer 24b
void Set_pixel_24b(T_IO_Context *context, short x_pos, short y_pos, byte r, byte g, byte b)
{
byte color;
// Clipping
if (x_pos<0 || y_pos<0 || x_pos>=context->Width || y_pos>=context->Height)
return;
switch(context->Type)
{
case CONTEXT_MAIN_IMAGE:
case CONTEXT_BRUSH:
case CONTEXT_SURFACE:
{
int index;
index=(y_pos*context->Width)+x_pos;
context->Buffer_image_24b[index].R=r;
context->Buffer_image_24b[index].G=g;
context->Buffer_image_24b[index].B=b;
}
break;
case CONTEXT_PREVIEW:
if (((x_pos % context->Preview_factor_X)==0) && ((y_pos % context->Preview_factor_Y)==0))
{
color=((r >> 5) << 5) |
((g >> 5) << 2) |
((b >> 6));
// Tag the color as 'used'
context->Preview_usage[color]=1;
context->Preview_bitmap[x_pos/context->Preview_factor_X + (y_pos/context->Preview_factor_Y)*PREVIEW_WIDTH*Menu_factor_X]=color;
}
break;
case CONTEXT_PREVIEW_PALETTE:
case CONTEXT_PALETTE:
// In a palette, there are no pixels!
break;
}
}
// Création d'une palette fake
void Set_palette_fake_24b(T_Palette palette)
{
int color;
// Génération de la palette
for (color=0;color<256;color++)
{
palette[color].R=((color & 0xE0)>>5)<<5;
palette[color].G=((color & 0x1C)>>2)<<5;
palette[color].B=((color & 0x03)>>0)<<6;
}
}
void Set_frame_duration(T_IO_Context *context, int duration)
{
switch(context->Type)
{
case CONTEXT_MAIN_IMAGE:
Main.backups->Pages->Image[context->Current_layer].Duration = duration;
break;
default:
break;
}
}
int Get_frame_duration(T_IO_Context *context)
{
switch(context->Type)
{
case CONTEXT_MAIN_IMAGE:
return Main.backups->Pages->Image[context->Current_layer].Duration;
default:
return 0;
}
}
void Set_image_mode(T_IO_Context *context, enum IMAGE_MODES mode)
{
if (context->Type == CONTEXT_MAIN_IMAGE)
{
Main.backups->Pages->Image_mode = mode;
//Switch_layer_mode(mode);
Update_screen_targets();
if (mode > IMAGE_MODE_ANIMATION)
Selected_Constraint_Mode = mode;
// update the "FX" button state
Draw_menu_button(BUTTON_EFFECTS,Any_effect_active());
}
}
enum IMAGE_MODES Get_image_mode(T_IO_Context *context)
{
if (context->Type == CONTEXT_MAIN_IMAGE)
return Main.backups->Pages->Image_mode;
return IMAGE_MODE_LAYERED;
}
///
/// Generic allocation and similar stuff, done at beginning of image load,
/// as soon as size is known.
void Pre_load(T_IO_Context *context, short width, short height, long file_size, int format, enum PIXEL_RATIO ratio, byte bpp)
{
char str[10];
byte truecolor;
if (width < 0 || width > 9999 || height < 0 || height > 9999)
{
File_error = 1;
return;
}
if (bpp == 0)
bpp = 8; // default to 8bits
truecolor = (bpp > 8) ? 1 : 0;
context->bpp = bpp;
context->Pitch = width; // default
context->Width = width;
context->Height = height;
context->Ratio = ratio;
context->Nb_layers = 1;
context->Transparent_color=0;
context->Background_transparent=0;
switch(context->Type)
{
// Preview
case CONTEXT_PREVIEW:
// Préparation du chargement d'une preview:
context->Preview_bitmap=calloc(1, PREVIEW_WIDTH*PREVIEW_HEIGHT*Menu_factor_X*Menu_factor_Y);
if (!context->Preview_bitmap)
File_error=1;
// Affichage des données "Image size:"
memcpy(str, "VERY BIG!", 10); // default string
if (context->Original_width != 0)
{
if (context->Original_width < 10000 && context->Original_height < 10000)
snprintf(str, sizeof(str), "%4hux%4hu", context->Original_width, context->Original_height);
}
else if ((width<10000) && (height<10000))
{
snprintf(str, sizeof(str), "%4hux%4hu", width, height);
}
Print_in_window(101,59,str,MC_Black,MC_Light);
snprintf(str, sizeof(str), "%2dbpp", bpp);
Print_in_window(181,59,str,MC_Black,MC_Light);
// Affichage de la taille du fichier
if (file_size<1048576)
{
// Le fichier fait moins d'un Mega, on affiche sa taille direct
Num2str(file_size,str,7);
}
else if (((file_size+512)/1024)<100000)
{
// Le fichier fait plus d'un Mega, on peut afficher sa taille en Ko
Num2str((file_size+512)/1024,str,5);
strcpy(str+5,"KB");
}
else
{
// Le fichier fait plus de 100 Mega octets (cas très rare :))
memcpy(str,"LARGE!!",8);
}
Print_in_window(236,59,str,MC_Black,MC_Light);
// Affichage du vrai format
Print_in_window( 59,59,Get_fileformat(format)->Label,MC_Black,MC_Light);
// On efface le commentaire précédent
Window_rectangle(45,70,32*8,8,MC_Light);
// Calcul des données nécessaires à l'affichage de la preview:
if (ratio == PIXEL_WIDE &&
Pixel_ratio != PIXEL_WIDE &&
Pixel_ratio != PIXEL_WIDE2)
width*=2;
else if (ratio == PIXEL_TALL &&
Pixel_ratio != PIXEL_TALL &&
Pixel_ratio != PIXEL_TALL2 &&
Pixel_ratio != PIXEL_TALL3)
height*=2;
context->Preview_factor_X=Round_div_max(width,120*Menu_factor_X);
context->Preview_factor_Y=Round_div_max(height, 80*Menu_factor_Y);
if ( (!Config.Maximize_preview) && (context->Preview_factor_X!=context->Preview_factor_Y) )
{
if (context->Preview_factor_X>context->Preview_factor_Y)
context->Preview_factor_Y=context->Preview_factor_X;
else
context->Preview_factor_X=context->Preview_factor_Y;
}
context->Preview_pos_X=Window_pos_X+183*Menu_factor_X;
context->Preview_pos_Y=Window_pos_Y+ 95*Menu_factor_Y;
// On nettoie la zone où va s'afficher la preview:
Window_rectangle(183,95,PREVIEW_WIDTH,PREVIEW_HEIGHT,MC_Light);
// Un update pour couvrir les 4 zones: 3 libellés plus le commentaire
Update_window_area(45,48,256,30);
// Zone de preview
Update_window_area(183,95,PREVIEW_WIDTH,PREVIEW_HEIGHT);
break;
// Other loading
case CONTEXT_MAIN_IMAGE:
if (Backup_new_image(1,width,height))
{
// La nouvelle page a pu être allouée, elle est pour l'instant pleine
// de 0s. Elle fait Main_image_width de large.
// Normalement tout va bien, tout est sous contrôle...
// Load into layer 0, by default.
context->Nb_layers=1;
Main.current_layer=0;
Main.layers_visible=1<<0;
Set_loading_layer(context,0);
// Remove previous comment, unless we load just a palette
if (! Get_fileformat(context->Format)->Palette_only)
context->Comment[0]='\0';
}
else
{
// Afficher un message d'erreur
// Pour être sûr que ce soit lisible.
Compute_optimal_menu_colors(context->Palette);
Message_out_of_memory();
File_error=1; // 1 => On n'a pas perdu l'image courante
}
break;
case CONTEXT_BRUSH:
context->Buffer_image = (byte *)GFX2_malloc(width*height);
if (! context->Buffer_image)
{
File_error=3;
return;
}
context->Target_address=context->Buffer_image;
break;
case CONTEXT_SURFACE:
context->Surface = New_GFX2_Surface(width, height);
if (! context->Surface)
{
File_error=1;
return;
}
//context->Pitch = context->Surface->pitch;
//context->Target_address = context->Surface->pixels;
break;
case CONTEXT_PALETTE:
case CONTEXT_PREVIEW_PALETTE:
// In a palette, there are no pixels!
break;
}
if (File_error)
return;
// Extra process for truecolor images
if (truecolor)
{
switch(context->Type)
{
case CONTEXT_MAIN_IMAGE:
case CONTEXT_BRUSH:
case CONTEXT_SURFACE:
// Allocate 24bit buffer
context->Buffer_image_24b =
(T_Components *)GFX2_malloc(width*height*sizeof(T_Components));
if (!context->Buffer_image_24b)
{
// Print an error message
// The following is to be sure the message is readable
Compute_optimal_menu_colors(context->Palette);
Message_out_of_memory();
File_error=1;
}
break;
case CONTEXT_PREVIEW:
// 3:3:2 "True Color" palette will be loaded lated in context->Palette
// There can be an image palette used to decode the true color picture
// Such as for HAM ILBM pictures
break;
case CONTEXT_PALETTE:
case CONTEXT_PREVIEW_PALETTE:
// In a palette, there are no pixels!
break;
}
}
}
/////////////////////////////////////////////////////////////////////////////
// -- Charger n'importe connu quel type de fichier d'image (ou palette) -----
void Load_image(T_IO_Context *context)
{
unsigned int index; // index de balayage des formats
const T_Format *format = &(File_formats[FORMAT_ALL_FILES+1]); // Format du fichier à charger
int i;
byte old_cursor_shape;
FILE * f;
// Not sure it's the best place...
context->Color_cycles=0;
// On place par défaut File_error à vrai au cas où on ne sache pas
// charger le format du fichier:
File_error=1;
if (context->Format == FORMAT_CLIPBOARD)
{
Load_ClipBoard_Image(context);
if (File_error != 0 && context->Format == FORMAT_CLIPBOARD)
{
Error(0);
return;
}
}
if (context->Format != FORMAT_CLIPBOARD)
{
if (context->File_name == NULL || context->File_directory == NULL)
{
GFX2_Log(GFX2_ERROR, "Load_Image() called with NULL file name or directory\n");
Error(0);
return;
}
f = Open_file_read(context);
if (f == NULL)
{
GFX2_Log(GFX2_WARNING, "Cannot open file for reading\n");
Error(0);
return;
}
if (context->Format > FORMAT_ALL_FILES)
{
format = Get_fileformat(context->Format);
if (format->Test)
format->Test(context, f);
}
if (File_error)
{
// Sinon, on va devoir scanner les différents formats qu'on connait pour
// savoir à quel format est le fichier:
for (index=0; index < Nb_known_formats(); index++)
{
format = Get_fileformat(index);
// Loadable format
if (format->Test == NULL)
continue;
fseek(f, 0, SEEK_SET); // rewind
// On appelle le testeur du format:
format->Test(context, f);
// On s'arrête si le fichier est au bon format:
if (File_error==0)
break;
}
}
fclose(f);
if (File_error)
{
context->Format = DEFAULT_FILEFORMAT;
// try with recoil
#ifndef NORECOIL
Load_Recoil_Image(context);
if (File_error)
#endif
#if defined(USE_SDL) || defined(USE_SDL2)
{
// Last try: with SDL_image
Load_SDL_Image(context);
}
if (File_error)
#endif
{
// Sinon, l'appelant sera au courant de l'échec grace à File_error;
// et si on s'apprêtait à faire un chargement définitif de l'image (pas
// une preview), alors on flash l'utilisateur.
//if (Pixel_load_function!=Pixel_load_in_preview)
// Error(0);
return;
}
}
else
// Si on a su déterminer avec succès le format du fichier:
{
context->Format = format->Identifier;
// On peut charger le fichier:
// Dans certains cas il est possible que le chargement plante
// après avoir modifié la palette. TODO
format->Load(context);
}
if (File_error>0)
{
GFX2_Log(GFX2_WARNING, "Unable to load file %s (error %d)! format:%s\n", context->File_name, File_error, format->Label);
if (context->Type!=CONTEXT_SURFACE)
Error(0);
}
}
// Post-load
if (context->Buffer_image_24b)
{
// On vient de charger une image 24b
if (!File_error)
{
switch(context->Type)
{
case CONTEXT_MAIN_IMAGE:
// Cas d'un chargement dans l'image
old_cursor_shape=Cursor_shape;
Hide_cursor();
Cursor_shape=CURSOR_SHAPE_HOURGLASS;
Display_cursor();
Flush_update();
if (Convert_24b_bitmap_to_256(Main.backups->Pages->Image[0].Pixels,context->Buffer_image_24b,context->Width,context->Height,context->Palette))
File_error=2;
Hide_cursor();
Cursor_shape=old_cursor_shape;
Display_cursor();
break;
case CONTEXT_BRUSH:
// Cas d'un chargement dans la brosse
old_cursor_shape=Cursor_shape;
Hide_cursor();
Cursor_shape=CURSOR_SHAPE_HOURGLASS;
Display_cursor();
Flush_update();
if (Convert_24b_bitmap_to_256(context->Buffer_image,context->Buffer_image_24b,context->Width,context->Height,context->Palette))
File_error=2;
Hide_cursor();
Cursor_shape=old_cursor_shape;
Display_cursor();
break;
case CONTEXT_PREVIEW:
// in this context, context->Buffer_image_24b is not allocated
// pixels are drawn to context->Preview_bitmap
break;
case CONTEXT_SURFACE:
if (Convert_24b_bitmap_to_256(context->Surface->pixels,context->Buffer_image_24b,context->Width,context->Height,context->Palette))
File_error=1;
break;
case CONTEXT_PALETTE:
case CONTEXT_PREVIEW_PALETTE:
// In a palette, there are no pixels!
break;
}
}
free(context->Buffer_image_24b);
context->Buffer_image_24b = NULL;
}
else if (context->Type == CONTEXT_MAIN_IMAGE)
{
// Non-24b main image: Add menu colors
if (Config.Safety_colors)
{
dword color_usage[256];
memset(color_usage,0,sizeof(color_usage));
if (Count_used_colors(color_usage)<252)
{
int gui_index;
// From white to black
for (gui_index=3; gui_index>=0; gui_index--)
{
int c;
T_Components gui_color;
gui_color=*Favorite_GUI_color(gui_index);
// Try find a very close match (ignore last 2 bits)
for (c=255; c>=0; c--)
{
if ((context->Palette[c].R|3) == (gui_color.R|3)
&& (context->Palette[c].G|3) == (gui_color.G|3)
&& (context->Palette[c].B|3) == (gui_color.B|3) )
break;
}
if (c<0) // Not found
{
// Find an unused slot at end of palette
for (c=255; c>=0; c--)
{
if (color_usage[c]==0)
{
context->Palette[c]=gui_color;
// Tag as a used color
color_usage[c]=1;
break;
}
}
}
}
}
}
}
if (context->Type == CONTEXT_MAIN_IMAGE)
{
if ( File_error!=1)
{
Set_palette(context->Palette);
if (format->Palette_only)
{
// Make a backup step
Backup_layers(LAYER_NONE);
}
// Copy the loaded palette
memcpy(Main.palette, context->Palette, sizeof(T_Palette));
memcpy(Main.backups->Pages->Palette, context->Palette, sizeof(T_Palette));
// For formats that handle more than just the palette:
// Transfer the data to main image.
if (!format->Palette_only)
{
free(Main.backups->Pages->Filename);
free(Main.backups->Pages->File_directory);
free(Main.backups->Pages->Filename_unicode);
Main.backups->Pages->Filename_unicode = NULL;
if (context->Original_file_name != NULL
&& context->Original_file_directory != NULL)
{
Main.backups->Pages->Filename = context->Original_file_name; // steal buffer !
context->Original_file_name = NULL;
Main.backups->Pages->File_directory = context->Original_file_directory; // steal heap buffer
context->Original_file_directory = NULL;
}
else
{
Main.backups->Pages->Filename = strdup(context->File_name);
Main.backups->Pages->File_directory = strdup(context->File_directory);
if (context->File_name_unicode)
Main.backups->Pages->Filename_unicode = Unicode_strdup(context->File_name_unicode);
}
// On considère que l'image chargée n'est plus modifiée
Main.image_is_modified=0;
// Et on documente la variable Main_fileformat avec la valeur:
Main.fileformat=format->Identifier;
// already done initially on Backup_with_new_dimensions
//Main_image_width= context->Width;
//Main_image_height= context->Height;
if (Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
{
Main.current_layer = 0;
}
else
{
Main.current_layer = context->Nb_layers - 1;
Main.layers_visible = (2<Pages->Transparent_color = context->Transparent_color;
Main.backups->Pages->Background_transparent = context->Background_transparent;
// Correction des dimensions
if (Main.image_width<1)
Main.image_width=1;
if (Main.image_height<1)
Main.image_height=1;
// Color cyling ranges:
for (i=0; i<16; i++)
Main.backups->Pages->Gradients->Range[i].Speed=0;
for (i=0; iColor_cycles; i++)
{
Main.backups->Pages->Gradients->Range[i].Start=context->Cycle_range[i].Start;
Main.backups->Pages->Gradients->Range[i].End=context->Cycle_range[i].End;
Main.backups->Pages->Gradients->Range[i].Inverse=context->Cycle_range[i].Inverse;
Main.backups->Pages->Gradients->Range[i].Speed=context->Cycle_range[i].Speed;
}
// Comment
strcpy(Main.backups->Pages->Comment, context->Comment);
}
}
else if (File_error!=1)
{
// On considère que l'image chargée est encore modifiée
Main.image_is_modified=1;
// Et on documente la variable Main_fileformat avec la valeur:
Main.fileformat=format->Identifier;
}
else
{
// Dans ce cas, on sait que l'image n'a pas changé, mais ses
// paramètres (dimension, palette, ...) si. Donc on les restaures.
Download_infos_page_main(Main.backups->Pages);
}
}
else if (context->Type == CONTEXT_PALETTE)
{
if ( File_error!=1)
{
Set_palette(context->Palette);
// Make a backup step
Backup_layers(LAYER_NONE);
// Copy the loaded palette
memcpy(Main.palette, context->Palette, sizeof(T_Palette));
memcpy(Main.backups->Pages->Palette, context->Palette, sizeof(T_Palette));
}
}
else if (context->Type == CONTEXT_BRUSH && File_error==0)
{
// For brushes, Back_color is the transparent color.
// Set it before remapping, Remap_brush() will take care of it
if (context->Background_transparent)
Back_color = context->Transparent_color;
if (Realloc_brush(context->Width, context->Height, context->Buffer_image, NULL))
{
File_error=3;
free(context->Buffer_image);
}
memcpy(Brush_original_palette, context->Palette, sizeof(T_Palette));
Remap_brush();
context->Buffer_image = NULL;
}
else if (context->Type == CONTEXT_SURFACE)
{
if (File_error == 0)
{
// Copy the palette
memcpy(context->Surface->palette, context->Palette, sizeof(T_Palette));
}
}
else if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE
/*&& !context->Buffer_image_24b*/
/*&& !Get_fileformat(context->Format)->Palette_only*/)
{
// Try to adapt the palette to accomodate the GUI.
int c;
int count_unused;
byte unused_color[4];
if (context->Type == CONTEXT_PREVIEW && context->bpp > 8)
Set_palette_fake_24b(context->Palette);
count_unused=0;
// Try find 4 unused colors and insert good colors there
for (c=255; c>=0 && count_unused<4; c--)
{
if (!context->Preview_usage[c])
{
unused_color[count_unused]=c;
count_unused++;
}
}
// Found! replace them with some favorites
if (count_unused==4)
{
int gui_index;
for (gui_index=0; gui_index<4; gui_index++)
{
context->Palette[unused_color[gui_index]]=*Favorite_GUI_color(gui_index);
}
}
// All preview display is here
// Update palette and screen first
Compute_optimal_menu_colors(context->Palette);
Remap_screen_after_menu_colors_change();
Set_palette(context->Palette);
// Display palette preview
if (Get_fileformat(context->Format)->Palette_only
|| context->Type == CONTEXT_PREVIEW_PALETTE)
{
short index;
if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE)
for (index=0; index<256; index++)
Window_rectangle(183+(index/16)*7,95+(index&15)*5,5,5,index);
}
// Display normal image
else if (context->Preview_bitmap)
{
int x_pos,y_pos;
int width,height;
width=context->Width/context->Preview_factor_X;
height=context->Height/context->Preview_factor_Y;
if (context->Ratio == PIXEL_WIDE &&
Pixel_ratio != PIXEL_WIDE &&
Pixel_ratio != PIXEL_WIDE2)
width*=2;
else if (context->Ratio == PIXEL_TALL &&
Pixel_ratio != PIXEL_TALL &&
Pixel_ratio != PIXEL_TALL2 &&
Pixel_ratio != PIXEL_TALL3)
height*=2;
for (y_pos=0; y_posPreview_bitmap[x_pos+y_pos*PREVIEW_WIDTH*Menu_factor_X];
// Skip transparent if image has transparent background.
if (color == context->Transparent_color && context->Background_transparent)
color=MC_Window;
Pixel(context->Preview_pos_X+x_pos,
context->Preview_pos_Y+y_pos,
color);
}
}
// Refresh modified part
Update_window_area(183,95,PREVIEW_WIDTH,PREVIEW_HEIGHT);
// Preview comment
Print_in_window(45,70,context->Comment,MC_Black,MC_Light);
//Update_window_area(45,70,32*8,8);
}
}
// -- Sauver n'importe quel type connu de fichier d'image (ou palette) ------
void Save_image(T_IO_Context *context)
{
const T_Format *format;
// On place par défaut File_error à vrai au cas où on ne sache pas
// sauver le format du fichier: (Est-ce vraiment utile??? Je ne crois pas!)
File_error=1;
format = Get_fileformat(context->Format);
switch (context->Type)
{
case CONTEXT_MAIN_IMAGE:
if ((context->Format == FORMAT_CLIPBOARD || !format->Supports_layers)
&& (Main.backups->Pages->Nb_layers > 1)
&& (!format->Palette_only))
{
if (Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION)
{
if (! Confirmation_box("This format doesn't support\nanimation and will save only\ncurrent frame. Proceed?"))
{
// File_error is already set to 1.
return;
}
// current layer
context->Nb_layers=1;
context->Target_address=Main.backups->Pages->Image[Main.current_layer].Pixels;
}
else // all other layer-based formats
{
int clicked_button;
Open_window(208,100,"Format warning");
Print_in_window( 8, 20,"This file format doesn't",MC_Black,MC_Light);
Print_in_window( 8, 30,"support layers.",MC_Black,MC_Light);
Window_set_normal_button(23,44, 162,14,"Save flattened copy",0,1,KEY_NONE); // 1
Window_set_normal_button(23,62, 162,14,"Save current frame" ,0,1,KEY_NONE); // 2
Window_set_normal_button(23,80, 162,14,"Cancel" ,0,1,KEY_ESC); // 3
Update_window_area(0,0,Window_width, Window_height);
Display_cursor();
do
{
clicked_button=Window_clicked_button();
// Some help on file formats ?
//if (Is_shortcut(Key,0x100+BUTTON_HELP))
//{
// Key=0;
// Window_help(???, NULL);
//}
}
while (clicked_button<=0);
Close_window();
Display_cursor();
switch(clicked_button)
{
case 1: // flatten
context->Nb_layers=1;
context->Target_address=Main.visible_image.Image;
break;
case 2: // current layer
context->Nb_layers=1;
context->Target_address=Main.backups->Pages->Image[Main.current_layer].Pixels;
break;
default: // Cancel
// File_error is already set to 1.
return;
}
}
}
break;
case CONTEXT_BRUSH:
break;
case CONTEXT_PREVIEW:
case CONTEXT_PREVIEW_PALETTE:
break;
case CONTEXT_SURFACE:
break;
case CONTEXT_PALETTE:
// In a palette, there are no pixels!
break;
}
if (context->Format == FORMAT_CLIPBOARD)
Save_ClipBoard_Image(context);
else
{
if (format->Save)
format->Save(context);
}
if (File_error)
{
Error(0);
return;
}
}
#if defined(USE_SDL) || defined(USE_SDL2)
void Load_SDL_Image(T_IO_Context *context)
{
char * filename; // full path
word x_pos,y_pos;
// long file_size;
dword pixel;
long file_size;
SDL_Surface * surface;
filename = Filepath_append_to_dir(context->File_directory, context->File_name);
file_size = File_length(filename);
File_error = 0;
surface = IMG_Load(filename);
free(filename);
if (!surface)
{
File_error=1;
return;
}
if (surface->format->BytesPerPixel == 1)
{
// 8bpp image
Pre_load(context, surface->w, surface->h, file_size ,FORMAT_MISC, PIXEL_SIMPLE, 8);
// Read palette
if (surface->format->palette)
{
Get_SDL_Palette(surface->format->palette, context->Palette);
}
for (y_pos=0; y_posHeight; y_pos++)
{
for (x_pos=0; x_posWidth; x_pos++)
{
Set_pixel(context, x_pos, y_pos, Get_SDL_pixel_8(surface, x_pos, y_pos));
}
}
}
else
{
{
// Hi/Trucolor
Pre_load(context, surface->w, surface->h, file_size ,FORMAT_ALL_IMAGES, PIXEL_SIMPLE, 8 * surface->format->BytesPerPixel);
}
for (y_pos=0; y_posHeight; y_pos++)
{
for (x_pos=0; x_posWidth; x_pos++)
{
pixel = Get_SDL_pixel_hicolor(surface, x_pos, y_pos);
Set_pixel_24b(
context,
x_pos,
y_pos,
((pixel & surface->format->Rmask) >> surface->format->Rshift) << surface->format->Rloss,
((pixel & surface->format->Gmask) >> surface->format->Gshift) << surface->format->Gloss,
((pixel & surface->format->Bmask) >> surface->format->Bshift) << surface->format->Bloss);
}
}
}
SDL_FreeSurface(surface);
}
#endif
T_GFX2_Surface * Load_surface(const char *full_name, T_Gradient_array *gradients)
{
T_GFX2_Surface * bmp=NULL;
T_IO_Context context;
Init_context_surface(&context, full_name, "");
Load_image(&context);
if (context.Surface)
{
bmp=context.Surface;
// Caller wants the gradients:
if (gradients != NULL)
{
int i;
memset(gradients, 0, sizeof(T_Gradient_array));
for (i=0; iRange[i].Start=context.Cycle_range[i].Start;
gradients->Range[i].End=context.Cycle_range[i].End;
gradients->Range[i].Inverse=context.Cycle_range[i].Inverse;
gradients->Range[i].Speed=context.Cycle_range[i].Speed;
}
}
}
Destroy_context(&context);
return bmp;
}
static void Load_ClipBoard_Image(T_IO_Context * context)
{
#ifdef WIN32
UINT format;
HANDLE clipboard;
if (!OpenClipboard(GFX2_Get_Window_Handle()))
{
GFX2_Log(GFX2_ERROR, "Failed to open Clipboard\n");
return;
}
format = EnumClipboardFormats(0);
while (format != 0)
{
const char * format_name = NULL;
char format_name_buffer[256];
switch (format)
{
case CF_TEXT:
format_name = "TEXT";
break;
case CF_OEMTEXT:
format_name = "OEMTEXT";
break;
case CF_UNICODETEXT:
format_name = "UNICODE TEXT";
break;
#if(WINVER >= 0x0400)
case CF_LOCALE:
format_name = "Locale identifier";
break;
case CF_HDROP:
format_name = "Drop Handle";
break;
#endif
case CF_DIB:
format_name = "DIB (BITMAPINFO)";
break;
case CF_DIBV5:
format_name = "DIBV5 (BITMAPV5HEADER)";
break;
case CF_BITMAP:
format_name = "HBITMAP";
break;
case CF_METAFILEPICT:
format_name = "METAFILEPICT";
break;
case CF_PALETTE:
format_name = "Palette";
break;
case CF_TIFF:
format_name = "TIFF";
break;
case CF_ENHMETAFILE:
format_name = "HENMETAFILE";
break;
default:
if (GetClipboardFormatNameA(format, format_name_buffer, sizeof(format_name_buffer)) <= 0)
GFX2_Log(GFX2_WARNING, "Failed to get name for clipboard format %u\n", format);
else
format_name = format_name_buffer;
}
if (format_name != NULL)
GFX2_Log(GFX2_DEBUG, "Available format %5u \"%s\"\n", format, format_name);
format = EnumClipboardFormats(format); // get next format
}
clipboard = GetClipboardData(CF_DIB);
if (clipboard == NULL)
{
// Try to load the filename
UINT filename_type = RegisterClipboardFormatA("FileName");
GFX2_Log(GFX2_INFO, "Failed to get Clipboard in DIB (BITMAPINFO) format\n");
if (filename_type == 0)
GFX2_Log(GFX2_ERROR, "Failed to register \"FileName\" Clipboard format\n");
else
{
clipboard = GetClipboardData(filename_type);
if (clipboard == NULL)
GFX2_Log(GFX2_INFO, "Failed to get Clipboard in \"FileName\" format\n");
else
{
const char * filename = (const char *)GlobalLock(clipboard);
if (filename == NULL)
GFX2_Log(GFX2_ERROR, "GlobalLock() failed error 0x%08x\n", GetLastError());
else
{
GFX2_Log(GFX2_DEBUG, "filename from clipboard : \"%s\"\n", filename);
if (File_exists(filename))
{
free(context->File_name);
context->File_name = Extract_filename(NULL, filename);
free(context->File_directory);
context->File_directory = Extract_path(NULL, filename);
context->Format = DEFAULT_FILEFORMAT;
}
else
{
GFX2_Log(GFX2_WARNING, "file \"%s\" does not exist\n", filename);
}
GlobalUnlock(clipboard);
}
}
}
}
else
{
// Load the DIB (BITMAPINFO)
const PBITMAPINFO bmi = (PBITMAPINFO)GlobalLock(clipboard);
if (bmi == NULL)
GFX2_Log(GFX2_ERROR, "GlobalLock() failed error 0x%08x\n", GetLastError());
else
{
unsigned long width, height;
width = bmi->bmiHeader.biWidth;
height = (bmi->bmiHeader.biHeight > 0) ? bmi->bmiHeader.biHeight : -bmi->bmiHeader.biHeight;
GFX2_Log(GFX2_DEBUG, "DIB %ldx%ld planes=%u bpp=%u compression=%u size=%u ClrUsed=%u\n",
bmi->bmiHeader.biWidth, bmi->bmiHeader.biHeight,
bmi->bmiHeader.biPlanes, bmi->bmiHeader.biBitCount,
bmi->bmiHeader.biCompression, bmi->bmiHeader.biSizeImage,
bmi->bmiHeader.biClrUsed);
if (width > 9999 || height > 9999)
GFX2_Log(GFX2_INFO, "Image too big : %lux%lu\n", width, height);
else if (bmi->bmiHeader.biCompression == BI_RGB)
{
unsigned i, color_count;
const byte * pixels;
unsigned int x, y;
File_error = 0; // have to be set before calling Pre_load()
Pre_load(context, width, height, bmi->bmiHeader.biSizeImage, FORMAT_CLIPBOARD, PIXEL_SIMPLE, bmi->bmiHeader.biBitCount);
color_count = bmi->bmiHeader.biClrUsed;
if (bmi->bmiHeader.biBitCount <= 8)
{ // get palette
if (color_count == 0)
color_count = 1 << bmi->bmiHeader.biBitCount;
for (i = 0; i < color_count; i++)
{
context->Palette[i].R = bmi->bmiColors[i].rgbRed;
context->Palette[i].G = bmi->bmiColors[i].rgbGreen;
context->Palette[i].B = bmi->bmiColors[i].rgbBlue;
}
}
pixels = (const byte *)(&bmi->bmiColors[color_count]);
switch (bmi->bmiHeader.biBitCount)
{
case 8:
for (y = 0; y < height; y++)
{
const byte * line;
if (bmi->bmiHeader.biHeight > 0)
line = pixels + (height - y - 1) * ((bmi->bmiHeader.biWidth + 3) & ~3);
else
line = pixels + y * ((bmi->bmiHeader.biWidth + 3) & ~3);
for (x = 0; x < width; x++)
Set_pixel(context, x, y, line[x]);
}
break;
case 24:
for (y = 0; y < height; y++)
{
const byte * line = pixels;
if (bmi->bmiHeader.biHeight > 0)
line += (height - y - 1) * ((bmi->bmiHeader.biWidth * 3 + 3) & ~3);
else
line += y * ((bmi->bmiHeader.biWidth * 3 + 3) & ~3);
for (x = 0; x < width; x++)
Set_pixel_24b(context, x, y, line[x*3 + 2], line[x*3 + 1], line[x*3]);
}
break;
case 32:
for (y = 0; y < height; y++)
{
const byte * line;
if (bmi->bmiHeader.biHeight > 0)
line = pixels + (height - y - 1) * bmi->bmiHeader.biWidth * 4;
else
line = pixels + y * bmi->bmiHeader.biWidth * 4;
for (x = 0; x < width; x++)
Set_pixel_24b(context, x, y, line[x*4 + 2], line[x*4 + 1], line[x*4]);
}
break;
default:
GFX2_Log(GFX2_ERROR, "Loading %ubpp pictures from Clipboard is not implemented yet!\n", bmi->bmiHeader.biBitCount);
File_error = 1;
}
}
else if (bmi->bmiHeader.biCompression == BI_BITFIELDS)
{
dword r_mask = *((const dword *)&bmi->bmiColors[0]);
int r_bits = count_set_bits(r_mask);
int r_shift = count_trailing_zeros(r_mask);
dword g_mask = *((const dword *)&bmi->bmiColors[1]);
int g_bits = count_set_bits(g_mask);
int g_shift = count_trailing_zeros(g_mask);
dword b_mask = *((const dword *)&bmi->bmiColors[2]);
int b_bits = count_set_bits(b_mask);
int b_shift = count_trailing_zeros(b_mask);
const byte * pixels = (const byte *)&bmi->bmiColors[3];
unsigned int bytes_per_pixel = (bmi->bmiHeader.biBitCount + 7) >> 3;
int bytes_per_line = (bmi->bmiHeader.biWidth * bytes_per_pixel + 3) & ~3;
unsigned int x, y;
GFX2_Log(GFX2_DEBUG, "RGB%d%d%d, masks : %08x %08x %08x\n", r_bits, g_bits, b_bits, r_mask, g_mask, b_mask);
File_error = 0; // have to be set before calling Pre_load()
Pre_load(context, width, height, bmi->bmiHeader.biSizeImage, FORMAT_CLIPBOARD, PIXEL_SIMPLE, bmi->bmiHeader.biBitCount);
for (y = 0; y < height; y++)
{
const byte * ptr;
if (bmi->bmiHeader.biHeight > 0)
ptr = pixels + (height - y - 1) * bytes_per_line;
else
ptr = pixels + y * bytes_per_line;
for (x = 0; x < width; x++)
{
dword rgb, r, g, b;
switch (bytes_per_pixel)
{
case 1:
rgb = *ptr;
break;
case 2:
rgb = *((const word *)ptr);
break;
case 3:
rgb = ((dword)ptr[2] << 16) | ((dword)ptr[1] << 8) | (dword)ptr[0];
break;
default:
rgb = *((const dword *)ptr);
}
r = (rgb & r_mask) >> r_shift;
r = (r << (8 - r_bits)) | (r >> (2 * r_bits - 8));
g = (rgb & g_mask) >> g_shift;
g = (g << (8 - g_bits)) | (g >> (2 * g_bits - 8));
b = (rgb & b_mask) >> b_shift;
b = (b << (8 - b_bits)) | (b >> (2 * b_bits - 8));
Set_pixel_24b(context, x, y, r, g, b);
ptr += bytes_per_pixel;
}
}
}
else
GFX2_Log(GFX2_INFO, "Unsupported DIB compression %u\n", bmi->bmiHeader.biCompression);
GlobalUnlock(clipboard);
}
}
CloseClipboard();
#elif defined(__macosx__)
unsigned long size;
const void * tiff = get_tiff_paste_board(&size);
GFX2_Log(GFX2_DEBUG, "TIFF pasteboard : %p (%lu bytes)\n", tiff, size);
if (tiff != NULL)
Load_TIFF_from_memory(context, tiff, size);
#elif defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11))
int i;
Atom selection;
Window selection_owner;
#if defined(SDL_VIDEO_DRIVER_X11)
Display * X11_display;
Window X11_window;
int old_wmevent_state;
if (!GFX2_Get_X11_Display_Window(&X11_display, &X11_window))
{
GFX2_Log(GFX2_ERROR, "Failed to get X11 display and window\n");
return;
}
if (X11_display == NULL)
{
#if defined(USE_SDL)
char video_driver_name[32];
GFX2_Log(GFX2_WARNING, "X11 display is NULL. X11 is needed for Copy/Paste. SDL video driver is currently %s\n", SDL_VideoDriverName(video_driver_name, sizeof(video_driver_name)));
#elif defined(USE_SDL2)
GFX2_Log(GFX2_WARNING, "X11 display is NULL. X11 is needed for Copy/Paste. SDL video driver is currently %s\n", SDL_GetCurrentVideoDriver());
#endif
return;
}
#endif
selection = XInternAtom(X11_display, "CLIPBOARD", False);
selection_owner = XGetSelectionOwner(X11_display, selection);
if (selection_owner == None)
{
GFX2_Log(GFX2_INFO, "No owner for the X11 \"CLIPBOARD\" selection\n");
return;
}
#if defined(USE_SDL) || defined(USE_SDL2)
// Enable processing of X11 events
old_wmevent_state = SDL_EventState(SDL_SYSWMEVENT, SDL_QUERY);
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
#endif
// "TARGETS" is a special content type. The selection owner will
// respond with a list of supported type. We will then choose our
// prefered type and ask for it.
// see Handle_SelectionNotify()
// We could ask directly for "image/png" or "image/tiff" but that is
// not sure this is supported by the selection owner.
XConvertSelection(X11_display, selection, XInternAtom(X11_display, "TARGETS", False),
XInternAtom(X11_display, "GFX2_CLIP", False), /* Property */
X11_window, CurrentTime);
// wait for the event to be received. 500ms maximum
for(i = 0; X11_clipboard == NULL && i < 25; i++)
{
Get_input(20);
}
#if defined(USE_SDL) || defined(USE_SDL2)
SDL_EventState(SDL_SYSWMEVENT, old_wmevent_state);
#endif
switch(X11_clipboard_type)
{
case X11_CLIPBOARD_NONE:
GFX2_Log(GFX2_INFO, "Unable to retrieve X11 \"CLIPBOARD\" selection in a supported format. X11_clipboard=%p\n", X11_clipboard);
break;
#ifndef __no_pnglib__
case X11_CLIPBOARD_PNG:
if (png_sig_cmp((byte *)X11_clipboard, 0, 8) == 0)
{
File_error = 0;
Load_PNG_Sub(context, NULL, X11_clipboard, X11_clipboard_size);
}
else
GFX2_Log(GFX2_WARNING, "Failed to load PNG Clipboard\n");
break;
#endif
#ifndef __no_tifflib__
case X11_CLIPBOARD_TIFF:
Load_TIFF_from_memory(context, X11_clipboard, X11_clipboard_size);
if (File_error != 0)
GFX2_Log(GFX2_WARNING, "Failed to load TIFF Clipboard\n");
break;
#endif
case X11_CLIPBOARD_UTF8STRING:
case X11_CLIPBOARD_URILIST:
{
char * tmp_path = NULL;
char * p;
p = strchr(X11_clipboard, '\r');
if (p != NULL)
*p = '\0';
p = strchr(X11_clipboard, '\n');
if (p != NULL)
*p = '\0';
p = X11_clipboard;
if (strncmp(p, "file://", 7) == 0)
{
tmp_path = GFX2_malloc(strlen(p) + 1);
if (tmp_path == NULL)
break;
p += 7;
for (i = 0; *p; i++)
{
// URLdecode
if (p[0] == '%' && p[1] && p[2])
{
p++;
tmp_path[i] = ((*p >= 'A' && *p <= 'F') ? *p - 'A' + 10 : *p - '0') << 4;
p++;
tmp_path[i] += ((*p >= 'A' && *p <= 'F') ? *p - 'A' + 10 : *p - '0');
}
else
tmp_path[i] = *p;
p++;
}
tmp_path[i] = '\0';
p = tmp_path;
}
if (File_exists(p))
{
free(context->File_name);
context->File_name = Extract_filename(NULL, p);
free(context->File_directory);
context->File_directory = Extract_path(NULL, p);
context->Format = DEFAULT_FILEFORMAT;
}
else
{
GFX2_Log(GFX2_WARNING, "not a filename : \"%s\"\n", p);
}
free(tmp_path);
}
break;
default:
GFX2_Log(GFX2_WARNING, "Unsupported Clipboard format %d\n", (int)X11_clipboard_type);
}
free(X11_clipboard);
X11_clipboard = NULL;
X11_clipboard_size = 0;
X11_clipboard_type = X11_CLIPBOARD_NONE;
#else
(void)context;
GFX2_Log(GFX2_ERROR, "Load_ClipBoard_Image() not implemented on this platform yet\n");
File_error = 1;
#endif
}
static void Save_ClipBoard_Image(T_IO_Context * context)
{
#ifdef WIN32
if (!OpenClipboard(GFX2_Get_Window_Handle()))
{
GFX2_Log(GFX2_ERROR, "Failed to open Clipboard\n");
return;
}
if (!EmptyClipboard())
GFX2_Log(GFX2_ERROR, "EmptyClipboard() failed error 0x%08x\n", GetLastError());
else
{
int line_width = (context->Width + 3) & ~3;
HGLOBAL clipboard = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD) + line_width * context->Height);
if (clipboard == NULL)
GFX2_Log(GFX2_ERROR, "GlobalAlloc() failed error 0x%08x\n", GetLastError());
else
{
PBITMAPINFO bmi = (PBITMAPINFO)GlobalLock(clipboard);
if (bmi == NULL)
GFX2_Log(GFX2_ERROR, "GlobalLock() failed error 0x%08x\n", GetLastError());
else
{
byte * pixels;
unsigned int i;
int x, y;
bmi->bmiHeader.biSize = sizeof(bmi->bmiHeader);
bmi->bmiHeader.biWidth = context->Width;
bmi->bmiHeader.biHeight = context->Height;
bmi->bmiHeader.biPlanes = 1;
bmi->bmiHeader.biBitCount = 8;
bmi->bmiHeader.biCompression = BI_RGB;
bmi->bmiHeader.biSizeImage = context->Height * line_width;
//bmi->bmiHeader.biXPelsPerMeter;
//bmi->bmiHeader.biYPelsPerMeter;
for (i = 0; i < 256; i++)
{
bmi->bmiColors[i].rgbRed = context->Palette[i].R;
bmi->bmiColors[i].rgbGreen = context->Palette[i].G;
bmi->bmiColors[i].rgbBlue = context->Palette[i].B;
bmi->bmiColors[i].rgbReserved = 0;
}
pixels = (byte *)&bmi->bmiColors[256] + bmi->bmiHeader.biSizeImage;
for (y = 0; y < context->Height; y++)
{
pixels -= line_width;
for (x = 0; x < context->Width; x++)
pixels[x] = Get_pixel(context, x, y);
}
GlobalUnlock(clipboard);
if (SetClipboardData(CF_DIB, clipboard) == NULL)
GFX2_Log(GFX2_ERROR, "SetClipboardData() failed error 0x%08x\n", GetLastError());
else
File_error = 0;
}
}
}
CloseClipboard();
#elif defined(__macosx__)
void * tiff = NULL;
unsigned long size = 0;
Save_TIFF_to_memory(context, &tiff, &size);
if (File_error == 0 && tiff != NULL)
{
if(!set_tiff_paste_board(tiff, size))
File_error = 1;
}
free(tiff);
#elif defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11))
Atom selection;
#if defined(SDL_VIDEO_DRIVER_X11)
Display * X11_display;
Window X11_window;
//int old_wmevent_state;
if (!GFX2_Get_X11_Display_Window(&X11_display, &X11_window))
{
GFX2_Log(GFX2_ERROR, "Failed to get X11 display and window\n");
return;
}
if (X11_display == NULL)
{
#if defined(USE_SDL)
char video_driver_name[32];
GFX2_Log(GFX2_WARNING, "X11 display is NULL. X11 is needed for Copy/Paste. SDL video driver is currently %s\n", SDL_VideoDriverName(video_driver_name, sizeof(video_driver_name)));
#elif defined(USE_SDL2)
GFX2_Log(GFX2_WARNING, "X11 display is NULL. X11 is needed for Copy/Paste. SDL video driver is currently %s\n", SDL_GetCurrentVideoDriver());
#endif
return;
}
#endif
File_error = 0;
if (X11_clipboard != NULL)
{
free(X11_clipboard);
X11_clipboard = NULL;
X11_clipboard_size = 0;
}
Save_PNG_Sub(context, NULL, &X11_clipboard, &X11_clipboard_size);
if (!File_error)
{
#if defined(USE_SDL) || defined(USE_SDL2)
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
#endif
selection = XInternAtom(X11_display, "CLIPBOARD", False);
XSetSelectionOwner(X11_display, selection, X11_window, CurrentTime);
}
#else
(void)context;
GFX2_Log(GFX2_ERROR, "Save_ClipBoard_Image() not implemented on this platform yet\n");
File_error = 1;
#endif
}
/// Saves an image.
/// This routine will only be called when all hope is lost, memory thrashed, etc
/// It's the last chance to save anything, but the code has to be extremely
/// careful, anything could happen.
/// The chosen format is IMG since it's extremely simple, difficult to make it
/// create an unusable image.
void Emergency_backup(const char *fname, byte *source, int width, int height, T_Palette *palette)
{
char * filename; // Full path name
FILE *file;
short x_pos,y_pos;
T_IMG_Header IMG_header;
if (width == 0 || height == 0 || source == NULL)
return;
// Open the file
filename = Filepath_append_to_dir(Config_directory, fname);
file = fopen(filename,"wb");
free(filename);
if (!file)
return;
memcpy(IMG_header.Filler1,"\x01\x00\x47\x12\x6D\xB0",6);
memset(IMG_header.Filler2,0,118);
IMG_header.Filler2[4]=0xFF;
IMG_header.Filler2[22]=64; // Lo(Longueur de la signature)
IMG_header.Filler2[23]=0; // Hi(Longueur de la signature)
memcpy(IMG_header.Filler2+23,"GRAFX2 by SunsetDesign (IMG format taken from PV (c)W.Wiedmann)",64);
if (!Write_bytes(file,IMG_header.Filler1,6) ||
!Write_word_le(file,width) ||
!Write_word_le(file,height) ||
!Write_bytes(file,IMG_header.Filler2,118) ||
!Write_bytes(file,palette,sizeof(T_Palette)))
{
fclose(file);
return;
}
for (y_pos=0; ((y_posPages && Main.backups->Pages->Nb_layers == 1)
Emergency_backup(SAFETYBACKUP_PREFIX_A "999999" BACKUP_FILE_EXTENSION,Main_screen, Main.image_width, Main.image_height, &Main.palette);
if (Spare.backups && Spare.backups->Pages && Spare.backups->Pages->Nb_layers == 1)
Emergency_backup(SAFETYBACKUP_PREFIX_B "999999" BACKUP_FILE_EXTENSION,Spare.visible_image.Image, Spare.image_width, Spare.image_height, &Spare.palette);
}
const T_Format * Get_fileformat(byte format)
{
unsigned int i;
const T_Format * safe_default = File_formats;
for (i=0; i < Nb_known_formats(); i++)
{
if (File_formats[i].Identifier == format)
return &(File_formats[i]);
if (File_formats[i].Identifier == FORMAT_GIF)
safe_default=&(File_formats[i]);
}
// Normally impossible to reach this point, unless called with an invalid
// enum....
return safe_default;
}
/// Query the color of a pixel (to save)
byte Get_pixel(T_IO_Context *context, short x, short y)
{
return *(context->Target_address + y*context->Pitch + x);
}
/// Cleans up resources
void Destroy_context(T_IO_Context *context)
{
free(context->Buffer_image_24b);
free(context->Buffer_image);
free(context->Preview_bitmap);
free(context->File_name);
free(context->File_name_unicode);
free(context->File_directory);
free(context->Original_file_directory);
free(context->Original_file_name);
memset(context, 0, sizeof(T_IO_Context));
}
/// Setup for loading a preview in fileselector
void Init_context_preview(T_IO_Context * context, const char *file_name, const char *file_directory)
{
memset(context, 0, sizeof(T_IO_Context));
context->Type = CONTEXT_PREVIEW;
context->File_name = file_name != NULL ? strdup(file_name) : NULL;
context->File_directory = file_directory != NULL ? strdup(file_directory) : NULL;
}
// Setup for loading/saving an intermediate backup
void Init_context_backup_image(T_IO_Context * context, const char *file_name, const char *file_directory)
{
Init_context_layered_image(context, file_name, file_directory);
}
/// Setup for loading/saving the current main image
void Init_context_layered_image(T_IO_Context * context, const char *file_name, const char *file_directory)
{
int i;
memset(context, 0, sizeof(T_IO_Context));
context->Type = CONTEXT_MAIN_IMAGE;
context->File_name = file_name != NULL ? strdup(file_name) : NULL;
context->File_directory = file_directory != NULL ? strdup(file_directory) : NULL;
context->Format = Main.fileformat;
memcpy(context->Palette, Main.palette, sizeof(T_Palette));
context->Width = Main.image_width;
context->Height = Main.image_height;
context->Nb_layers = Main.backups->Pages->Nb_layers;
strcpy(context->Comment, Main.backups->Pages->Comment);
context->Transparent_color=Main.backups->Pages->Transparent_color;
context->Background_transparent=Main.backups->Pages->Background_transparent;
context->Ratio = Pixel_ratio;
context->Target_address=Main.backups->Pages->Image[0].Pixels;
context->Pitch=Main.image_width;
// Color cyling ranges:
for (i=0; i<16; i++)
{
if (Main.backups->Pages->Gradients->Range[i].Start!=Main.backups->Pages->Gradients->Range[i].End)
{
context->Cycle_range[context->Color_cycles].Start=Main.backups->Pages->Gradients->Range[i].Start;
context->Cycle_range[context->Color_cycles].End=Main.backups->Pages->Gradients->Range[i].End;
context->Cycle_range[context->Color_cycles].Inverse=Main.backups->Pages->Gradients->Range[i].Inverse;
context->Cycle_range[context->Color_cycles].Speed=Main.backups->Pages->Gradients->Range[i].Speed;
context->Color_cycles++;
}
}
}
/// Setup for loading/saving the flattened version of current main image
//void Init_context_flat_image(T_IO_Context * context, char *file_name, char *file_directory)
//{
//}
/// Setup for loading/saving the user's brush
void Init_context_brush(T_IO_Context * context, const char *file_name, const char *file_directory)
{
memset(context, 0, sizeof(T_IO_Context));
context->Type = CONTEXT_BRUSH;
context->File_name = file_name != NULL ? strdup(file_name) : NULL;
context->File_directory = file_directory != NULL ? strdup(file_directory) : NULL;
context->Format = Brush_fileformat;
// Use main screen's palette
memcpy(context->Palette, Main.palette, sizeof(T_Palette));
context->Width = Brush_width;
context->Height = Brush_height;
context->Nb_layers = 1;
context->Transparent_color=Back_color; // Transparent color for brushes
context->Background_transparent=1;
context->Ratio=PIXEL_SIMPLE;
context->Target_address=Brush;
context->Pitch=Brush_width;
}
// Setup for loading an image into a new GFX2 surface.
void Init_context_surface(T_IO_Context * context, const char *file_name, const char *file_directory)
{
memset(context, 0, sizeof(T_IO_Context));
context->Type = CONTEXT_SURFACE;
context->File_name = file_name != NULL ? strdup(file_name) : NULL;
context->File_directory = file_directory != NULL ? strdup(file_directory) : NULL;
context->Format = DEFAULT_FILEFORMAT;
// context->Palette
// context->Width
// context->Height
context->Nb_layers = 1;
context->Transparent_color=0;
context->Background_transparent=0;
context->Ratio=PIXEL_SIMPLE;
//context->Target_address
//context->Pitch
}
/// Function to call when need to switch layers.
void Set_saving_layer(T_IO_Context *context, int layer)
{
context->Current_layer = layer;
if (context->Type == CONTEXT_MAIN_IMAGE)
{
if (context->Nb_layers==1 && Main.backups->Pages->Nb_layers!=1)
{
// Context is set to saving a single layer: do nothing
}
else
{
context->Target_address=Main.backups->Pages->Image[layer].Pixels;
}
}
}
/// Function to call when need to switch layers.
void Set_loading_layer(T_IO_Context *context, int layer)
{
context->Current_layer = layer;
if (context->Type == CONTEXT_MAIN_IMAGE)
{
// This awful thing is the part that happens on load
while (layer >= context->Nb_layers)
{
if (Add_layer(Main.backups, layer))
{
// Failure to add a layer on load:
// Position on last layer
layer = context->Nb_layers-1;
break;
}
context->Nb_layers = Main.backups->Pages->Nb_layers;
Main.layers_visible = (2<Target_address=Main.backups->Pages->Image[layer].Pixels;
Update_pixel_renderer();
}
}
// ============================================
// Safety backups
// ============================================
typedef struct T_String_list
{
char * String;
struct T_String_list * Next;
} T_String_list;
/// A list of files, used for scanning a directory
T_String_list *Backups_main = NULL;
/// A list of files, used for scanning a directory
T_String_list *Backups_spare = NULL;
// Settings for safety backup (frequency, numbers, etc)
const int Rotation_safety_backup = 8;
const int Min_interval_for_safety_backup = 30000;
const int Min_edits_for_safety_backup = 10;
const int Max_interval_for_safety_backup = 60000;
const int Max_edits_for_safety_backup = 30;
///
/// Adds a file to Backups_main or Backups_spare lists, if it's a backup.
///
static void Add_backup_file(const char * full_name, const char *file_name)
{
T_String_list ** list;
T_String_list * elem;
int i;
(void)full_name;
// Only files names of the form a0000000.* and b0000000.* are expected
// Check first character
if (file_name[0]==Main.safety_backup_prefix)
list = &Backups_main;
else if (file_name[0]==Spare.safety_backup_prefix)
list = &Backups_spare;
else {
// Not a good file
return;
}
// Check next characters till file extension
i = 1;
while (file_name[i]!='\0' && file_name[i]!='.')
{
if (file_name[i]< '0' || file_name[i] > '9')
{
// Not a good file
return;
}
i++;
}
// Add to list (top insertion)
elem = (T_String_list *)GFX2_malloc(sizeof(T_String_list));
elem->String=strdup(file_name);
elem->Next=*list;
*list=elem;
}
/// String comparer for sorting
int String_compare (const void * a, const void * b)
{
return strcmp(*(char**)a,*(char**)b);
}
///
/// Reload safety backups, by loading several files in the right order.
///
byte Process_backups(T_String_list **list)
{
int nb_files;
int i;
char ** files_vector;
T_String_list *element;
byte backup_max_undo_pages;
if (*list == NULL)
return 0;
// Save the maximum number of pages
// (It's used in Create_new_page() which gets called on each Load_image)
backup_max_undo_pages = Config.Max_undo_pages;
Config.Max_undo_pages = 99;
// Count files
nb_files=0;
element=*list;
while (element != NULL)
{
nb_files++;
element = element->Next;
}
// Allocate a vector
files_vector = (char **)GFX2_malloc(sizeof(char *) * nb_files);
// TODO
// Copy from list to vector
for (i=0;iString;
next = (*list)->Next;
free(*list);
*list = next;
}
// Sort the vector
qsort (files_vector, nb_files , sizeof(char **), String_compare);
for (i=0; i < nb_files; i++)
{
// Load this file
T_IO_Context context;
Init_context_backup_image(&context, files_vector[i], Config_directory);
// Provide buffers to read original location
Load_image(&context);
Main.image_is_modified=1;
Destroy_context(&context);
Redraw_layered_image();
Display_all_screen();
}
// Done with the vector
for (i=0; i < nb_files; i++)
{
free(files_vector[i]);
}
free(files_vector);
files_vector = NULL;
// Restore the maximum number of pages
Config.Max_undo_pages = backup_max_undo_pages;
return nb_files;
}
/// Global indicator that tells if the safety backup system is active
byte Safety_backup_active = 0;
///
/// Checks if there are any pending safety backups, and then opens them.
/// @return 0 if no problem, -1 if the backup system cannot be activated, >=1 if some backups are restored
int Check_recovery(void)
{
int restored_spare;
int restored_main;
// First check if can write backups
#if defined (__MINT__)
//TODO: enable file lock under Freemint only
return 0;
#else
if (Create_lock_file(Config_directory))
return -1;
#endif
Safety_backup_active=1;
Backups_main = NULL;
Backups_spare = NULL;
For_each_file(Config_directory, Add_backup_file);
// Do the processing twice: once for possible backups of the main page,
// once for possible backups of the spare.
restored_spare = Process_backups(&Backups_spare);
if (restored_spare)
{
Main.offset_X=0;
Main.offset_Y=0;
Compute_limits();
Compute_paintbrush_coordinates();
if (Backups_main)
Button_Page(BUTTON_PAGE);
}
restored_main = Process_backups(&Backups_main);
if (restored_main)
{
Main.offset_X=0;
Main.offset_Y=0;
Compute_limits();
Compute_paintbrush_coordinates();
}
return restored_main + restored_spare;
}
void Rotate_safety_backups(void)
{
dword now;
T_IO_Context context;
char file_name[12+1];
if (!Safety_backup_active)
return;
now = GFX2_GetTicks();
// It's time to save if either:
// - Many edits have taken place
// - A minimum number of edits have taken place AND a minimum time has passed
// - At least one edit was done, and a maximum time has passed
if ((Main.edits_since_safety_backup > Max_edits_for_safety_backup) ||
(Main.edits_since_safety_backup > Min_edits_for_safety_backup &&
now > Main.time_of_safety_backup + Min_interval_for_safety_backup) ||
(Main.edits_since_safety_backup > 1 &&
now > Main.time_of_safety_backup + Max_interval_for_safety_backup))
{
char * deleted_file;
size_t len = strlen(Config_directory) + strlen(BACKUP_FILE_EXTENSION) + 1 + 6 + 1;
deleted_file = GFX2_malloc(len);
if (deleted_file == NULL)
return;
// Clear a previous save (rotating saves)
snprintf(deleted_file, len, "%s%c%6.6d" BACKUP_FILE_EXTENSION,
Config_directory,
Main.safety_backup_prefix,
(dword)(Main.safety_number + 1000000l - Rotation_safety_backup) % (dword)1000000l);
Remove_path(deleted_file); // no matter if fail
free(deleted_file);
// Reset counters
Main.edits_since_safety_backup=0;
Main.time_of_safety_backup=now;
// Create a new file name and save
sprintf(file_name, "%c%6.6d" BACKUP_FILE_EXTENSION,
Main.safety_backup_prefix,
(int)Main.safety_number);
Init_context_backup_image(&context, file_name, Config_directory);
context.Format=FORMAT_GIF;
// Provide original file data, to store as a GIF Application Extension
context.Original_file_name = strdup(Main.backups->Pages->Filename);
context.Original_file_directory = strdup(Main.backups->Pages->File_directory);
Save_image(&context);
Destroy_context(&context);
Main.safety_number++;
}
}
/// Remove safety backups. Need to call on normal program exit.
void Delete_safety_backups(void)
{
T_String_list *element;
T_String_list *next;
if (!Safety_backup_active)
return;
Backups_main = NULL;
Backups_spare = NULL;
For_each_file(Config_directory, Add_backup_file);
Change_directory(Config_directory);
for (element=Backups_main; element!=NULL; element=next)
{
next = element->Next;
if(remove(element->String))
printf("Failed to delete %s\n",element->String);
free(element->String);
free(element);
}
Backups_main = NULL;
for (element=Backups_spare; element!=NULL; element=next)
{
next = element->Next;
if(remove(element->String))
printf("Failed to delete %s\n",element->String);
free(element->String);
free(element);
}
Backups_spare = NULL;
// Release lock file
#if defined (__MINT__)
//TODO: release file lock under Freemint only
#else
Release_lock_file(Config_directory);
#endif
}