From a1042598f301eef7c24c227d7149079c7ed4fe99 Mon Sep 17 00:00:00 2001
From: yboucher <yann.boucher@etu.utc.fr>
Date: Sat, 12 Jun 2021 10:29:13 +0200
Subject: [PATCH] Support for saving images and animated GIF files

---
 forms/interface.ui     |  16 +++
 include/gridview.hpp   |   5 +-
 include/history.h      |  12 ++
 include/interface.hpp  |   6 +
 include/simulation.hpp |   4 +-
 src/gif/README         | 142 ++++++++++++++++++
 src/gif/gifenc.c       | 316 +++++++++++++++++++++++++++++++++++++++++
 src/gif/gifenc.h       |  31 ++++
 src/interface.cpp      |  62 +++++++-
 src/src.pro            |   1 +
 10 files changed, 591 insertions(+), 4 deletions(-)
 create mode 100644 src/gif/README
 create mode 100644 src/gif/gifenc.c
 create mode 100644 src/gif/gifenc.h

diff --git a/forms/interface.ui b/forms/interface.ui
index 9b4054b..cda944d 100644
--- a/forms/interface.ui
+++ b/forms/interface.ui
@@ -138,6 +138,9 @@
                  <property name="minimum">
                   <number>1</number>
                  </property>
+                 <property name="maximum">
+                  <number>100</number>
+                 </property>
                  <property name="value">
                   <number>20</number>
                  </property>
@@ -621,7 +624,10 @@ pattern recorded :</string>
     <property name="title">
      <string>Edit</string>
     </property>
+    <addaction name="separator"/>
     <addaction name="actionCharger_depuis_une_image"/>
+    <addaction name="action_save_image"/>
+    <addaction name="action_save_gif"/>
     <addaction name="action_save_struct"/>
    </widget>
    <widget class="QMenu" name="menuA_propos">
@@ -682,6 +688,16 @@ pattern recorded :</string>
     <string>Play Snake</string>
    </property>
   </action>
+  <action name="action_save_image">
+   <property name="text">
+    <string>Save as an image...</string>
+   </property>
+  </action>
+  <action name="action_save_gif">
+   <property name="text">
+    <string>Save as an animated GIF...</string>
+   </property>
+  </action>
  </widget>
  <customwidgets>
   <customwidget>
diff --git a/include/gridview.hpp b/include/gridview.hpp
index a43036d..eaf451e 100644
--- a/include/gridview.hpp
+++ b/include/gridview.hpp
@@ -139,6 +139,9 @@ public:
     //! \brief Annule la dernière opération (effectue un Ctrl-Z).
     void undo();
 
+    //! \brief Retourne une QImage représentant la grille actuelle.
+    const QImage &grid_image() const;
+
 signals:
     //! \brief Signal émis quand le zoom change.
     //! \param cell_size la nouvelle taille à l'écran en pixels d'une cellule
@@ -159,8 +162,6 @@ private:
     void click_on(Coord coord);
     void update_current_mouse_pos(Coord coord);
 
-    const QImage &grid_image() const;
-
 private slots:
     void handle_rubberband(QRect, QPointF, QPointF);
 
diff --git a/include/history.h b/include/history.h
index dcc555b..11dcd6f 100644
--- a/include/history.h
+++ b/include/history.h
@@ -48,6 +48,18 @@ public:
     //! \return Retourne le nombre de lignes de la Grille
     unsigned int get_nbMax()const{return nbMax;}
 
+    //! \brief Retourne le nombre de grilles contenues dans l'historique.
+    unsigned int size() const
+    { return tab.size(); }
+
+    //! \brief Accède à la grille d'indice i.
+    Grid at(unsigned int i) const
+    {
+        if (i >= size())
+            throw HistoryException("The stack is empty.");
+        return tab[i];
+    }
+
     //! \brief Ajoute une grille dans l'historique
     void pushGrid(const Grid& g);
 
diff --git a/include/interface.hpp b/include/interface.hpp
index 76375ed..42a9c0e 100644
--- a/include/interface.hpp
+++ b/include/interface.hpp
@@ -54,6 +54,12 @@ private slots:
     //! \returns Le nom du fichier choisi
     QString afficher_interface_sauvegarde_structure();
 
+    //! \brief Sauvegarde la grille actuelle en tant qu'image.
+    void save_as_image();
+
+    //! \brief Sauvegarde l'historique d'exécution en tant que GIF animé.
+    void save_as_gif();
+
     void on_nbrStateComboBox_currentTextChanged(const QString &arg1);
 
     //! \brief Créer une nouvelle grille ayant des états aléatoire
diff --git a/include/simulation.hpp b/include/simulation.hpp
index 2176ca4..43d391c 100644
--- a/include/simulation.hpp
+++ b/include/simulation.hpp
@@ -61,7 +61,9 @@ public:
     //! Vide également l'historique
     void setHistorySize(unsigned int size) {hist = History(size);}
 
-
+    //! \brief Retourne une référence constante vers l'History actuel.
+    const History& getHistory() const
+    { return hist; }
 
     //! \brief Définit la grille
     //!
diff --git a/src/gif/README b/src/gif/README
new file mode 100644
index 0000000..3c1d2b7
--- /dev/null
+++ b/src/gif/README
@@ -0,0 +1,142 @@
+Source: https://github.com/lecram/gifenc
+
+GIF encoder
+===========
+
+This is a small C library that can be used to create GIF animations.
+
+Features
+--------
+
+  * user-defined palette of any depth from 1 up to 8
+  * each frame has its own (user-specified) delay time
+  * flexible looping options: no loop, N repetitions, infinite loop
+  * GIF size optimization: only stores frame differences
+  * memory efficient: saves frames to file as soon as possible
+  * small and portable: less than 300 lines of C99
+  * public domain
+
+
+Limitations
+-----------
+
+  * no frame-local palettes (incompatible with size optimization)
+  * no interlacing (bad for compression, useless for animations)
+
+
+Documentation
+-------------
+
+There   are  only   three  functions   declared  in   "gifenc.h":  ge_new_gif(),
+ge_add_frame() and ge_close_gif().
+
+The  ge_new_gif() function  receives GIF  global  options and  returns a  ge_GIF
+handler:
+
+    ge_GIF *ge_new_gif(
+        const char *fname,                  /* GIF file name */
+        uint16_t width, uint16_t height,    /* frame size */
+        uint8_t *palette, int depth,        /* color table */
+        int loop                            /* looping information */
+    );
+
+The `palette` parameter  must point to an  array of color data. Each  entry is a
+24-bits RGB color, stored as three contiguous  bytes: the first is the red value
+(0-255), then green, then blue. Entries are stored in a contiguous byte array.
+
+The  `depth` parameter  specifies  how  many colors  are  present  in the  given
+palette. The number of color entries must be 2 ^ depth, where 1 <= depth <= 8.
+
+Example `palette` and `depth` values:
+
+    uint8_t palette[] = {
+        0x00, 0x00, 0x00,   /* entry 0: black */
+        0xFF, 0xFF, 0xFF,   /* entry 1: white */
+        0xFF, 0x00, 0x00,   /* entry 2: red */
+        0x00, 0x00, 0xFF,   /* entry 3: blue */
+    };
+    int depth = 2;          /* palette has 1 << 2 (i.e. 4) entries */
+
+If `palette` is NULL,  entries are taken from a default table  of 256 colors. If
+`depth` < 8,  the default table will  be truncated to the  appropriate size. The
+default table is composed  of the 16 standard VGA colors,  plus the 216 web-safe
+colors (all combinations of  RGB with only 6 valid values  per channel), plus 24
+grey colors equally spaced between black and white, excluding both.
+
+If `depth` < 0 and `palette` is not NULL, then the default table with 2 ^ -depth
+colors is used and it is stored in the array at the `palette` address.
+
+If the `loop` parameter is zero, the resulting GIF will loop forever. If it is a
+positive number, the  animation will  be played that number  of times. If `loop`
+is negative,  no looping  information is stored  in the GIF  file (for  most GIF
+viewers, this is equivalent to `loop` == 1, i.e., "play once").
+
+The  ge_add_frame() function  reads  pixel  data from  a  buffer  and saves  the
+resulting frame to the file associated with the given ge_GIF handler:
+
+    void ge_add_frame(ge_GIF *gif, uint16_t delay);
+
+The `delay` parameter  specifies how long the frame will  be shown, in hundreths
+of a second. For example, `delay` ==  100 means "show this frame for one second"
+and `delay` == 25  means "show this frame for a quarter of  a second". Note that
+short delays may not be supported by  some GIF viewers: it's recommended to keep
+a minimum of `delay` == 6. If `delay` == 0, no delay information  will be stored
+for the frame. This can be used when creating still (single-frame) GIF images.
+
+Pixel data is read from `gif->frame`, which points to a memory block like this:
+
+    uint8_t _frame_[gif->width * gif->height];
+
+Note that  the address of  `gif->frame` changes between calls  to ge_add_frame()
+(*). For this reason, each frame must  be written in its entirety to the current
+address, even if one only wants to change  a few pixels from the last frame. The
+encoder will automatically detect the  difference between two consecutive frames
+in order to minimize the size of the output.
+
+Each byte in the frame buffer represents a  pixel. The value of each pixel is an
+index to a palette entry. For instance, given the example  palette above, we can
+create a frame displaying a red-on-black "F" letter like this:
+
+    uint8_t pixels[] = {
+        2, 2, 2, 2,
+        2, 0, 0, 0,
+        2, 0, 0, 0,
+        2, 2, 2, 0,
+        2, 0, 0, 0,
+        2, 0, 0, 0,
+        2, 0, 0, 0
+    };
+    ge_GIF *gif = ge_new_gif("F.gif", 4, 7, palette, depth, -1);
+    memcpy(gif->frame, pixels, sizeof(pixels));
+    ge_add_frame(gif, 0);
+    ge_close_gif(gif);
+
+The function  ge_close_gif() finishes writting  GIF data to the  file associated
+with the  given ge_GIF handler and  does memory clean-up. This  function must be
+called once after all desired frames have been added, in order to correctly save
+the GIF  file. After calling  this function, the  ge_GIF handler cannot  be used
+anymore.
+
+    void ge_close_gif(ge_GIF* gif);
+
+(*) The  encoder keeps two frame  buffers internally, in order  to implement the
+size  optimization. The  address of  `gif->frame` alternates  between those  two
+buffers after each call to ge_add_frame().
+
+
+Example
+-------
+
+See the file "example.c". It can be tested like this:
+
+$ cc -o example gifenc.c example.c
+$ ./example
+
+That should create an animated GIF named "example.gif".
+
+
+Copying
+-------
+
+All of the source code and documentation for gifenc is released into the
+public domain and provided without warranty of any kind.
diff --git a/src/gif/gifenc.c b/src/gif/gifenc.c
new file mode 100644
index 0000000..5be9ffe
--- /dev/null
+++ b/src/gif/gifenc.c
@@ -0,0 +1,316 @@
+#include "gifenc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+/* helper to write a little-endian 16-bit number portably */
+#define write_num(fd, n) write((fd), (uint8_t []) {(n) & 0xFF, (n) >> 8}, 2)
+
+static uint8_t vga[0x30] = {
+    0x00, 0x00, 0x00,
+    0xAA, 0x00, 0x00,
+    0x00, 0xAA, 0x00,
+    0xAA, 0x55, 0x00,
+    0x00, 0x00, 0xAA,
+    0xAA, 0x00, 0xAA,
+    0x00, 0xAA, 0xAA,
+    0xAA, 0xAA, 0xAA,
+    0x55, 0x55, 0x55,
+    0xFF, 0x55, 0x55,
+    0x55, 0xFF, 0x55,
+    0xFF, 0xFF, 0x55,
+    0x55, 0x55, 0xFF,
+    0xFF, 0x55, 0xFF,
+    0x55, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF,
+};
+
+struct Node {
+    uint16_t key;
+    struct Node *children[];
+};
+typedef struct Node Node;
+
+static Node *
+new_node(uint16_t key, int degree)
+{
+    Node *node = calloc(1, sizeof(*node) + degree * sizeof(Node *));
+    if (node)
+        node->key = key;
+    return node;
+}
+
+static Node *
+new_trie(int degree, int *nkeys)
+{
+    Node *root = new_node(0, degree);
+    /* Create nodes for single pixels. */
+    for (*nkeys = 0; *nkeys < degree; (*nkeys)++)
+        root->children[*nkeys] = new_node(*nkeys, degree);
+    *nkeys += 2; /* skip clear code and stop code */
+    return root;
+}
+
+static void
+del_trie(Node *root, int degree)
+{
+    if (!root)
+        return;
+    for (int i = 0; i < degree; i++)
+        del_trie(root->children[i], degree);
+    free(root);
+}
+
+#define write_and_store(s, dst, fd, src, n) \
+do { \
+    write(fd, src, n); \
+    if (s) { \
+        memcpy(dst, src, n); \
+        dst += n; \
+    } \
+} while (0);
+
+static void put_loop(ge_GIF *gif, uint16_t loop);
+
+ge_GIF *
+ge_new_gif(
+    const char *fname, uint16_t width, uint16_t height,
+    uint8_t *palette, int depth, int loop
+)
+{
+    int i, r, g, b, v;
+    int store_gct, custom_gct;
+    ge_GIF *gif = calloc(1, sizeof(*gif) + 2*width*height);
+    if (!gif)
+        goto no_gif;
+    gif->w = width; gif->h = height;
+    gif->frame = (uint8_t *) &gif[1];
+    gif->back = &gif->frame[width*height];
+#ifdef _WIN32
+    gif->fd = creat(fname, S_IWRITE);
+#else
+    gif->fd = creat(fname, 0666);
+#endif
+    if (gif->fd == -1)
+        goto no_fd;
+#ifdef _WIN32
+    setmode(gif->fd, O_BINARY);
+#endif
+    write(gif->fd, "GIF89a", 6);
+    write_num(gif->fd, width);
+    write_num(gif->fd, height);
+    store_gct = custom_gct = 0;
+    if (palette) {
+        if (depth < 0)
+            store_gct = 1;
+        else
+            custom_gct = 1;
+    }
+    if (depth < 0)
+        depth = -depth;
+    gif->depth = depth > 1 ? depth : 2;
+    write(gif->fd, (uint8_t []) {0xF0 | (depth-1), 0x00, 0x00}, 3);
+    if (custom_gct) {
+        write(gif->fd, palette, 3 << depth);
+    } else if (depth <= 4) {
+        write_and_store(store_gct, palette, gif->fd, vga, 3 << depth);
+    } else {
+        write_and_store(store_gct, palette, gif->fd, vga, sizeof(vga));
+        i = 0x10;
+        for (r = 0; r < 6; r++) {
+            for (g = 0; g < 6; g++) {
+                for (b = 0; b < 6; b++) {
+                    write_and_store(store_gct, palette, gif->fd,
+                      ((uint8_t []) {r*51, g*51, b*51}), 3
+                    );
+                    if (++i == 1 << depth)
+                        goto done_gct;
+                }
+            }
+        }
+        for (i = 1; i <= 24; i++) {
+            v = i * 0xFF / 25;
+            write_and_store(store_gct, palette, gif->fd,
+              ((uint8_t []) {v, v, v}), 3
+            );
+        }
+    }
+done_gct:
+    if (loop >= 0 && loop <= 0xFFFF)
+        put_loop(gif, (uint16_t) loop);
+    return gif;
+no_fd:
+    free(gif);
+no_gif:
+    return NULL;
+}
+
+static void
+put_loop(ge_GIF *gif, uint16_t loop)
+{
+    write(gif->fd, (uint8_t []) {'!', 0xFF, 0x0B}, 3);
+    write(gif->fd, "NETSCAPE2.0", 11);
+    write(gif->fd, (uint8_t []) {0x03, 0x01}, 2);
+    write_num(gif->fd, loop);
+    write(gif->fd, "\0", 1);
+}
+
+/* Add packed key to buffer, updating offset and partial.
+ *   gif->offset holds position to put next *bit*
+ *   gif->partial holds bits to include in next byte */
+static void
+put_key(ge_GIF *gif, uint16_t key, int key_size)
+{
+    int byte_offset, bit_offset, bits_to_write;
+    byte_offset = gif->offset / 8;
+    bit_offset = gif->offset % 8;
+    gif->partial |= ((uint32_t) key) << bit_offset;
+    bits_to_write = bit_offset + key_size;
+    while (bits_to_write >= 8) {
+        gif->buffer[byte_offset++] = gif->partial & 0xFF;
+        if (byte_offset == 0xFF) {
+            write(gif->fd, "\xFF", 1);
+            write(gif->fd, gif->buffer, 0xFF);
+            byte_offset = 0;
+        }
+        gif->partial >>= 8;
+        bits_to_write -= 8;
+    }
+    gif->offset = (gif->offset + key_size) % (0xFF * 8);
+}
+
+static void
+end_key(ge_GIF *gif)
+{
+    int byte_offset;
+    byte_offset = gif->offset / 8;
+    if (gif->offset % 8)
+        gif->buffer[byte_offset++] = gif->partial & 0xFF;
+    if (byte_offset) {
+        write(gif->fd, (uint8_t []) {byte_offset}, 1);
+        write(gif->fd, gif->buffer, byte_offset);
+    }
+    write(gif->fd, "\0", 1);
+    gif->offset = gif->partial = 0;
+}
+
+static void
+put_image(ge_GIF *gif, uint16_t w, uint16_t h, uint16_t x, uint16_t y)
+{
+    int nkeys, key_size, i, j;
+    Node *node, *child, *root;
+    int degree = 1 << gif->depth;
+
+    write(gif->fd, ",", 1);
+    write_num(gif->fd, x);
+    write_num(gif->fd, y);
+    write_num(gif->fd, w);
+    write_num(gif->fd, h);
+    write(gif->fd, (uint8_t []) {0x00, gif->depth}, 2);
+    root = node = new_trie(degree, &nkeys);
+    key_size = gif->depth + 1;
+    put_key(gif, degree, key_size); /* clear code */
+    for (i = y; i < y+h; i++) {
+        for (j = x; j < x+w; j++) {
+            uint8_t pixel = gif->frame[i*gif->w+j] & (degree - 1);
+            child = node->children[pixel];
+            if (child) {
+                node = child;
+            } else {
+                put_key(gif, node->key, key_size);
+                if (nkeys < 0x1000) {
+                    if (nkeys == (1 << key_size))
+                        key_size++;
+                    node->children[pixel] = new_node(nkeys++, degree);
+                } else {
+                    put_key(gif, degree, key_size); /* clear code */
+                    del_trie(root, degree);
+                    root = node = new_trie(degree, &nkeys);
+                    key_size = gif->depth + 1;
+                }
+                node = root->children[pixel];
+            }
+        }
+    }
+    put_key(gif, node->key, key_size);
+    put_key(gif, degree + 1, key_size); /* stop code */
+    end_key(gif);
+    del_trie(root, degree);
+}
+
+static int
+get_bbox(ge_GIF *gif, uint16_t *w, uint16_t *h, uint16_t *x, uint16_t *y)
+{
+    int i, j, k;
+    int left, right, top, bottom;
+    left = gif->w; right = 0;
+    top = gif->h; bottom = 0;
+    k = 0;
+    for (i = 0; i < gif->h; i++) {
+        for (j = 0; j < gif->w; j++, k++) {
+            if (gif->frame[k] != gif->back[k]) {
+                if (j < left)   left    = j;
+                if (j > right)  right   = j;
+                if (i < top)    top     = i;
+                if (i > bottom) bottom  = i;
+            }
+        }
+    }
+    if (left != gif->w && top != gif->h) {
+        *x = left; *y = top;
+        *w = right - left + 1;
+        *h = bottom - top + 1;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static void
+set_delay(ge_GIF *gif, uint16_t d)
+{
+    write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4);
+    write_num(gif->fd, d);
+    write(gif->fd, "\0\0", 2);
+}
+
+void
+ge_add_frame(ge_GIF *gif, uint16_t delay)
+{
+    uint16_t w, h, x, y;
+    uint8_t *tmp;
+
+    if (delay)
+        set_delay(gif, delay);
+    if (gif->nframes == 0) {
+        w = gif->w;
+        h = gif->h;
+        x = y = 0;
+    } else if (!get_bbox(gif, &w, &h, &x, &y)) {
+        /* image's not changed; save one pixel just to add delay */
+        w = h = 1;
+        x = y = 0;
+    }
+    put_image(gif, w, h, x, y);
+    gif->nframes++;
+    tmp = gif->back;
+    gif->back = gif->frame;
+    gif->frame = tmp;
+}
+
+void
+ge_close_gif(ge_GIF* gif)
+{
+    write(gif->fd, ";", 1);
+    close(gif->fd);
+    free(gif);
+}
diff --git a/src/gif/gifenc.h b/src/gif/gifenc.h
new file mode 100644
index 0000000..9b63c7e
--- /dev/null
+++ b/src/gif/gifenc.h
@@ -0,0 +1,31 @@
+#ifndef GIFENC_H
+#define GIFENC_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct ge_GIF {
+    uint16_t w, h;
+    int depth;
+    int fd;
+    int offset;
+    int nframes;
+    uint8_t *frame, *back;
+    uint32_t partial;
+    uint8_t buffer[0xFF];
+} ge_GIF;
+
+ge_GIF *ge_new_gif(
+    const char *fname, uint16_t width, uint16_t height,
+    uint8_t *palette, int depth, int loop
+);
+void ge_add_frame(ge_GIF *gif, uint16_t delay);
+void ge_close_gif(ge_GIF* gif);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* GIFENC_H */
diff --git a/src/interface.cpp b/src/interface.cpp
index 30fe9a4..59a484d 100644
--- a/src/interface.cpp
+++ b/src/interface.cpp
@@ -1,4 +1,4 @@
-#include "interface.hpp"
+#include "interface.hpp"
 #include "ui_interface.h"
 
 #include "savingdialog.hpp"
@@ -13,6 +13,8 @@
 #include "neighborhoodDialog.hpp"
 #include "colorlabel.h"
 
+#include "gif/gifenc.h"
+
 #include <QtGlobal>
 #include <QJsonArray>
 #include <QDate>
@@ -78,6 +80,8 @@ MainWindow::MainWindow(QWidget *parent)
             {
                 load_from_image();
             });
+    connect(ui->action_save_image, &QAction::triggered, this, &MainWindow::save_as_image);
+    connect(ui->action_save_gif, &QAction::triggered, this, &MainWindow::save_as_gif);
     connect(ui->border_combo, QOverload<int>::of(&QComboBox::activated), this, &MainWindow::load_boundary_policy);
 
     ui->struct_library->update_cell_pixel_size(ui->grid_view->cell_screen_size());
@@ -192,6 +196,62 @@ QString MainWindow::afficher_interface_sauvegarde_structure()
     return filename;
 }
 
+void MainWindow::save_as_image()
+{
+    QString filename = QFileDialog::getSaveFileName(this, tr("Save image"),"",tr("PNG (*.png);;JPG (*.jpg);;BMP (*.bmp)"));
+    if (filename.isEmpty())
+        return;
+
+    QImage img = ui->grid_view->grid_image();
+    if (!img.save(filename))
+        QMessageBox::warning(this, "Error", "Could not save file");
+}
+
+void MainWindow::save_as_gif()
+{
+    const unsigned width  = ui->grid_view->get_grid().get_col();
+    const unsigned height = ui->grid_view->get_grid().get_rows();
+    const unsigned delay = 100/ui->simSpeedSlider->value(); // hundredths of second
+    const Alphabet& alph = ui->grid_view->alphabet();
+    unsigned state_count = alph.taille();
+    unsigned palette_depth = 0;
+    while (state_count >>= 1) ++palette_depth; // log2
+
+    QString filename = QFileDialog::getSaveFileName(this, tr("Save GIF"),"",tr("GIF (*.gif)"));
+    if (filename.isEmpty())
+        return;
+
+    std::vector<uint8_t> palette;
+    for (unsigned i = 0; i < alph.taille(); ++i)
+    {
+        palette.push_back(alph.getState(i).getColor().getRed());
+        palette.push_back(alph.getState(i).getColor().getGreen());
+        palette.push_back(alph.getState(i).getColor().getBlue());
+    }
+
+    ge_GIF* gif = ge_new_gif(filename.toStdString().c_str(), width, height, palette.data(), palette_depth, 0);
+
+    const History& hist = simulation.getHistory();
+    for (unsigned k = 0; k < hist.size(); ++k)
+    {
+        Grid grid = hist.at(k);
+        // L'historique contient une grid de taille différente, on l'ignore
+        if (grid.get_col() != width || grid.get_rows() != height)
+            continue;
+
+        for(int i = 0; i < (int)height; ++i)
+        {
+            for(int j = 0; j < (int)width; ++j)
+            {
+                gif->frame[i*width + j] = grid.get_state({j,i});
+            }
+        }
+        ge_add_frame(gif, delay);
+    }
+
+    ge_close_gif(gif);
+}
+
 
 void MainWindow::on_validateGridDim_clicked()
 {
diff --git a/src/src.pro b/src/src.pro
index bef270a..ad38b46 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -40,6 +40,7 @@ SOURCES += \
     interface.cpp \
     grid.cpp \
     history.cpp \
+    gif/gifenc.c \
     neighborhood_rules/vonNeumannNeighborhoodRule.cpp \
     structurewriter.cpp \
     structurelibraryview.cpp \
-- 
GitLab