/* vim:expandtab:ts=2 sw=2: */ /* 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 "loadsave.h" #include "misc.h" #include "windows.h" #include "tiles.h" #include "graph.h" // -- Layers data /// Array of two images, that contains the "flattened" version of the visible layers. T_Bitmap Main_visible_image; T_Bitmap Main_visible_image_backup; T_Bitmap Main_visible_image_depth_buffer; T_Bitmap Spare_visible_image; /// /// GESTION DES PAGES /// /// Bitfield which records which layers are backed up in Page 0. static dword Last_backed_up_layers=0; /// Total number of unique bitmaps (layers, animation frames, backups) long Stats_pages_number=0; /// Total memory used by bitmaps (layers, animation frames, backups) long long Stats_pages_memory=0; /// Allocate and initialize a new page. T_Page * New_page(int nb_layers) { T_Page * page; page = (T_Page *)malloc(sizeof(T_Page)+nb_layers*sizeof(T_Image)); if (page!=NULL) { int i; for (i=0; iImage[i].Pixels=NULL; page->Image[i].Duration=100; } page->Width=0; page->Height=0; page->Image_mode = IMAGE_MODE_LAYERED; 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->Gradients=NULL; page->Transparent_color=0; // Default transparent color page->Background_transparent=0; 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; // Stats Stats_pages_number++; Stats_pages_memory+=pixel_size; *ptr = 1; return (byte *)(ptr+1); } /// Free a layer void Free_layer(T_Page * page, int layer) { short * ptr; if (page->Image[layer].Pixels==NULL) return; ptr = (short *)(page->Image[layer].Pixels); if (-- (*(ptr-1))) // Users-- return; else { free(ptr-1); } // Stats Stats_pages_number--; Stats_pages_memory-=page->Width * page->Height; } /// Duplicate a layer (new reference) byte * Dup_layer(byte * layer) { short * ptr = (short *)(layer); if (layer==NULL) return NULL; (*(ptr-1)) ++; // Users ++ return layer; } // ============================================================== /// Adds a shared reference to the gradient data of another page. Pass NULL for new. T_Gradient_array *Dup_gradient(T_Page * page) { // new if (page==NULL || page->Gradients==NULL) { T_Gradient_array *array; array=(T_Gradient_array *)calloc(1, sizeof(T_Gradient_array)); if (!array) return NULL; array->Used=1; return array; } // shared page->Gradients->Used++; return page->Gradients; } 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)); 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_buffers( page->Width, page->Height); //memcpy(Main_screen, page->Image[Main_current_layer].Pixels, page->Width*page->Height); } void Redraw_layered_image(void) { if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // Re-construct the image with the visible layers byte layer=0; // First layer if (Main_backups->Pages->Image_mode == IMAGE_MODE_MODE5 && Main_layers_visible & (1<<4)) { // The raster result layer is visible: start there // Copy it in Main_visible_image int i; for (i=0; i< Main_image_width*Main_image_height; i++) { layer = *(Main_backups->Pages->Image[4].Pixels+i); if (Main_layers_visible & (1 << layer)) Main_visible_image.Image[i]=*(Main_backups->Pages->Image[layer].Pixels+i); else Main_visible_image.Image[i] = layer; } // Copy it to the depth buffer memcpy(Main_visible_image_depth_buffer.Image, Main_backups->Pages->Image[4].Pixels, Main_image_width*Main_image_height); // Next layer= (1<<4)+1; } else { for (layer=0; layerPages->Nb_layers; layer++) { if ((1<Pages->Image[layer].Pixels, Main_image_width*Main_image_height); // Initialize the depth buffer memset(Main_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].Pixels+i); if (color != Main_backups->Pages->Transparent_color) // transparent color { *(Main_visible_image.Image+i) = color; if (layer != Main_current_layer) *(Main_visible_image_depth_buffer.Image+i) = layer; } } } } } else { Update_screen_targets(); } Update_FX_feedback(Config.FX_Feedback); } void Update_depth_buffer(void) { if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // Re-construct the depth buffer with the visible layers. // This function doesn't touch the visible buffer, it assumes // that it was already up-to-date. (Ex. user only changed active layer) int layer; // First layer for (layer=0; layerPages->Nb_layers; layer++) { if ((1<Pages->Nb_layers; layer++) { // skip the current layer, whenever we reach it if (layer == Main_current_layer) continue; if ((1<Pages->Image[layer].Pixels+i); if (color != Main_backups->Pages->Transparent_color) // transparent color { *(Main_visible_image_depth_buffer.Image+i) = layer; } } } } } Update_FX_feedback(Config.FX_Feedback); } void Redraw_spare_image(void) { if (Spare_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // Re-construct the image with the visible layers byte layer; // First layer for (layer=0; layerPages->Nb_layers; layer++) { if ((1<Pages->Image[layer].Pixels, Spare_image_width*Spare_image_height); // No depth buffer in the spare //memset(Spare_visible_image_depth_buffer.Image, // layer, // Spare_image_width*Spare_image_height); // skip all other layers layer++; break; } } // subsequent layer(s) for (; layerPages->Nb_layers; layer++) { if ((1<Pages->Image[layer].Pixels+i); if (color != Spare_backups->Pages->Transparent_color) // transparent color { *(Spare_visible_image.Image+i) = color; //if (layer != Spare_current_layer) // *(Spare_visible_image_depth_buffer.Image+i) = layer; } } } } } } void Redraw_current_layer(void) { if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { int i; for (i=0; iPages->Image[Main_current_layer].Pixels+i); if (color != Main_backups->Pages->Transparent_color) // transparent color { *(Main_visible_image.Image+i) = color; } else { *(Main_visible_image.Image+i) = *(Main_backups->Pages->Image[depth].Pixels+i); } } } } } void Upload_infos_page_main(T_Page * page) // Sauve l'écran courant dans la page { if (page!=NULL) { //page->Image[Main_current_layer].Pixels=Main_screen; page->Width=Main_image_width; page->Height=Main_image_height; memcpy(page->Palette,Main_palette,sizeof(T_Palette)); page->File_format=Main_fileformat; } } void Download_infos_page_spare(T_Page * page) { if (page!=NULL) { Spare_image_width=page->Width; Spare_image_height=page->Height; memcpy(Spare_palette,page->Palette,sizeof(T_Palette)); Spare_fileformat=page->File_format; } } void Upload_infos_page_spare(T_Page * page) { if (page!=NULL) { //page->Image[Spare_current_layer].Pixels=Spare_screen; page->Width=Spare_image_width; page->Height=Spare_image_height; memcpy(page->Palette,Spare_palette,sizeof(T_Palette)); page->File_format=Spare_fileformat; } } byte * FX_feedback_screen; void Update_FX_feedback(byte with_feedback) { if (with_feedback) FX_feedback_screen=Main_backups->Pages->Image[Main_current_layer].Pixels; else FX_feedback_screen=Main_backups->Pages->Next->Image[Main_current_layer].Pixels; } 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, i); page->Image[i].Pixels=NULL; page->Image[i].Duration=0; } // Free_gradient() : This data is reference-counted if (page->Gradients) { page->Gradients->Used--; if (page->Gradients->Used==0) free(page->Gradients); page->Gradients=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; dest->Gradients=NULL; } /// /// 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(1); if (!page) return 0; // Set as first page of the list page->Next = page; page->Prev = page; list->Pages = page; list->List_size=1; page->Gradients=Dup_gradient(NULL); if (!page->Gradients) return 0; 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); page = NULL; 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, dword layer_mask) { // This function fills the "Image" field of a new Page, // based on the pages's attributes (width,height,...) // then pushes it on front of a Page list. if (list->List_size >= (Config.Max_undo_pages+1)) { // List is full. // If some other memory-limit was to be implemented, here would // be the right place to do it. // For example, we could rely on Stats_pages_memory, // because it's the sum of all bitmaps in use (in bytes). // Destroy the latest page Free_last_page_of_list(list); } { int i; for (i=0; iNb_layers; i++) { if ((1<Image[i].Pixels=New_layer(new_page->Height*new_page->Width); else new_page->Image[i].Pixels=Dup_layer(list->Pages->Image[i].Pixels); new_page->Image[i].Duration=list->Pages->Image[i].Duration; } } // 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); } } void Update_screen_targets(void) { if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { Main_screen=Main_visible_image.Image; Screen_backup=Main_visible_image_backup.Image; } else { Main_screen=Main_backups->Pages->Image[Main_current_layer].Pixels; // Sometimes this function will be called in situations where the // current history step and previous one don't have as many layers. // I don't like the idea of letting Screen_backup NULL or dangling, // so in case Screen_backup was queried, it will point to a valid // readable bitmap of correct size : current image. if (Main_backups->Pages->Nb_layers != Main_backups->Pages->Next->Nb_layers || Main_backups->Pages->Width != Main_backups->Pages->Next->Width || Main_backups->Pages->Height != Main_backups->Pages->Next->Height) Screen_backup=Main_screen; else Screen_backup=Main_backups->Pages->Next->Image[Main_current_layer].Pixels; } Update_pixel_renderer(); } /// Update all the special image buffers, if necessary. int Update_buffers(int width, int height) { if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // At least one dimension is different if (Main_visible_image.Width*Main_visible_image.Height != width*height) { // Current image free(Main_visible_image.Image); Main_visible_image.Image = (byte *)malloc(width * height); if (Main_visible_image.Image == NULL) return 0; } Main_visible_image.Width = width; Main_visible_image.Height = height; if (Main_visible_image_backup.Width*Main_visible_image_backup.Height != width*height) { // Previous image free(Main_visible_image_backup.Image); Main_visible_image_backup.Image = (byte *)malloc(width * height); if (Main_visible_image_backup.Image == NULL) return 0; } Main_visible_image_backup.Width = width; Main_visible_image_backup.Height = height; if (Main_visible_image_depth_buffer.Width*Main_visible_image_depth_buffer.Height != width*height) { // Depth buffer free(Main_visible_image_depth_buffer.Image); Main_visible_image_depth_buffer.Image = (byte *)malloc(width * height); if (Main_visible_image_depth_buffer.Image == NULL) return 0; } Main_visible_image_depth_buffer.Width = width; Main_visible_image_depth_buffer.Height = height; } Update_screen_targets(); return 1; } /// Update all the special image buffers of the spare page, if necessary. int Update_spare_buffers(int width, int height) { if (Spare_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // At least one dimension is different if (Spare_visible_image.Width*Spare_visible_image.Height != width*height) { // Current image free(Spare_visible_image.Image); Spare_visible_image.Image = (byte *)malloc(width * height); if (Spare_visible_image.Image == NULL) return 0; } Spare_visible_image.Width = width; Spare_visible_image.Height = height; } return 1; } /// /// GESTION DES BACKUPS /// int Init_all_backup_lists(enum IMAGE_MODES image_mode, 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; strcpy(Main_backups->Pages->File_directory,Main_selector.Directory); strcpy(Main_backups->Pages->Filename,"NO_NAME.GIF"); for (i=0; iPages->Nb_layers; i++) { Main_backups->Pages->Image[i].Pixels=New_layer(width*height); if (! Main_backups->Pages->Image[i].Pixels) return 0; memset(Main_backups->Pages->Image[i].Pixels, 0, width*height); } Main_visible_image.Width = 0; Main_visible_image.Height = 0; Main_visible_image.Image = NULL; Main_visible_image_backup.Image = NULL; Main_visible_image_depth_buffer.Image = NULL; Main_backups->Pages->Image_mode = image_mode; Spare_visible_image.Width = 0; Spare_visible_image.Height = 0; Spare_visible_image.Image = NULL; Spare_backups->Pages->Image_mode = image_mode; if (!Update_buffers(width, height)) return 0; if (!Update_spare_buffers(width, height)) return 0; // For speed, instead of Redraw_layered_image() we'll directly set the buffers. if (Main_visible_image.Image != NULL) { memset(Main_visible_image.Image, 0, width*height); memset(Main_visible_image_backup.Image, 0, width*height); memset(Main_visible_image_depth_buffer.Image, 0, width*height); } if (Spare_visible_image.Image != NULL) memset(Spare_visible_image.Image, 0, width*height); Download_infos_page_main(Main_backups->Pages); Update_FX_feedback(Config.FX_Feedback); // 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,Main_selector.Directory); strcpy(Spare_backups->Pages->Filename,"NO_NAME2.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].Pixels=New_layer(width*height); if (! Spare_backups->Pages->Image[i].Pixels) return 0; memset(Spare_backups->Pages->Image[i].Pixels, 0, width*height); } //memset(Spare_screen,0,Spare_image_width*Spare_image_height); 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_new_image(int layers,int width,int height) { // Retourne 1 si une nouvelle page est disponible et 0 sinon T_Page * new_page; // On crée un descripteur pour la nouvelle page courante new_page=New_page(layers); if (!new_page) { Error(0); return 0; } new_page->Width=width; new_page->Height=height; new_page->Transparent_color=0; new_page->Gradients=Dup_gradient(NULL); if (!Create_new_page(new_page,Main_backups,0xFFFFFFFF)) { Error(0); return 0; } Update_buffers(width, height); memset(Main_visible_image_depth_buffer.Image, 0, width*height); Download_infos_page_main(Main_backups->Pages); return 1; } int Backup_with_new_dimensions(int width,int height) { // Retourne 1 si une nouvelle page est disponible (alors pleine de 0) et // 0 sinon. T_Page * new_page; int i; // 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 0; } new_page->Width=width; new_page->Height=height; new_page->Transparent_color=0; if (!Create_new_page(new_page,Main_backups,0xFFFFFFFF)) { Error(0); return 0; } // Copy data from previous history step memcpy(Main_backups->Pages->Palette,Main_backups->Pages->Next->Palette,sizeof(T_Palette)); strcpy(Main_backups->Pages->Comment,Main_backups->Pages->Next->Comment); Main_backups->Pages->File_format=Main_backups->Pages->Next->File_format; strcpy(Main_backups->Pages->Filename, Main_backups->Pages->Next->Filename); strcpy(Main_backups->Pages->File_directory, Main_backups->Pages->Next->File_directory); Main_backups->Pages->Gradients=Dup_gradient(Main_backups->Pages->Next); Main_backups->Pages->Background_transparent=Main_backups->Pages->Next->Background_transparent; Main_backups->Pages->Transparent_color=Main_backups->Pages->Next->Transparent_color; Main_backups->Pages->Image_mode=Main_backups->Pages->Next->Image_mode; // Fill with transparent color for (i=0; iPages->Nb_layers;i++) { memset(Main_backups->Pages->Image[i].Pixels, Main_backups->Pages->Transparent_color, width*height); } Update_buffers(width, height); Download_infos_page_main(Main_backups->Pages); // Same code as in End_of_modification(), // Without saving a safety backup: if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { memcpy(Main_visible_image_backup.Image, Main_visible_image.Image, Main_image_width*Main_image_height); } else { Update_screen_targets(); } Update_FX_feedback(Config.FX_Feedback); // -- return 1; } /// /// Resizes a backup step in-place (doesn't add a Undo/Redo step). /// Should only be called after an actual backup, because it loses the current. /// pixels. This function is meant to be used from within Lua scripts. int Backup_in_place(int width,int height) { // Retourne 1 si une nouvelle page est disponible (alors pleine de 0) et // 0 sinon. int i; byte ** new_layer; // Perform all allocations first new_layer=calloc(Main_backups->Pages->Nb_layers,sizeof(byte *)); if (!new_layer) return 0; for (i=0; iPages->Nb_layers; i++) { new_layer[i]=New_layer(height*width); if (!new_layer[i]) { // Allocation error for (; i>0; i--) free(new_layer[i]); free(new_layer); return 0; } } // Now ok to proceed for (i=0; iPages->Nb_layers; i++) { // Replace layers Free_layer(Main_backups->Pages,i); Main_backups->Pages->Image[i].Pixels=new_layer[i]; // Fill with transparency memset(Main_backups->Pages->Image[i].Pixels, Main_backups->Pages->Transparent_color, width*height); } Main_backups->Pages->Width=width; Main_backups->Pages->Height=height; Download_infos_page_main(Main_backups->Pages); // The following is part of Update_buffers() // (without changing the backup buffer) if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // At least one dimension is different if (Main_visible_image.Width*Main_visible_image.Height != width*height) { // Current image free(Main_visible_image.Image); Main_visible_image.Image = (byte *)malloc(width * height); if (Main_visible_image.Image == NULL) return 0; } Main_visible_image.Width = width; Main_visible_image.Height = height; if (Main_visible_image_depth_buffer.Width*Main_visible_image_depth_buffer.Height != width*height) { // Depth buffer free(Main_visible_image_depth_buffer.Image); Main_visible_image_depth_buffer.Image = (byte *)malloc(width * height); if (Main_visible_image_depth_buffer.Image == NULL) return 0; } Main_visible_image_depth_buffer.Width = width; Main_visible_image_depth_buffer.Height = height; } Update_screen_targets(); return 1; } 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; int nb_layers; nb_layers=Spare_backups->Pages->Nb_layers; // On crée un descripteur pour la nouvelle page de brouillon new_page=New_page(nb_layers); if (!new_page) { Error(0); return 0; } // Fill it with a copy of the latest history Copy_S_page(new_page,Spare_backups->Pages); new_page->Gradients=Dup_gradient(Spare_backups->Pages); new_page->Width=width; new_page->Height=height; if (Create_new_page(new_page,Spare_backups,0xFFFFFFFF)) { byte i; for (i=0; iPages->Image[i].Pixels, Spare_backups->Pages->Transparent_color, width*height); } // Update_buffers(width, height); // Not for spare Download_infos_page_spare(Spare_backups->Pages); // Light up the 'has unsaved changes' indicator Spare_image_is_modified=1; return_code=1; } 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); // Create a fresh Page descriptor new_page=New_page(Main_backups->Pages->Nb_layers); if (!new_page) { Error(0); return; } // Fill it with a copy of the latest history Copy_S_page(new_page,Main_backups->Pages); new_page->Gradients=Dup_gradient(Main_backups->Pages); Create_new_page(new_page,Main_backups,layer_mask); Download_infos_page_main(new_page); Update_FX_feedback(Config.FX_Feedback); // Copy the actual pixels from the backup to the latest page for (i=0; iPages->Nb_layers;i++) { if ((1<Pages->Image[i].Pixels, Main_backups->Pages->Next->Image[i].Pixels, Main_image_width*Main_image_height); } // Light up the 'has unsaved changes' indicator Main_image_is_modified=1; /* Last_backed_up_layers = 1<Pages->Nb_layers); if (!new_page) { Error(0); return; } // Fill it with a copy of the latest history Copy_S_page(new_page,Spare_backups->Pages); new_page->Gradients=Dup_gradient(Spare_backups->Pages); Create_new_page(new_page,Spare_backups,layer_mask); // Copy the actual pixels from the backup to the latest page for (i=0; iPages->Nb_layers;i++) { if ((1<Pages->Image[i].Pixels, Spare_backups->Pages->Next->Image[i].Pixels, Spare_image_width*Spare_image_height); } // Light up the 'has unsaved changes' indicator Spare_image_is_modified=1; } void Check_layers_limits() { if (Main_current_layer > Main_backups->Pages->Nb_layers-1) { Main_current_layer = Main_backups->Pages->Nb_layers-1; Main_layers_visible |= 1<Pages); // On fait faire un undo à la liste des backups de la page principale Backward_in_list_of_pages(Main_backups); Update_buffers(Main_backups->Pages->Width, Main_backups->Pages->Height); // On extrait ensuite les infos sur la nouvelle page courante Download_infos_page_main(Main_backups->Pages); // 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. Check_layers_limits(); Redraw_layered_image(); End_of_modification(); if (width != Main_image_width || height != Main_image_height) Tilemap_update(); } void Redo(void) { int width = Main_image_width; int height = Main_image_height; 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); Update_buffers(Main_backups->Pages->Width, Main_backups->Pages->Height); // On extrait ensuite les infos sur la nouvelle page courante Download_infos_page_main(Main_backups->Pages); // 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. Check_layers_limits(); Redraw_layered_image(); End_of_modification(); if (width != Main_image_width || height != Main_image_height) Tilemap_update(); } 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); // 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. Update_buffers(Main_backups->Pages->Width, Main_backups->Pages->Height); Check_layers_limits(); Redraw_layered_image(); End_of_modification(); } 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. /* 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_page_spare(Spare_backups->Pages); } void End_of_modification(void) { //Update_buffers(Main_image_width, Main_image_height); if (Main_backups->Pages->Image_mode != IMAGE_MODE_ANIMATION) { // Backup buffer can have "wrong" size if a Lua script // performs a resize. Update_buffers(Main_image_width, Main_image_height); // memcpy(Main_visible_image_backup.Image, Main_visible_image.Image, Main_image_width*Main_image_height); } else { Update_screen_targets(); } Update_FX_feedback(Config.FX_Feedback); /* Last_backed_up_layers = 0; Backup(); */ // // Processing safety backups // Main_edits_since_safety_backup++; Rotate_safety_backups(); } /// Add a new layer to latest page of a list. Returns 0 on success. byte Add_layer(T_List_of_pages *list, int layer) { int max[] = {MAX_NB_LAYERS, MAX_NB_FRAMES, 5}; T_Page * source_page; T_Page * new_page; byte * new_image; int i; int duration; source_page = list->Pages; if (list->Pages->Nb_layers >= max[list->Pages->Image_mode]) // MAX_NB_LAYERS return 1; // Keep the position reasonable if (layer > list->Pages->Nb_layers) layer = list->Pages->Nb_layers; // Allocate the pixel data new_image = New_layer(list->Pages->Height*list->Pages->Width); if (! new_image) { Error(0); return 1; } // Re-allocate the page itself, with room for one more pointer new_page = realloc(source_page, sizeof(T_Page)+(list->Pages->Nb_layers+1)*sizeof(T_Image)); if (!new_page) { Error(0); return 1; } if (new_page != source_page) { // Need some housekeeping because the page moved in memory. // Update all pointers that pointed to it: new_page->Prev->Next = new_page; new_page->Next->Prev = new_page; list->Pages = new_page; } list->Pages->Nb_layers++; // Move around the pointers. This part is going to be tricky when we // have 'animations x layers' in this vector. for (i=list->Pages->Nb_layers-1; i>layer ; i--) { new_page->Image[i]=new_page->Image[i-1]; } new_page->Image[layer].Pixels=new_image; if (list->Pages->Nb_layers==0) duration=100; else if (layer>0) duration=new_page->Image[layer-1].Duration; else duration=new_page->Image[1].Duration; new_page->Image[layer].Duration=duration; // Fill with transparency, initially memset(new_image, Main_backups->Pages->Transparent_color, list->Pages->Height*list->Pages->Width); // transparent color // Done. Note that the visible buffer is already ok since we // only inserted a transparent "slide" somewhere. // The depth buffer is all wrong though. // Update the flags of visible layers. { dword layers_before; dword layers_after; dword *visible_layers_flag; // Determine if we're modifying the spare or the main page. if (list == Main_backups) { visible_layers_flag = &Main_layers_visible; Main_current_layer = layer; } else { visible_layers_flag = &Spare_layers_visible; Spare_current_layer = layer; } // Fun with binary! layers_before = ((1<Pages; // Keep the position reasonable if (layer >= list->Pages->Nb_layers) layer = list->Pages->Nb_layers - 1; if (list->Pages->Nb_layers == 1) return 1; // For simplicity, we won't actually shrink the page in terms of allocation. // It would only save the size of a pointer, and anyway, as the user draws, // this page is going to fall off the end of the Undo-list // and so it will be cleared anyway. // Smart freeing of the pixel data Free_layer(list->Pages, layer); list->Pages->Nb_layers--; // Move around the pointers. This part is going to be tricky when we // have 'animations x layers' in this vector. for (i=layer; i < list->Pages->Nb_layers; i++) { list->Pages->Image[i]=list->Pages->Image[i+1]; } // Done. At this point the visible buffer and the depth buffer are // all wrong. // Update the flags of visible layers. { dword layers_before; dword layers_after; dword *visible_layers_flag; byte new_current_layer; // Determine if we're modifying the spare or the main page. if (list == Main_backups) { visible_layers_flag = &Main_layers_visible; if (Main_current_layer>=layer && Main_current_layer>0) Main_current_layer--; new_current_layer = Main_current_layer; } else { visible_layers_flag = &Spare_layers_visible; if (Spare_current_layer>=layer && Spare_current_layer>0) Spare_current_layer--; new_current_layer = Spare_current_layer; } // Fun with binary! layers_before = ((1<>1; *visible_layers_flag = layers_before | layers_after; // Ensure the current layer is part what is shown. *visible_layers_flag |= 1<Pages->Image[Main_current_layer].Pixels+i); if (color != Main_backups->Pages->Transparent_color) // transparent color *(Main_backups->Pages->Image[Main_current_layer-1].Pixels+i) = color; } return Delete_layer(Main_backups,Main_current_layer); } void Switch_layer_mode(enum IMAGE_MODES new_mode) { if (new_mode == Main_backups->Pages->Image_mode) return; Main_backups->Pages->Image_mode = new_mode; switch (new_mode) { case IMAGE_MODE_MODE5: case IMAGE_MODE_LAYERED: Update_buffers(Main_image_width, Main_image_height); Redraw_layered_image(); break; case IMAGE_MODE_ANIMATION: // nothing to do. // Eventually, we may clear the buffers to save a bit of memory... break; } Update_pixel_renderer(); }