summaryrefslogtreecommitdiff
path: root/examples/animgifimage.cxx
diff options
context:
space:
mode:
authorwcout <wcout@users.noreply.github.com>2023-01-21 17:27:58 +0100
committerGitHub <noreply@github.com>2023-01-21 17:27:58 +0100
commit2ddfd9d9492d9fc1df111ec9211dd1be4d424c35 (patch)
treec766d0dfb3a2d7a75c275db2821d5bcf0e935a15 /examples/animgifimage.cxx
parent1fc269b0d4c79b256cc57740d318f95dded8c340 (diff)
Animated GIF support (Fl_Anim_GIF_Image class) (#375)
Diffstat (limited to 'examples/animgifimage.cxx')
-rw-r--r--examples/animgifimage.cxx303
1 files changed, 303 insertions, 0 deletions
diff --git a/examples/animgifimage.cxx b/examples/animgifimage.cxx
new file mode 100644
index 000000000..515b76b65
--- /dev/null
+++ b/examples/animgifimage.cxx
@@ -0,0 +1,303 @@
+//
+// Test program for displaying animated GIF files using the
+// Fl_Anim_GIF_Image class.
+//
+#include <FL/Fl_Anim_GIF_Image.H>
+
+#include <FL/Fl_Double_Window.H>
+#include <FL/Fl_File_Chooser.H>
+#include <FL/Fl_Shared_Image.H>
+#include <FL/Fl_Tiled_Image.H>
+#include <stdlib.h>
+
+static int g_good_count = 0, g_bad_count = 0, g_frame_count = 0;
+
+static const Fl_Color BackGroundColor = FL_GRAY; // use e.g. FL_RED to see
+ // transparent parts better
+static const double RedrawDelay = 1./20; // interval [sec] for forced redraw
+
+static void quit_cb(Fl_Widget* w_, void*) {
+ exit(0);
+}
+
+static void set_title(Fl_Window *win, Fl_Anim_GIF_Image *animgif) {
+ char buf[200];
+ snprintf(buf, sizeof(buf), "%s (%d frames) %2.2fx", fl_filename_name(animgif->name()),
+ animgif->frames(), animgif->speed());
+ if (animgif->frame_uncache())
+ strcat(buf, " U");
+ win->copy_label(buf);
+ win->copy_tooltip(buf);
+}
+
+static void cb_forced_redraw(void *d) {
+ Fl_Window *win = Fl::first_window();
+ while (win) {
+ if (!win->menu_window())
+ win->redraw();
+ win = Fl::next_window(win);
+ }
+ if (Fl::first_window())
+ Fl::repeat_timeout(RedrawDelay, cb_forced_redraw);
+}
+
+Fl_Window *openFile(const char *name, char *flags, bool close = false) {
+ // determine test options from 'flags'
+ bool uncache = strchr(flags, 'u');
+ char *d = flags - 1;
+ int debug = 0;
+ while ((d = strchr(++d, 'd'))) debug++;
+ bool optimize_mem = strchr(flags, 'm');
+ bool desaturate = strchr(flags, 'D');
+ bool average = strchr(flags, 'A');
+ bool test_tiles = strchr(flags, 'T');
+ bool test_forced_redraw = strchr(flags, 'f');
+ char *r = strchr(flags, 'r');
+ bool resizable = r && !test_tiles;
+ double scale = 1.0;
+ if (r && resizable) scale = atof(r+1);
+ if (scale <= 0.1 || scale > 5)
+ scale = resizable ? 0.7 : 1.0;
+
+ // setup window
+ Fl::remove_timeout(cb_forced_redraw);
+ Fl_Double_Window *win = new Fl_Double_Window(300, 300);
+ win->color(BackGroundColor);
+ if (close)
+ win->callback(quit_cb);
+ printf("Loading '%s'%s%s ... ", name,
+ uncache ? " (uncached)" : "",
+ optimize_mem ? " (optimized)" : "");
+
+ // create a canvas for the animation
+ Fl_Box *canvas = test_tiles ? 0 : new Fl_Box(0, 0, 0, 0); // canvas will be resized by animation
+ Fl_Box *canvas2 = 0;
+ unsigned short gif_flags = debug ? Fl_Anim_GIF_Image::LOG_FLAG : 0;
+ if (debug > 1)
+ gif_flags |= Fl_Anim_GIF_Image::DEBUG_FLAG;
+ if (optimize_mem)
+ gif_flags |= Fl_Anim_GIF_Image::OPTIMIZE_MEMORY;
+
+ // create animation, specifying this canvas as display widget
+ Fl_Anim_GIF_Image *animgif = new Fl_Anim_GIF_Image(name, canvas, gif_flags);
+ bool good( animgif->ld() == 0 && animgif->valid() );
+ printf("%s: %d x %d (%d frames) %s\n",
+ animgif->name(), animgif->w(), animgif->h(), animgif->frames(), good ? "OK" : "ERROR");
+ // for the statistics (when run on testsuite):
+ g_good_count += good;
+ g_bad_count += !good;
+ g_frame_count += animgif->frames();
+
+ win->user_data(animgif); // store address of image (see note in main())
+
+ // exercise the optional tests on the animation
+ animgif->frame_uncache(uncache);
+ if (scale != 1.0) {
+ animgif->resize(scale);
+ printf("TEST: resized %s by %.2f to %d x %d\n", animgif->name(), scale, animgif->w(), animgif->h());
+ }
+ if (average) {
+ printf("TEST: color_average %s\n", animgif->name());
+ animgif->color_average(FL_GREEN, 0.5); // currently hardcoded
+ }
+ if (desaturate) {
+ printf("TEST: desaturate %s\n", animgif->name());
+ animgif->desaturate();
+ }
+ int W = animgif->w();
+ int H = animgif->h();
+ if (animgif->frames()) {
+ if (test_tiles) {
+ // demonstrate a way how to use the animation with Fl_Tiled_Image
+ printf("TEST: use %s as tiles\n", animgif->name());
+ W *= 2;
+ H *= 2;
+ Fl_Tiled_Image *tiled_image = new Fl_Tiled_Image(animgif);
+ Fl_Group *group = new Fl_Group(0, 0, win->w(), win->h());
+ group->image(tiled_image);
+ group->align(FL_ALIGN_INSIDE);
+ animgif->canvas(group, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS | Fl_Anim_GIF_Image::DONT_SET_AS_IMAGE );
+ win->resizable(group);
+ } else {
+ // demonstrate a way how to use same animation in another canvas simultaneously:
+ // as the current implementation allows only automatic redraw of one canvas..
+ if (test_forced_redraw) {
+ if (W < 400) {
+ printf("TEST: open %s in another animation with application redraw\n", animgif->name());
+ canvas2 = new Fl_Box(W, 0, animgif->w(), animgif->h()); // another canvas for animation
+ canvas2->image(animgif); // is set to same animation!
+ W *= 2;
+ Fl::add_timeout(RedrawDelay, cb_forced_redraw); // force periodic redraw
+ }
+ }
+ }
+ // make window resizable (must be done before show())
+ if (resizable && canvas && !test_tiles) {
+ win->resizable(win);
+ }
+ win->size(W, H); // change to actual size of canvas
+ // start the animation
+ win->end();
+ win->show();
+ win->wait_for_expose();
+ set_title(win, animgif);
+ if (resizable && !test_tiles) {
+ // need to reposition the widgets (have been moved by setting resizable())
+ if (canvas && canvas2) {
+ canvas->resize(0, 0, W/2, canvas->h());
+ canvas2->resize(W/2, 0, W/2, canvas2->h());
+ }
+ else if (canvas) {
+ canvas->resize(0, 0, animgif->canvas_w(), animgif->canvas_h());
+ }
+ }
+ win->init_sizes(); // IMPORTANT: otherwise weird things happen at Ctrl+/- scaling
+ } else {
+ delete win;
+ return 0;
+ }
+ if (debug >=3) {
+ // open each frame in a separate window
+ for (int i = 0; i < animgif->frames(); i++) {
+ char buf[200];
+ snprintf(buf, sizeof(buf), "Frame #%d", i + 1);
+ Fl_Double_Window *win = new Fl_Double_Window(animgif->w(), animgif->h());
+ win->copy_tooltip(buf);
+ win->copy_label(buf);
+ win->color(BackGroundColor);
+ int w = animgif->image(i)->w();
+ int h = animgif->image(i)->h();
+ // in 'optimize_mem' mode frames must be offsetted to canvas
+ int x = (w == animgif->w() && h == animgif->h()) ? 0 : animgif->frame_x(i);
+ int y = (w == animgif->w() && h == animgif->h()) ? 0 : animgif->frame_y(i);
+ Fl_Box *b = new Fl_Box(x, y, w, h);
+ // get the frame image
+ b->image(animgif->image(i));
+ win->end();
+ win->show();
+ }
+ }
+ return win;
+}
+
+#include <FL/filename.H>
+bool openDirectory(const char *dir, char *flags) {
+ dirent **list;
+ int nbr_of_files = fl_filename_list(dir, &list, fl_alphasort);
+ if (nbr_of_files <= 0)
+ return false;
+ int cnt = 0;
+ for (int i = 0; i < nbr_of_files; i++) {
+ char buf[512];
+ const char *name = list[i]->d_name;
+ if (!strcmp(name, ".") || !strcmp(name, "..")) continue;
+ const char *p = strstr(name, ".gif");
+ if (!p) p = strstr(name, ".GIF");
+ if (!p) continue;
+ if (*(p+4)) continue; // is no extension!
+ snprintf(buf, sizeof(buf), "%s/%s", dir, name);
+ if (strstr(name, "debug")) // hack: when name contains 'debug' open single frames
+ strcat(flags, "d");
+ if (openFile(buf, flags, cnt == 0))
+ cnt++;
+ }
+ return cnt != 0;
+}
+
+static void change_speed(double delta) {
+ Fl_Widget *below = Fl::belowmouse();
+ if (below && below->image()) {
+ Fl_Anim_GIF_Image *animgif = 0;
+ // Q: is there a way to determine Fl_Tiled_Image without using dynamic cast?
+ Fl_Tiled_Image *tiled = dynamic_cast<Fl_Tiled_Image *>(below->image());
+ animgif = tiled ?
+ dynamic_cast<Fl_Anim_GIF_Image *>(tiled->image()) :
+ dynamic_cast<Fl_Anim_GIF_Image *>(below->image());
+ if (animgif && animgif->playing()) {
+ double speed = animgif->speed();
+ if (!delta) speed = 1.;
+ else speed += delta;
+ if (speed < 0.1) speed = 0.1;
+ if (speed > 10) speed = 10;
+ animgif->speed(speed);
+ set_title(below->window(), animgif);
+ }
+ }
+}
+
+static int events(int event) {
+ if (event == FL_SHORTCUT) {
+ if (Fl::event_key() == '+')
+ change_speed(0.1);
+ else if (Fl::event_key() == '-')
+ change_speed(-0.1);
+ else if (Fl::event_key() == '0')
+ change_speed(0);
+ else
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+static const char testsuite[] = "testsuite";
+
+int main(int argc, char *argv[]) {
+ fl_register_images();
+ Fl::add_handler(events);
+ char *openFlags = (char *)calloc(1024, 1);
+ if (argc > 1) {
+ // started with argumemts
+ if (strstr(argv[1], "-h")) {
+ printf("Usage:\n"
+ " -t [directory] [-{flags}] open all files in directory (default name: %s) [with options]\n"
+ " filename [-{flags}] open single file [with options] \n"
+ " No arguments open a fileselector\n"
+ " {flags} can be: d=debug mode, u=uncached, D=desaturated, A=color averaged, T=tiled\n"
+ " m=minimal update, r[scale factor]=resize by 'scale factor'\n"
+ " Use keys '+'/'-/0' to change speed of the active image (belowmouse).\n", testsuite);
+ exit(1);
+ }
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-')
+ strcat(openFlags, &argv[i][1]);
+ }
+ if (strchr(openFlags, 't')) { // open all GIF-files in a given directory
+ const char *dir = testsuite;
+ for (int i = 2; i < argc; i++)
+ if (argv[i][0] != '-')
+ dir = argv[i];
+ openDirectory(dir, openFlags);
+ printf("Summary: good=%d, bad=%d, frames=%d\n", g_good_count, g_bad_count, g_frame_count);
+ } else { // open given file(s)
+ for (int i = 1; i < argc; i++)
+ if (argv[i][0] != '-')
+ openFile(argv[i], openFlags, strchr(openFlags, 'd'));
+ }
+ } else {
+ // started without arguments: choose file
+ Fl_GIF_Image::animate = true; // create animated shared .GIF images (e.g. file chooser)
+ while (1) {
+ Fl::add_timeout(0.1, cb_forced_redraw); // animate images in chooser
+ const char *filename = fl_file_chooser("Select a GIF image file","*.{gif,GIF}", NULL);
+ Fl::remove_timeout(cb_forced_redraw);
+ if (!filename)
+ break;
+ Fl_Window *win = openFile(filename, openFlags);
+ Fl::run();
+ // delete last window (which is now just hidden) to test destructors
+ // NOTE: it is essential that *before* doing this also the
+ // animated image is destroyed, otherwise it will crash
+ // because it's canvas will be gone.
+ // In order to keep this demo simple, the adress of the
+ // Fl_Anim_GIF_Image has been stored in the window's user_data.
+ // In a real-life application you will probably store
+ // it somewhere in the window's or canvas' object and destroy
+ // the image in the window's or canvas' destructor.
+ if (win && win->user_data())
+ delete ((Fl_Anim_GIF_Image *)win->user_data());
+ delete win;
+ }
+ }
+ return Fl::run();
+}