Commit a1042598 authored by Yann Boucher's avatar Yann Boucher
Browse files

Support for saving images and animated GIF files

parent 41cb870f
Pipeline #79726 passed with stages
in 18 seconds
...@@ -138,6 +138,9 @@ ...@@ -138,6 +138,9 @@
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum">
<number>100</number>
</property>
<property name="value"> <property name="value">
<number>20</number> <number>20</number>
</property> </property>
...@@ -621,7 +624,10 @@ pattern recorded :</string> ...@@ -621,7 +624,10 @@ pattern recorded :</string>
<property name="title"> <property name="title">
<string>Edit</string> <string>Edit</string>
</property> </property>
<addaction name="separator"/>
<addaction name="actionCharger_depuis_une_image"/> <addaction name="actionCharger_depuis_une_image"/>
<addaction name="action_save_image"/>
<addaction name="action_save_gif"/>
<addaction name="action_save_struct"/> <addaction name="action_save_struct"/>
</widget> </widget>
<widget class="QMenu" name="menuA_propos"> <widget class="QMenu" name="menuA_propos">
...@@ -682,6 +688,16 @@ pattern recorded :</string> ...@@ -682,6 +688,16 @@ pattern recorded :</string>
<string>Play Snake</string> <string>Play Snake</string>
</property> </property>
</action> </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> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
......
...@@ -139,6 +139,9 @@ public: ...@@ -139,6 +139,9 @@ public:
//! \brief Annule la dernière opération (effectue un Ctrl-Z). //! \brief Annule la dernière opération (effectue un Ctrl-Z).
void undo(); void undo();
//! \brief Retourne une QImage représentant la grille actuelle.
const QImage &grid_image() const;
signals: signals:
//! \brief Signal émis quand le zoom change. //! \brief Signal émis quand le zoom change.
//! \param cell_size la nouvelle taille à l'écran en pixels d'une cellule //! \param cell_size la nouvelle taille à l'écran en pixels d'une cellule
...@@ -159,8 +162,6 @@ private: ...@@ -159,8 +162,6 @@ private:
void click_on(Coord coord); void click_on(Coord coord);
void update_current_mouse_pos(Coord coord); void update_current_mouse_pos(Coord coord);
const QImage &grid_image() const;
private slots: private slots:
void handle_rubberband(QRect, QPointF, QPointF); void handle_rubberband(QRect, QPointF, QPointF);
......
...@@ -48,6 +48,18 @@ public: ...@@ -48,6 +48,18 @@ public:
//! \return Retourne le nombre de lignes de la Grille //! \return Retourne le nombre de lignes de la Grille
unsigned int get_nbMax()const{return nbMax;} 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 //! \brief Ajoute une grille dans l'historique
void pushGrid(const Grid& g); void pushGrid(const Grid& g);
......
...@@ -54,6 +54,12 @@ private slots: ...@@ -54,6 +54,12 @@ private slots:
//! \returns Le nom du fichier choisi //! \returns Le nom du fichier choisi
QString afficher_interface_sauvegarde_structure(); 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); void on_nbrStateComboBox_currentTextChanged(const QString &arg1);
//! \brief Créer une nouvelle grille ayant des états aléatoire //! \brief Créer une nouvelle grille ayant des états aléatoire
......
...@@ -61,7 +61,9 @@ public: ...@@ -61,7 +61,9 @@ public:
//! Vide également l'historique //! Vide également l'historique
void setHistorySize(unsigned int size) {hist = History(size);} 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 //! \brief Définit la grille
//! //!
......
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.
#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);
}
#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
);