/* Grafx2 - The Ultimate 256-color bitmap paint program
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
*/
//////////////////////////////////////////////////////////////////////////
/////////////////////////// GESTION DU BACKUP ////////////////////////////
//////////////////////////////////////////////////////////////////////////
#include
#include
#include
#include "global.h"
#include "pages.h"
#include "errors.h"
#include "misc.h"
#include "windows.h"
///
/// GESTION DES PAGES
///
/// Bitfield which records which layers are backed up in Page 0.
static word Last_backed_up_layers=0;
/// Allocate and initialize a new page.
T_Page * New_page(byte nb_layers)
{
T_Page * page;
page = (T_Page *)malloc(sizeof(T_Page)+NB_LAYERS*sizeof(byte *));
if (page!=NULL)
{
int i;
for (i=0; iImage[i]=NULL;
page->Width=0;
page->Height=0;
memset(page->Palette,0,sizeof(T_Palette));
page->Comment[0]='\0';
page->File_directory[0]='\0';
page->Filename[0]='\0';
page->File_format=DEFAULT_FILEFORMAT;
page->Nb_layers=nb_layers;
page->Next = page->Prev = NULL;
}
return page;
}
// ==============================================================
// Layers allocation functions.
//
// Layers are made of a "number of users" (short), followed by
// the actual pixel data (a large number of bytes).
// Every time a layer is 'duplicated' as a reference, the number
// of users is incremented.
// Every time a layer is freed, the number of users is decreased,
// and only when it reaches zero the pixel data is freed.
// ==============================================================
/// Allocate a new layer
byte * New_layer(long pixel_size)
{
short * ptr = malloc(sizeof(short)+pixel_size);
if (ptr==NULL)
return NULL;
*ptr = 1;
return (byte *)(ptr+1);
}
/// Free a layer
void Free_layer(byte * layer)
{
short * ptr = (short *)(layer);
if (layer==NULL)
return;
if (-- (*(ptr-1))) // Users--
return;
else
free(ptr-1);
}
/// Duplicate a layer (new reference)
byte * Dup_layer(byte * layer)
{
short * ptr = (short *)(layer);
if (layer==NULL)
return NULL;
(*(ptr-1)) ++; // Users ++
return layer;
}
// ==============================================================
void Download_infos_page_main(T_Page * page)
// Affiche la page à l'écran
{
//int factor_index;
int size_is_modified;
if (page!=NULL)
{
size_is_modified=(Main_image_width!=page->Width) ||
(Main_image_height!=page->Height);
Main_image_width=page->Width;
Main_image_height=page->Height;
memcpy(Main_palette,page->Palette,sizeof(T_Palette));
strcpy(Main_comment,page->Comment);
strcpy(Main_file_directory,page->File_directory);
strcpy(Main_filename,page->Filename);
Main_fileformat=page->File_format;
if (size_is_modified)
{
Main_magnifier_mode=0;
Main_offset_X=0;
Main_offset_Y=0;
Pixel_preview=Pixel_preview_normal;
Compute_limits();
Compute_paintbrush_coordinates();
}
}
//Update_visible_page_buffer(Visible_image_index, page->Width, page->Height);
//memcpy(Main_screen, page->Image[Main_current_layer], page->Width*page->Height);
}
void Redraw_layered_image(void)
{
// Re-construct the image with the visible layers
int layer;
// First layer
for (layer=0; layerPages->Nb_layers; layer++)
{
if ((1<Pages->Image[layer],
Main_image_width*Main_image_height);
// Initialize the depth buffer
memset(Visible_image_depth_buffer.Image,
layer,
Main_image_width*Main_image_height);
// skip all other layers
layer++;
break;
}
}
// subsequent layer(s)
for (; layerPages->Nb_layers; layer++)
{
if ((1<Pages->Image[layer]+i);
if (color != 0) /* transp color */
{
*(Visible_image[0].Image+i) = color;
if (layer != Main_current_layer)
*(Visible_image_depth_buffer.Image+i) = layer;
}
}
}
}
Download_infos_backup(Main_backups);
}
void Upload_infos_page_main(T_Page * page)
// Sauve l'écran courant dans la page
{
if (page!=NULL)
{
//page->Image[Main_current_layer]=Main_screen;
page->Width=Main_image_width;
page->Height=Main_image_height;
memcpy(page->Palette,Main_palette,sizeof(T_Palette));
strcpy(page->Comment,Main_comment);
strcpy(page->File_directory,Main_file_directory);
strcpy(page->Filename,Main_filename);
page->File_format=Main_fileformat;
}
}
void Download_infos_page_spare(T_Page * page)
{
if (page!=NULL)
{
//Spare_screen=page->Image[Spare_current_layer];
Spare_image_width=page->Width;
Spare_image_height=page->Height;
memcpy(Spare_palette,page->Palette,sizeof(T_Palette));
strcpy(Spare_comment,page->Comment);
strcpy(Spare_file_directory,page->File_directory);
strcpy(Spare_filename,page->Filename);
Spare_fileformat=page->File_format;
}
}
void Upload_infos_page_spare(T_Page * page)
{
if (page!=NULL)
{
//page->Image[Spare_current_layer]=Spare_screen;
page->Width=Spare_image_width;
page->Height=Spare_image_height;
memcpy(page->Palette,Spare_palette,sizeof(T_Palette));
strcpy(page->Comment,Spare_comment);
strcpy(page->File_directory,Spare_file_directory);
strcpy(page->Filename,Spare_filename);
page->File_format=Spare_fileformat;
}
}
void Download_infos_backup(T_List_of_pages * list)
{
//list->Pages->Next->Image[Main_current_layer];
if (Config.FX_Feedback)
FX_feedback_screen=list->Pages->Image[Main_current_layer];
// Visible_image[0].Image;
else
FX_feedback_screen=list->Pages->Next->Image[Main_current_layer];
// Visible_image[1].Image;
}
void Clear_page(T_Page * page)
{
// On peut appeler cette fonction sur une page non allouée.
int i;
for (i=0; iNb_layers; i++)
{
Free_layer(page->Image[i]);
page->Image[i]=NULL;
}
page->Width=0;
page->Height=0;
// On ne se préoccupe pas de ce que deviens le reste des infos de l'image.
}
void Copy_S_page(T_Page * dest,T_Page * source)
{
*dest=*source;
}
///
/// GESTION DES LISTES DE PAGES
///
void Init_list_of_pages(T_List_of_pages * list)
{
// Important: appeler cette fonction sur toute nouvelle structure
// T_List_of_pages!
list->List_size=0;
list->Pages=NULL;
}
int Allocate_list_of_pages(T_List_of_pages * list)
{
// Important: la T_List_of_pages ne doit pas déjà désigner une liste de
// pages allouée auquel cas celle-ci serait perdue.
T_Page * page;
// On initialise chacune des nouvelles pages
page=New_page(NB_LAYERS);
if (!page)
return 0;
// Set as first page of the list
page->Next = page;
page->Prev = page;
list->Pages = page;
list->List_size=1;
return 1; // Succès
}
void Backward_in_list_of_pages(T_List_of_pages * list)
{
// Cette fonction fait l'équivalent d'un "Undo" dans la liste de pages.
// Elle effectue une sorte de ROL (Rotation Left) sur la liste:
// +---+-+-+-+-+-+-+-+-+-+ |
// ¦0¦1¦2¦3¦4¦5¦6¦7¦8¦9¦A¦ |
// +---+-+-+-+-+-+-+-+-+-+ | 0=page courante
// ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ |_ A=page la plus ancienne
// v v v v v v v v v v v | 1=DerniÞre page (1er backup)
// +---+-+-+-+-+-+-+-+-+-+ |
// ¦1¦2¦3¦4¦5¦6¦7¦8¦9¦A¦0¦ |
// +---+-+-+-+-+-+-+-+-+-+ |
// Pour simuler un véritable Undo, l'appelant doit mettre la structure
// de page courante à jour avant l'appel, puis en réextraire les infos en
// sortie, ainsi que celles relatives à la plus récente page d'undo (1ère
// page de la liste).
if (Last_backed_up_layers)
{
// First page contains a ready-made backup of its ->Next.
// We have swap the first two pages, so the original page 0
// will end up in position 0 again, and then overwrite it with a backup
// of the 'new' page1.
T_Page * page0;
T_Page * page1;
page0 = list->Pages;
page1 = list->Pages->Next;
page0->Next = page1->Next;
page1->Prev = page0->Prev;
page0->Prev = page1;
page1->Next = page0;
list->Pages = page0;
return;
}
list->Pages = list->Pages->Next;
}
void Advance_in_list_of_pages(T_List_of_pages * list)
{
// Cette fonction fait l'équivalent d'un "Redo" dans la liste de pages.
// Elle effectue une sorte de ROR (Rotation Right) sur la liste:
// +-+-+-+-+-+-+-+-+-+-+-+ |
// |0|1|2|3|4|5|6|7|8|9|A| |
// +-+-+-+-+-+-+-+-+-+-+-+ | 0=page courante
// | | | | | | | | | | | |_ A=page la plus ancienne
// v v v v v v v v v v v | 1=Dernière page (1er backup)
// +-+-+-+-+-+-+-+-+-+-+-+ |
// |A|0|1|2|3|4|5|6|7|8|9| |
// +-+-+-+-+-+-+-+-+-+-+-+ |
// Pour simuler un véritable Redo, l'appelant doit mettre la structure
// de page courante à jour avant l'appel, puis en réextraire les infos en
// sortie, ainsi que celles relatives à la plus récente page d'undo (1ère
// page de la liste).
if (Last_backed_up_layers)
{
// First page contains a ready-made backup of its ->Next.
// We have swap the first two pages, so the original page 0
// will end up in position -1 again, and then overwrite it with a backup
// of the 'new' page1.
T_Page * page0;
T_Page * page1;
page0 = list->Pages;
page1 = list->Pages->Prev;
page0->Prev = page1->Prev;
page1->Next = page0->Next;
page0->Next = page1;
page1->Prev = page0;
list->Pages = page1;
return;
}
list->Pages = list->Pages->Prev;
}
void Free_last_page_of_list(T_List_of_pages * list)
{
if (list!=NULL)
{
if (list->List_size>0)
{
T_Page * page;
// The last page is the one before first
page = list->Pages->Prev;
page->Next->Prev = page->Prev;
page->Prev->Next = page->Next;
Clear_page(page);
free(page);
list->List_size--;
}
}
}
// layer_mask tells which layers have to be fresh copies instead of references
int Create_new_page(T_Page * new_page,T_List_of_pages * list, byte layer_mask)
{
// Cette fonction crée une nouvelle page dont les attributs correspondent à
// ceux de new_page (width,height,...) (le champ Image est invalide
// à l'appel, c'est la fonction qui le met à jour), et l'enfile dans
// list.
if (list->List_size >= (Config.Max_undo_pages+1))
{
// On manque de mémoire ou la list est pleine. Dans tous les
// cas, il faut libérer une page.
// Détruire la dernière page allouée dans la Liste_à_raboter
Free_last_page_of_list(list);
}
{
int i;
for (i=0; iNb_layers; i++)
{
if ((1<Image[i]=New_layer(new_page->Height*new_page->Width);
else
new_page->Image[i]=Dup_layer(list->Pages->Image[i]);
}
}
// Insert as first
new_page->Next = list->Pages;
new_page->Prev = list->Pages->Prev;
list->Pages->Prev->Next = new_page;
list->Pages->Prev = new_page;
list->Pages = new_page;
list->List_size++;
return 1;
}
void Change_page_number_of_list(T_List_of_pages * list,int number)
{
// Truncate the list if larger than requested
while(list->List_size > number)
{
Free_last_page_of_list(list);
}
}
void Free_page_of_a_list(T_List_of_pages * list)
{
// On ne peut pas détruire la page courante de la liste si après
// destruction il ne reste pas encore au moins une page.
if (list->List_size>1)
{
// On fait faire un undo à la liste, comme ça, la nouvelle page courante
// est la page précédente
Backward_in_list_of_pages(Main_backups);
// Puis on détruit la dernière page, qui est l'ancienne page courante
Free_last_page_of_list(list);
}
}
int Update_visible_page_buffer(int index, int width, int height)
{
if (Visible_image[index].Width != width || Visible_image[index].Height != height)
{
Visible_image[index].Width = width;
Visible_image[index].Height = height;
free(Visible_image[index].Image);
Visible_image[index].Image = (byte *)malloc(width * height);
if (Visible_image[index].Image == NULL)
return 0;
}
return 1;
}
int Update_depth_buffer(int width, int height)
{
if (Visible_image_depth_buffer.Width != width || Visible_image_depth_buffer.Height != height)
{
Visible_image_depth_buffer.Width = width;
Visible_image_depth_buffer.Height = height;
free(Visible_image_depth_buffer.Image);
Visible_image_depth_buffer.Image = (byte *)malloc(width * height);
if (Visible_image_depth_buffer.Image == NULL)
return 0;
}
return 1;
}
///
/// GESTION DES BACKUPS
///
int Init_all_backup_lists(int width,int height)
{
// width et height correspondent à la dimension des images de départ.
int i;
if (! Allocate_list_of_pages(Main_backups) ||
! Allocate_list_of_pages(Spare_backups))
return 0;
// On a réussi à allouer deux listes de pages dont la taille correspond à
// celle demandée par l'utilisateur.
// On crée un descripteur de page correspondant à la page principale
Upload_infos_page_main(Main_backups->Pages);
// On y met les infos sur la dimension de démarrage
Main_backups->Pages->Width=width;
Main_backups->Pages->Height=height;
for (i=0; iPages->Nb_layers; i++)
{
Main_backups->Pages->Image[i]=New_layer(width*height);
if (! Main_backups->Pages->Image[i])
return 0;
}
if (!Update_visible_page_buffer(0, width, height))
return 0;
Main_screen=Visible_image[0].Image;
if (!Update_visible_page_buffer(1, width, height))
return 0;
Screen_backup=Visible_image[1].Image;
Download_infos_page_main(Main_backups->Pages);
Download_infos_backup(Main_backups);
// Default values for spare page
Spare_backups->Pages->Width = width;
Spare_backups->Pages->Height = height;
memcpy(Spare_backups->Pages->Palette,Main_palette,sizeof(T_Palette));
strcpy(Spare_backups->Pages->Comment,"");
strcpy(Spare_backups->Pages->File_directory,Spare_current_directory);
strcpy(Spare_backups->Pages->Filename,"NO_NAME.GIF");
Spare_backups->Pages->File_format=DEFAULT_FILEFORMAT;
// Copy this informations in the global Spare_ variables
Download_infos_page_spare(Spare_backups->Pages);
// Clear the initial Visible buffer
//memset(Main_screen,0,Main_image_width*Main_image_height);
// Spare
for (i=0; iPages->Image[i]=New_layer(width*height);
if (! Spare_backups->Pages->Image[i])
return 0;
}
//memset(Spare_screen,0,Spare_image_width*Spare_image_height);
Visible_image[0].Width = width;
Visible_image[0].Height = height;
Visible_image[0].Image = NULL;
Visible_image[1].Width = width;
Visible_image[1].Height = height;
Visible_image[1].Image = NULL;
Visible_image_depth_buffer.Width = width;
Visible_image_depth_buffer.Height = height;
Visible_image_depth_buffer.Image = NULL;
Visible_image[0].Image = (byte *)malloc(Visible_image[0].Width * Visible_image[0].Height);
if (! Visible_image[0].Image)
return 0;
Visible_image[1].Image = (byte *)malloc(Visible_image[1].Width * Visible_image[1].Height);
if (! Visible_image[1].Image)
return 0;
Visible_image_depth_buffer.Image = (byte *)malloc(Visible_image_depth_buffer.Width * Visible_image_depth_buffer.Height);
if (! Visible_image_depth_buffer.Image)
return 0;
End_of_modification();
return 1;
}
void Set_number_of_backups(int nb_backups)
{
Change_page_number_of_list(Main_backups,nb_backups+1);
Change_page_number_of_list(Spare_backups,nb_backups+1);
// Le +1 vient du fait que dans chaque liste, en 1ère position on retrouve
// les infos de la page courante sur le brouillon et la page principale.
// (nb_backups = Nombre de backups, sans compter les pages courantes)
}
int Backup_with_new_dimensions(int upload,int width,int height)
{
// Retourne 1 si une nouvelle page est disponible (alors pleine de 0) et
// 0 sinon.
T_Page * new_page;
byte nb_layers;
int return_code=0;
int i;
if (upload)
// On remet à jour l'état des infos de la page courante (pour pouvoir les
// retrouver plus tard)
Upload_infos_page_main(Main_backups->Pages);
nb_layers=Main_backups->Pages->Nb_layers;
// On crée un descripteur pour la nouvelle page courante
new_page=New_page(nb_layers);
if (!new_page)
{
Error(0);
return 0;
}
//Copy_S_page(new_page,Main_backups->Pages);
Upload_infos_page_main(new_page);
new_page->Width=width;
new_page->Height=height;
if (Create_new_page(new_page,Main_backups,0))
{
for (i=0; iPages->Image[i]=(byte *)malloc(width*height);
memset(Main_backups->Pages->Image[i], 0, width*height);
}
Update_depth_buffer(width, height);
Update_visible_page_buffer(0, width, height);
Main_screen=Visible_image[0].Image;
Update_visible_page_buffer(1, width, height);
Screen_backup=Visible_image[1].Image;
Download_infos_page_main(Main_backups->Pages);
Download_infos_backup(Main_backups);
// On nettoie la nouvelle image:
//memset(Main_screen,0,width*height);
return_code=1;
}
// On détruit le descripteur de la page courante
//free(new_page);
return return_code;
}
int Backup_and_resize_the_spare(int width,int height)
{
// Retourne 1 si la page de dimension souhaitee est disponible en brouillon
// et 0 sinon.
T_Page * new_page;
int return_code=0;
// On remet à jour l'état des infos de la page de brouillon (pour pouvoir
// les retrouver plus tard)
Upload_infos_page_spare(Spare_backups->Pages);
// On crée un descripteur pour la nouvelle page de brouillon
new_page=New_page(Spare_backups->Pages->Nb_layers);
if (!new_page)
{
Error(0);
return 0;
}
Upload_infos_page_spare(new_page);
new_page->Width=width;
new_page->Height=height;
if (Create_new_page(new_page,Spare_backups,0))
{
Download_infos_page_spare(new_page);
return_code=1;
}
// On détruit le descripteur de la page courante
free(new_page);
return return_code;
}
void Backup(void)
// Sauve la page courante comme première page de backup et crée une nouvelle page
// pur continuer à dessiner. Utilisé par exemple pour le fill
{
Backup_layers(1<Pages);
// On crée un descripteur pour la nouvelle page courante
new_page=New_page(Main_backups->Pages->Nb_layers);
if (!new_page)
{
Error(0);
return;
}
// Enrichissement de l'historique
Copy_S_page(new_page,Main_backups->Pages);
Create_new_page(new_page,Main_backups,layer_mask);
Download_infos_page_main(new_page);
Download_infos_backup(Main_backups);
// On copie l'image du backup vers la page courante:
for (i=0; iPages->Nb_layers;i++)
{
if ((1<Pages->Image[i],
Main_backups->Pages->Next->Image[i],
Main_image_width*Main_image_height);
}
// On allume l'indicateur de modification de l'image
Main_image_is_modified=1;
/*
Last_backed_up_layers = 1<Pages);
// On fait faire un undo à la liste des backups de la page principale
Backward_in_list_of_pages(Main_backups);
// On extrait ensuite les infos sur la nouvelle page courante
Download_infos_page_main(Main_backups->Pages);
// Et celles du backup
Download_infos_backup(Main_backups);
// Note: le backup n'a pas obligatoirement les mêmes dimensions ni la même
// palette que la page courante. Mais en temps normal, le backup
// n'est pas utilisé à la suite d'un Undo. Donc ça ne devrait pas
// poser de problèmes.
Redraw_layered_image();
}
void Redo(void)
{
if (Last_backed_up_layers)
{
Free_page_of_a_list(Main_backups);
Last_backed_up_layers=0;
}
// On remet à jour l'état des infos de la page courante (pour pouvoir les
// retrouver plus tard)
Upload_infos_page_main(Main_backups->Pages);
// On fait faire un redo à la liste des backups de la page principale
Advance_in_list_of_pages(Main_backups);
// On extrait ensuite les infos sur la nouvelle page courante
Download_infos_page_main(Main_backups->Pages);
// Et celles du backup
Download_infos_backup(Main_backups);
// Note: le backup n'a pas obligatoirement les mêmes dimensions ni la même
// palette que la page courante. Mais en temps normal, le backup
// n'est pas utilisé à la suite d'un Redo. Donc ça ne devrait pas
// poser de problèmes.
Redraw_layered_image();
}
void Free_current_page(void)
{
// On détruit la page courante de la liste principale
Free_page_of_a_list(Main_backups);
// On extrait ensuite les infos sur la nouvelle page courante
Download_infos_page_main(Main_backups->Pages);
// Et celles du backup
Download_infos_backup(Main_backups);
// Note: le backup n'a pas obligatoirement les mêmes dimensions ni la même
// palette que la page courante. Mais en temps normal, le backup
// n'est pas utilisé à la suite d'une destruction de page. Donc ça ne
// devrait pas poser de problèmes.
}
void Exchange_main_and_spare(void)
{
T_List_of_pages * temp_list;
// On commence par mettre à jour dans les descripteurs les infos sur les
// pages qu'on s'apprête à échanger, pour qu'on se retrouve pas avec de
// vieilles valeurs qui datent de mathuzalem.
Upload_infos_page_main(Main_backups->Pages);
Upload_infos_page_spare(Spare_backups->Pages);
// On inverse les listes de pages
temp_list=Main_backups;
Main_backups=Spare_backups;
Spare_backups=temp_list;
// On extrait ensuite les infos sur les nouvelles pages courante, brouillon
// et backup.
Update_depth_buffer(Main_backups->Pages->Width, Main_backups->Pages->Height);
Update_visible_page_buffer(0, Main_backups->Pages->Width, Main_backups->Pages->Height);
Main_screen=Visible_image[0].Image;
Update_visible_page_buffer(1, Main_backups->Pages->Width, Main_backups->Pages->Height);
Screen_backup=Visible_image[1].Image;
/* SECTION GROS CACA PROUT PROUT */
// Auparavant on ruse en mettant déjà à jour les dimensions de la
// nouvelle page courante. Si on ne le fait pas, le "Download" va détecter
// un changement de dimensions et va bêtement sortir du mode loupe, alors
// que lors d'un changement de page, on veut bien conserver l'état du mode
// loupe du brouillon.
Main_image_width=Main_backups->Pages->Width;
Main_image_height=Main_backups->Pages->Height;
Download_infos_page_main(Main_backups->Pages);
Download_infos_backup(Main_backups);
Download_infos_page_spare(Spare_backups->Pages);
Redraw_layered_image();
}
void End_of_modification(void)
{
//Update_visible_page_buffer(1, Main_image_width, Main_image_height);
memcpy(Visible_image[1].Image,
Visible_image[0].Image,
Main_image_width*Main_image_height);
Download_infos_backup(Main_backups);
/*
Last_backed_up_layers = 0;
Backup();
*/
}