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:
parent
4ba4c11911
commit
43edf9fe3e
2 changed files with 176 additions and 65 deletions
|
|
@ -8,20 +8,22 @@ http://www.tuxpaint.org/
|
||||||
|
|
||||||
$Id$
|
$Id$
|
||||||
|
|
||||||
2020.July.26 (0.9.25)
|
2020.July.27 (0.9.25)
|
||||||
* New Features
|
* New Features
|
||||||
------------
|
------------
|
||||||
* Export drawings:
|
* Export drawings:
|
||||||
* GIF export option from the Open -> Slideshow dialog.
|
* GIF export option from the Open -> Slideshow dialog.
|
||||||
|
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/191/)
|
||||||
* Utilizes "gifenc", public domain by Marcel Rodrigues:
|
* Utilizes "gifenc", public domain by Marcel Rodrigues:
|
||||||
https://github.com/lecram/gifenc
|
https://github.com/lecram/gifenc
|
||||||
* Depends on "libimagequant", from the "pngquant2" project:
|
* Depends on "libimagequant", from the "pngquant2" project:
|
||||||
https://github.com/ImageOptim/libimagequant
|
https://github.com/ImageOptim/libimagequant
|
||||||
(GPL v3 or later, for Free/Libre Open Source Software)
|
(GPL v3 or later, for Free/Libre Open Source Software)
|
||||||
* WORK IN PROGRESS
|
* WORK IN PROGRESS -- Ignores speed
|
||||||
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/191/)
|
* WORK IN PROGRESS -- Needs documentation
|
||||||
* Single image (PNG) export from the Open dialog.
|
* Single image (PNG) export from the Open dialog.
|
||||||
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/192/)
|
(Closes https://sourceforge.net/p/tuxpaint/feature-requests/192/)
|
||||||
|
* WORK IN PROGRESS -- Needs documentation
|
||||||
* Both utilize XDG user dirs settings, if available, to determine
|
* Both utilize XDG user dirs settings, if available, to determine
|
||||||
where to store them, or "~/Pictures", otherwise. In those cases,
|
where to store them, or "~/Pictures", otherwise. In those cases,
|
||||||
files are stored in a new "TuxPaint" subdirectory.
|
files are stored in a new "TuxPaint" subdirectory.
|
||||||
|
|
|
||||||
233
src/tuxpaint.c
233
src/tuxpaint.c
|
|
@ -22,7 +22,7 @@
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
(See COPYING.txt)
|
(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 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);
|
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 int export_pict(char * fname);
|
||||||
static char * get_export_filepath(const char * ext);
|
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
|
* @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) {
|
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;
|
char *tmp_starter_id, *tmp_template_id, *tmp_file_id;
|
||||||
int tmp_starter_mirrored, tmp_starter_flipped, tmp_starter_personal;
|
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
|
/* Back up the current image's IDs, because they will get
|
||||||
clobbered below! */
|
clobbered below! */
|
||||||
|
|
@ -25399,72 +25409,139 @@ static int export_gif(int *selected, int num_selected, char *dirname, char **d_n
|
||||||
return FALSE;
|
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;
|
||||||
|
|
||||||
|
bitmap = malloc(num_selected * overall_area * 4);
|
||||||
done = 0;
|
if (bitmap != NULL)
|
||||||
|
|
||||||
for (i = 0; i < num_selected && !done; i++)
|
|
||||||
{
|
{
|
||||||
which = selected[i];
|
done = 0;
|
||||||
show_progress_bar(screen);
|
|
||||||
|
for (i = 0; i < num_selected && !done; i++)
|
||||||
|
|
||||||
/* 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)
|
|
||||||
{
|
{
|
||||||
autoscale_copy_smear_free(img, screen, SDL_BlitSurface);
|
which = selected[i];
|
||||||
|
show_progress_bar(screen);
|
||||||
safe_strncpy(file_id, d_names[which], sizeof(file_id));
|
|
||||||
|
|
||||||
|
/* Figure out filename: */
|
||||||
/* See if this saved image was based on a 'starter' */
|
safe_snprintf(fname, sizeof(fname), "%s/%s%s", dirname, d_names[which], d_exts[which]);
|
||||||
load_starter_id(d_names[which], NULL);
|
|
||||||
if (starter_id[0] != '\0')
|
/* Load and scale the image */
|
||||||
|
img = myIMG_Load(fname);
|
||||||
|
|
||||||
|
if (img != NULL)
|
||||||
{
|
{
|
||||||
load_starter(starter_id);
|
autoscale_copy_smear_free(img, screen, SDL_BlitSurface);
|
||||||
|
|
||||||
if (starter_mirrored)
|
safe_strncpy(file_id, d_names[which], sizeof(file_id));
|
||||||
mirror_starter();
|
|
||||||
|
/* See if this saved image was based on a 'starter' */
|
||||||
if (starter_flipped)
|
load_starter_id(d_names[which], NULL);
|
||||||
flip_starter();
|
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);
|
/* Record the raw RGB into a big strip, to be quantized and sliced later */
|
||||||
} else {
|
for (y = 0; y < overall_h; y++) {
|
||||||
/* Error loading !*/
|
for (x = 0; x < overall_w; x++) {
|
||||||
fprintf(stderr, "Error loading %s!\n", fname);
|
SDL_GetRGBA(getpixels[screen->format->BytesPerPixel](screen, x, y), screen->format, &r, &g, &b, &a);
|
||||||
/* FIXME */
|
|
||||||
|
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)
|
/* Quantize to max 256 (8bpp) colors and generate a suitable palette */
|
||||||
{
|
liq_handle = liq_attr_create();
|
||||||
done = 1;
|
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)
|
|
||||||
{
|
/* Open GIF */
|
||||||
key = event.key.keysym.sym;
|
ge_GIF *gif = ge_new_gif(
|
||||||
if (key == SDLK_ESCAPE) {
|
gif_fname,
|
||||||
done = 1;
|
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();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
/* Close the GIF */
|
||||||
SDL_Delay(10);
|
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
|
/* 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);
|
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"
|
* Copy an image (just the main PNG) from Tux Paint's "saved"
|
||||||
* directory to the user's chosen export directory
|
* directory to the user's chosen export directory
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue