Animated GIF export basics working

Tux Paint can export an animated GIF to the export directory
(e.g., ~/Pictures/TuxPaint) from the Open->Slideshow dialog.

To do -- GIF's animated speed should be based on speed slider
in Slideshow dialog.

To do -- Document this new feature (and single image (PNG) export)
in the README documentation.
This commit is contained in:
Bill Kendrick 2020-07-27 00:29:53 -07:00
parent 4ba4c11911
commit 43edf9fe3e
2 changed files with 176 additions and 65 deletions

View file

@ -8,20 +8,22 @@ http://www.tuxpaint.org/
$Id$
2020.July.26 (0.9.25)
2020.July.27 (0.9.25)
* New Features
------------
* Export drawings:
* GIF export option from the Open -> Slideshow dialog.
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/191/)
* Utilizes "gifenc", public domain by Marcel Rodrigues:
https://github.com/lecram/gifenc
* Depends on "libimagequant", from the "pngquant2" project:
https://github.com/ImageOptim/libimagequant
(GPL v3 or later, for Free/Libre Open Source Software)
* WORK IN PROGRESS
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/191/)
* WORK IN PROGRESS -- Ignores speed
* WORK IN PROGRESS -- Needs documentation
* Single image (PNG) export from the Open dialog.
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/192/)
* WORK IN PROGRESS -- Needs documentation
* Both utilize XDG user dirs settings, if available, to determine
where to store them, or "~/Pictures", otherwise. In those cases,
files are stored in a new "TuxPaint" subdirectory.

View file

@ -22,7 +22,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(See COPYING.txt)
June 14, 2002 - July 26, 2020
June 14, 2002 - July 27, 2020
*/
@ -1987,6 +1987,7 @@ static void play_slideshow(int *selected, int num_selected, char *dirname, char
static void draw_selection_digits(int right, int bottom, int n);
static int export_gif(int *selected, int num_selected, char *dirname, char **d_names, char **d_exts, int speed);
int export_gif_monitor_events(void);
static int export_pict(char * fname);
static char * get_export_filepath(const char * ext);
@ -25371,14 +25372,23 @@ char * get_xdg_user_dir(const char * dir_type, const char * fallback) {
* @return int -- 0 if export failed or was aborted, 1 if successful
*/
static int export_gif(int *selected, int num_selected, char *dirname, char **d_names, char **d_exts, int speed) {
char * gif_fname;
char fname[MAX_PATH];
int i, done, which;
SDL_Event event;
SDLKey key;
SDL_Surface *img;
char *tmp_starter_id, *tmp_template_id, *tmp_file_id;
int tmp_starter_mirrored, tmp_starter_flipped, tmp_starter_personal;
char * gif_fname;
char fname[MAX_PATH];
int i, j, done, which, x, y;
SDL_Surface *img;
int overall_w, overall_h, overall_area;
Uint8 * bitmap;
Uint8 r, g, b, a;
size_t pixels_size;
unsigned char *raw_8bit_pixels;
uint8_t gif_palette[768]; /* 256 x 3 */
liq_attr *liq_handle;
liq_image *input_image;
liq_result *quantization_result;
liq_error qtiz_status;
const liq_palette *palette;
/* Back up the current image's IDs, because they will get
clobbered below! */
@ -25399,72 +25409,139 @@ static int export_gif(int *selected, int num_selected, char *dirname, char **d_n
return FALSE;
}
/* FIXME: Open GIF */
/* For now, always saving GIF using the size of Tux Paint's window,
which is how images appear in the slide show */
overall_w = screen->w;
overall_h = screen->h;
overall_area = overall_w * overall_h;
done = 0;
for (i = 0; i < num_selected && !done; i++)
bitmap = malloc(num_selected * overall_area * 4);
if (bitmap != NULL)
{
which = selected[i];
show_progress_bar(screen);
/* Figure out filename: */
safe_snprintf(fname, sizeof(fname), "%s/%s%s", dirname, d_names[which], d_exts[which]);
img = myIMG_Load(fname);
if (img != NULL)
done = 0;
for (i = 0; i < num_selected && !done; i++)
{
autoscale_copy_smear_free(img, screen, SDL_BlitSurface);
safe_strncpy(file_id, d_names[which], sizeof(file_id));
/* See if this saved image was based on a 'starter' */
load_starter_id(d_names[which], NULL);
if (starter_id[0] != '\0')
which = selected[i];
show_progress_bar(screen);
/* Figure out filename: */
safe_snprintf(fname, sizeof(fname), "%s/%s%s", dirname, d_names[which], d_exts[which]);
/* Load and scale the image */
img = myIMG_Load(fname);
if (img != NULL)
{
load_starter(starter_id);
if (starter_mirrored)
mirror_starter();
if (starter_flipped)
flip_starter();
autoscale_copy_smear_free(img, screen, SDL_BlitSurface);
safe_strncpy(file_id, d_names[which], sizeof(file_id));
/* See if this saved image was based on a 'starter' */
load_starter_id(d_names[which], NULL);
if (starter_id[0] != '\0')
{
load_starter(starter_id);
if (starter_mirrored)
mirror_starter();
if (starter_flipped)
flip_starter();
}
else
load_template(template_id);
} else {
/* Error loading !*/
fprintf(stderr, "Error loading %s!\n", fname);
/* FIXME Abort? */
}
else
load_template(template_id);
} else {
/* Error loading !*/
fprintf(stderr, "Error loading %s!\n", fname);
/* FIXME */
/* Record the raw RGB into a big strip, to be quantized and sliced later */
for (y = 0; y < overall_h; y++) {
for (x = 0; x < overall_w; x++) {
SDL_GetRGBA(getpixels[screen->format->BytesPerPixel](screen, x, y), screen->format, &r, &g, &b, &a);
bitmap[((i * overall_area) + (y * overall_w) + x) * 4 + 0] = r;
bitmap[((i * overall_area) + (y * overall_w) + x) * 4 + 1] = g;
bitmap[((i * overall_area) + (y * overall_w) + x) * 4 + 2] = b;
bitmap[((i * overall_area) + (y * overall_w) + x) * 4 + 3] = 255;
}
}
SDL_Flip(screen);
done = export_gif_monitor_events();
}
SDL_Flip(screen);
while (SDL_PollEvent(&event))
if (!done)
{
if (event.type == SDL_QUIT)
{
done = 1;
/* Quantize to max 256 (8bpp) colors and generate a suitable palette */
liq_handle = liq_attr_create();
input_image = liq_image_create_rgba(liq_handle, bitmap, overall_w, num_selected * overall_h, 0);
liq_set_max_colors(liq_handle, 256);
show_progress_bar(screen);
done = export_gif_monitor_events();
qtiz_status = liq_image_quantize(input_image, liq_handle, &quantization_result);
done = (qtiz_status != LIQ_OK);
if (!done) {
// Use libimagequant to make new image pixels from the palette
pixels_size = num_selected * overall_area;
raw_8bit_pixels = malloc(pixels_size);
liq_set_dithering_level(quantization_result, 1.0);
liq_write_remapped_image(quantization_result, input_image, raw_8bit_pixels, pixels_size);
palette = liq_get_palette(quantization_result);
free(bitmap);
for (j = 0; j < (int) palette->count; j++) {
gif_palette[j * 3 + 0] = palette->entries[j].r;
gif_palette[j * 3 + 1] = palette->entries[j].g;
gif_palette[j * 3 + 2] = palette->entries[j].b;
}
else if (event.type == SDL_KEYDOWN)
{
key = event.key.keysym.sym;
if (key == SDLK_ESCAPE) {
done = 1;
/* Open GIF */
ge_GIF *gif = ge_new_gif(
gif_fname,
overall_w, overall_h,
gif_palette,
8, /* 256 colors */
0 /* infinite loop */
);
/* Export each frame */
for (i = 0; i < num_selected && !done; i++)
{
memcpy(gif->frame, raw_8bit_pixels + i * overall_area, overall_area);
ge_add_frame(gif, 100); // FIXME: Speed
show_progress_bar(screen);
done = export_gif_monitor_events();
}
}
}
SDL_Delay(10);
/* Close the GIF */
ge_close_gif(gif);
} else {
fprintf(stderr, "Quantization failed\n");
done = 1;
}
if (done)
{
/* Aborted; discard the partially-saved GIF */
unlink(gif_fname);
}
}
}
else
{
/* Out of memory! */
done = 1;
}
/* FIXME: Quantize */
/* FIXME: Export each frame */
/* FIXME: Close GIF */
/* Restore everything about the currently-active image
@ -25489,6 +25566,38 @@ static int export_gif(int *selected, int num_selected, char *dirname, char **d_n
return(!done);
}
/**
* Called by export_gif() while it's iterating through images
* in a few different ways, to monitor SDL event queue for
* any [Esc] keypress or quit event (e.g., closing window),
* which triggers an abort of the export.
*
* @return int 0 = keep going, 1 = abort
*/
int export_gif_monitor_events(void) {
int done;
SDL_Event event;
SDLKey key;
done = 0;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
done = 1;
}
else if (event.type == SDL_KEYDOWN)
{
key = event.key.keysym.sym;
if (key == SDLK_ESCAPE) {
done = 1;
}
}
}
SDL_Delay(10);
return done;
}
/**
* Copy an image (just the main PNG) from Tux Paint's "saved"
* directory to the user's chosen export directory