summaryrefslogtreecommitdiff
path: root/examples
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
parent1fc269b0d4c79b256cc57740d318f95dded8c340 (diff)
Animated GIF support (Fl_Anim_GIF_Image class) (#375)
Diffstat (limited to 'examples')
-rw-r--r--examples/CMakeLists.txt4
-rw-r--r--examples/Makefile6
-rw-r--r--examples/animgifimage-play.cxx237
-rw-r--r--examples/animgifimage-resize.cxx185
-rw-r--r--examples/animgifimage-simple.cxx40
-rw-r--r--examples/animgifimage.cxx303
6 files changed, 774 insertions, 1 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 2bc33557a..faa6d7daa 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -81,6 +81,10 @@ set (FLUID_SOURCES
############################################################
set (IMAGE_SOURCES
+ animgifimage
+ animgifimage-play
+ animgifimage-resize
+ animgifimage-simple
howto-simple-svg
)
diff --git a/examples/Makefile b/examples/Makefile
index dc312aa7a..946eda808 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -22,7 +22,11 @@ SHELL = /bin/sh
.SILENT:
# Executables
-ALL = browser-simple$(EXEEXT) \
+ALL = animgifimage$(EXEEXT) \
+ animgifimage-play$(EXEEXT) \
+ animgifimage-simple$(EXEEXT) \
+ animgifimage-resize$(EXEEXT) \
+ browser-simple$(EXEEXT) \
cairo-draw-x$(EXEEXT) \
chart-simple$(EXEEXT) \
draggable-group$(EXEEXT) \
diff --git a/examples/animgifimage-play.cxx b/examples/animgifimage-play.cxx
new file mode 100644
index 000000000..4a319f842
--- /dev/null
+++ b/examples/animgifimage-play.cxx
@@ -0,0 +1,237 @@
+//
+// Demonstrates how to play an animated GIF file
+// under application control frame by frame if
+// this is needed.
+// Also demonstrates how to use a single animation
+// object to load multiple animations.
+//
+// animgifimage <file> [-r] [-s speed_factor]
+//
+// Multiple files can be specified e.g. testsuite/*
+//
+// Use keys '+'/'-'/Enter to change speed, ' ' to pause.
+// Right key changes to next frame in paused mode.
+// 'n' changes to next file, 'r' toggles reverse play.
+//
+#include <FL/Fl_Anim_GIF_Image.H>
+#include <FL/Fl_Box.H>
+#include <FL/Fl_Double_Window.H>
+#include <FL/Fl.H>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static double speed_factor = 1.; // slow down/speed up playback by factor
+static bool reverse = false; // true = play animation backwards
+static bool paused = false; // flag for paused animation
+static bool frame_info = false; // flag to update current frame info in title
+static Fl_Anim_GIF_Image animgif; // the animation object
+static char **Argv = 0; // copy of main() argv[]
+static int Argc = 0; // copy of main() argc
+static int current_arg = 0; // current index in argv[]
+
+static int next_arg() {
+ while (1) {
+ current_arg++;
+ if (current_arg >= Argc) {
+ current_arg = 1;
+ }
+ if (Argv[current_arg]) break;
+ }
+ return current_arg;
+}
+
+static const char *next_file() {
+ while (Argv[next_arg()][0] == '-') ;
+ return Argv[current_arg];
+}
+
+static void set_title() {
+ char buf[200];
+ char fi[50];
+ if (frame_info)
+ snprintf(fi, sizeof(fi), "frame %d/%d", animgif.frame() + 1, animgif.frames());
+ else
+ snprintf(fi, sizeof(fi), "%d frames", animgif.frames());
+ snprintf(buf, sizeof(buf), "%s (%s) x %3.2f %s%s",
+ Argv[current_arg], fi,
+ speed_factor, reverse ? "reverse" : "",
+ paused ? " PAUSED" : "");
+
+ Fl::first_window()->copy_label(buf);
+}
+
+static void cb_anim(void *d_) {
+ Fl_Anim_GIF_Image *animgif = (Fl_Anim_GIF_Image *)d_;
+ int frame(animgif->frame());
+
+ // switch to next/previous frame
+ if (reverse) {
+ animgif->canvas()->window()->redraw();
+ frame--;
+ if (frame < 0) {
+ frame = animgif->frames() - 1;
+ }
+ }
+ else {
+ frame++;
+ if (frame >= animgif->frames()) {
+ frame = 0;
+ }
+ }
+ // set the frame (and update canvas)
+ animgif->frame(frame);
+
+ // setup timer for next frame
+ if (!paused && animgif->delay(frame)) {
+ Fl::repeat_timeout(animgif->delay(frame) / speed_factor, cb_anim, d_);
+ }
+ if (frame_info)
+ set_title();
+}
+
+static void next_frame() {
+ cb_anim(&animgif);
+}
+
+static void toggle_pause() {
+ paused = !paused;
+ set_title();
+ if (paused)
+ Fl::remove_timeout(cb_anim, &animgif);
+ else
+ next_frame();
+ set_title();
+}
+
+static void toggle_info() {
+ frame_info = !frame_info;
+ set_title();
+}
+
+static void toggle_reverse() {
+ reverse = !reverse;
+ set_title();
+}
+
+static void zoom(bool out) {
+ int W = animgif.w();
+ int H = animgif.h();
+ // Note: deliberately no range check (use key 'N' to reset)
+ static const double f = 1.05;
+ if (out)
+ animgif.resize((double)W/f, (double)H/f);
+ else
+ animgif.resize(f*W, f*H);
+}
+
+static void change_speed(int dir_) {
+ if (dir_> 0) {
+ speed_factor += (speed_factor < 1) ? 0.01 : 0.1;
+ if (speed_factor > 100)
+ speed_factor = 100.;
+ }
+ else if (dir_ < 0) {
+ speed_factor -= (speed_factor > 1) ? 0.1 : 0.01;
+ if (speed_factor < 0.01)
+ speed_factor = 0.01;
+ }
+ else {
+ speed_factor = 1.;
+ }
+ set_title();
+}
+
+static void load_next() {
+ Fl::remove_timeout(cb_anim, &animgif);
+ paused = false;
+ animgif.load(next_file());
+ animgif.canvas()->window()->redraw();
+ // check if loading succeeded
+ printf("valid: %d frames: %d\n", animgif.valid(), animgif.frames());
+ if (animgif.valid()) {
+ printf("play '%s'%s with %3.2f x speed\n", animgif.name(),
+ (reverse ? " reverse" : ""), speed_factor);
+ animgif.frame(reverse ? animgif.frames() - 1 : 0);
+ // setup first timeout, but check for zero-delay (normal GIF)!
+ if (animgif.delay(animgif.frame())) {
+ Fl::add_timeout(animgif.delay(animgif.frame()) / speed_factor, cb_anim, &animgif);
+ }
+ }
+ set_title();
+}
+
+static int events(int event_) {
+ if (event_ == FL_SHORTCUT && Fl::first_window()) {
+ switch (Fl::event_key()) {
+ case '+': change_speed(1); break;
+ case '-': change_speed(-1); break;
+ case FL_Enter: change_speed(0); break;
+ case 'n': load_next(); break;
+ case 'z': zoom(Fl::event_shift()); break;
+ case 'i': toggle_info(); break; // Note: this can raise cpu usage considerably!
+ case 'r': toggle_reverse(); break;
+ case ' ': toggle_pause(); break;
+ case FL_Right:
+ if (paused && Fl::get_key(FL_Right)) next_frame();
+ break;
+ default:
+ return 0;
+ }
+ Fl::first_window()->redraw();
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ // setup play parameters from args
+ Argv = argv;
+ Argc = argc;
+ int n = 0;
+ for (int i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-r"))
+ reverse = !reverse;
+ else if (!strcmp(argv[i], "-s") && i + 1 < argc) {
+ i++;
+ speed_factor = atof(argv[i]);
+ argv[i] = 0;
+ }
+ else if (argv[i][0] != '-') {
+ n++;
+ continue;
+ }
+ else {
+ printf("Invalid argument: '%s'\n", argv[i]);
+ exit(1);
+ }
+ }
+ if (!n) {
+ fprintf(stderr, "Test program for application controlled GIF animation.\n");
+ fprintf(stderr, "Please specify one or more image files!\n");
+ exit(0);
+ }
+ if (speed_factor < 0.01 || speed_factor > 100)
+ speed_factor = 1.;
+
+ Fl_Double_Window win(800, 600);
+
+ // prepare a canvas for the animation
+ // (we want to show it in the center of the window)
+ Fl_Box canvas(0, 0, win.w(), win.h());
+ Fl_Box help(0, win.h()-20, win.w(), 20, "Keys: N=next file, I=toggle info, R=play reverse, +/-/Enter/Space=change speed, Z=Zoom");
+ win.resizable(win);
+
+ win.end();
+ win.show();
+ Fl::add_handler(events);
+
+ // use the 'DONT_RESIZE_CANVAS' flag to tell the animation
+ // not to change the canvas size (which is the default).
+ unsigned short flags = Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS;
+// flags |= Fl_Anim_GIF_Image::DEBUG_FLAG|Fl_Anim_GIF_Image::LOG_FLAG;
+ animgif.canvas(&canvas, flags);
+
+ load_next();
+ return Fl::run();
+}
diff --git a/examples/animgifimage-resize.cxx b/examples/animgifimage-resize.cxx
new file mode 100644
index 000000000..1417255b3
--- /dev/null
+++ b/examples/animgifimage-resize.cxx
@@ -0,0 +1,185 @@
+//
+// Test program for Fl_Anim_GIF_Image::copy().
+//
+#include <FL/Fl_Anim_GIF_Image.H>
+#include <FL/Fl_Image.H>
+#include <FL/Fl_Box.H>
+#include <FL/Fl_Double_Window.H>
+#include <FL/Fl.H>
+#include <FL/fl_draw.H>
+#include <stdio.h>
+#include <stdlib.h>
+
+static Fl_Anim_GIF_Image *orig = 0;
+static bool draw_grid = true;
+
+static int events(int event_) {
+ if (event_ == FL_SHORTCUT && Fl::first_window()) {
+ if (Fl::event_key()=='g') {
+ draw_grid = !draw_grid;
+ printf("grid: %s\n", (draw_grid ? "ON" : "OFF"));
+ }
+ else if (Fl::event_key()=='b') {
+ if (Fl_Image::scaling_algorithm() != FL_RGB_SCALING_BILINEAR)
+ Fl_Image::scaling_algorithm(FL_RGB_SCALING_BILINEAR);
+ else
+ Fl_Image::scaling_algorithm(FL_RGB_SCALING_NEAREST);
+ printf("bilenear: %s\n", (Fl_Image::scaling_algorithm() != FL_RGB_SCALING_BILINEAR ? "OFF" : "ON"));
+ }
+ else
+ return 0;
+ Fl::first_window()->redraw();
+ }
+ return 1;
+}
+
+class Canvas : public Fl_Box {
+ typedef Fl_Box Inherited;
+public:
+ Canvas(int x, int y, int w, int h) :
+ Inherited(x, y, w, h) {}
+ void draw() FL_OVERRIDE {
+ if (draw_grid) {
+ // draw a transparency grid as background
+ static const Fl_Color C1 = fl_rgb_color(0xcc, 0xcc, 0xcc);
+ static const Fl_Color C2 = fl_rgb_color(0x88, 0x88, 0x88);
+ static const int SZ = 8;
+ for (int y = 0; y < h(); y += SZ) {
+ for (int x = 0; x < w(); x += SZ) {
+ fl_color(x%(SZ * 2) ? y%(SZ * 2) ? C1 : C2 : y%(SZ * 2) ? C2 : C1);
+ fl_rectf(x, y, 32, 32);
+ }
+ }
+ }
+ // draw the current image frame over the grid
+ Inherited::draw();
+ }
+ void do_resize(int W, int H) {
+ if (image() && (image()->w() != W || image()->h() != H)) {
+ Fl_Anim_GIF_Image *animgif = (Fl_Anim_GIF_Image *)image();
+ animgif->stop();
+ image(0);
+ // delete already copied images
+ if (animgif != orig ) {
+ delete animgif;
+ }
+ Fl_Anim_GIF_Image *copied = (Fl_Anim_GIF_Image *)orig->copy(W, H);
+ if (!copied->valid()) { // check success of copy
+ Fl::warning("Fl_Anim_GIF_Image::copy() %d x %d failed", W, H);
+ }
+ else {
+ printf("resized to %d x %d\n", copied->w(), copied->h());
+ }
+ copied->canvas(this, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS);
+ }
+ window()->cursor(FL_CURSOR_DEFAULT);
+ }
+ static void do_resize_cb(void *d) {
+ Canvas *c = (Canvas *)d;
+ c->do_resize(c->w(), c->h());
+ }
+ void resize(int x, int y, int w, int h) FL_OVERRIDE {
+ Inherited::resize(x, y, w, h);
+ // decouple resize event from actual resize operation
+ // to avoid lockups..
+ Fl::remove_timeout(do_resize_cb, this);
+ Fl::add_timeout(0.1, do_resize_cb, this);
+ window()->cursor(FL_CURSOR_WAIT);
+ }
+};
+
+int main(int argc, char *argv[]) {
+ // setup play parameters from args
+ const char *fileName = 0;
+ bool bilinear = false;
+ bool optimize = false;
+ bool uncache = false;
+ bool debug = false;
+ for (int i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-b")) // turn bilinear scaling on
+ bilinear = true;
+ else if (!strcmp(argv[i], "-o")) // turn optimize on
+ optimize = true;
+ else if (!strcmp(argv[i], "-g")) // disable grid
+ draw_grid = false;
+ else if (!strcmp(argv[i], "-u")) // uncache
+ uncache = true;
+ else if (!strcmp(argv[i], "-d")) // debug
+ debug = true;
+ else if (argv[i][0] != '-' && !fileName) {
+ fileName = argv[i];
+ }
+ else if (argv[i][0] == '-') {
+ printf("Invalid argument: '%s'\n", argv[i]);
+ exit(1);
+ }
+ }
+ if (!fileName) {
+ fprintf(stderr, "Test program for animated copy.\n");
+ fprintf(stderr, "Usage: %s fileName [-b]ilinear [-o]ptimize [-g]rid [-u]ncache\n", argv[0]);
+ exit(0);
+ }
+ Fl_Anim_GIF_Image::min_delay = 0.1; // set a minumum delay for playback
+
+ Fl_Double_Window win(640, 480);
+
+ // prepare a canvas for the animation
+ // (we want to show it in the center of the window)
+ Canvas canvas(0, 0, win.w(), win.h());
+ win.resizable(win);
+ win.size_range(1, 1);
+
+ win.end();
+ win.show();
+
+ // create/load the animated gif and start it immediately.
+ // We use the 'DONT_RESIZE_CANVAS' flag here to tell the
+ // animation not to change the canvas size (which is the default).
+ int flags = Fl_Anim_GIF_Image::Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS;
+ if (optimize) {
+ flags |= Fl_Anim_GIF_Image::OPTIMIZE_MEMORY;
+ printf("Using memory optimization (if image supports)\n");
+ }
+ if (debug) {
+ flags |= Fl_Anim_GIF_Image::DEBUG_FLAG;
+ }
+ orig = new Fl_Anim_GIF_Image(/*name_=*/ fileName,
+ /*canvas_=*/ &canvas,
+ /*flags_=*/ flags );
+
+ // check if loading succeeded
+ printf("%s: valid: %d frames: %d uncache: %d\n",
+ orig->name(), orig->valid(), orig->frames(), orig->frame_uncache());
+ if (orig->valid()) {
+ win.copy_label(fileName);
+
+ // print information about image optimization
+ int n = 0;
+ for (int i = 0; i < orig->frames(); i++) {
+ if (orig->frame_x(i) != 0 || orig->frame_y(i) != 0) n++;
+ }
+ printf("image has %d optimized frames\n", n);
+
+ Fl_Image::scaling_algorithm(FL_RGB_SCALING_NEAREST);
+ if (bilinear) {
+ Fl_Image::scaling_algorithm(FL_RGB_SCALING_BILINEAR);
+ printf("Using bilinear scaling - can be slow!\n");
+ // NOTE: this can be *really* slow with large sizes, if FLTK
+ // has to resize on its own without hardware scaling enabled.
+ }
+ orig->frame_uncache(uncache);
+ if (uncache) {
+ printf("Caching disabled - watch cpu load!\n");
+ }
+
+ // set initial size to fit into window
+ double ratio = orig->valid() ? (double)orig->w() / orig->h() : 1;
+ int W = win.w() - 40;
+ int H = (double)W / ratio;
+ printf("original size: %d x %d\n", orig->w(), orig->h());
+ win.size(W, H);
+ Fl::add_handler(events);
+
+ return Fl::run();
+ }
+}
diff --git a/examples/animgifimage-simple.cxx b/examples/animgifimage-simple.cxx
new file mode 100644
index 000000000..19f9732f2
--- /dev/null
+++ b/examples/animgifimage-simple.cxx
@@ -0,0 +1,40 @@
+//
+// Minimal program for displaying an animated GIF file
+// with the Fl_Anim_GIF_Image class.
+//
+#include <FL/Fl_Double_Window.H>
+#include <FL/Fl_Box.H>
+#include <FL/Fl_Anim_GIF_Image.H>
+#include <FL/Fl.H>
+#include <stdio.h>
+
+int main(int argc, char *argv[]) {
+ Fl_Double_Window win(400, 300, "Fl_Anim_GIF_Image demo");
+
+ // prepare a canvas (widget) for the animation
+ Fl_Box canvas(20, 40, win.w()-40, win.h()-80, "Hello from FLTK GIF-animation!");
+ canvas.align(FL_ALIGN_TOP|FL_ALIGN_IMAGE_BACKDROP);
+ canvas.labelsize(20);
+
+ win.resizable(win);
+ win.end();
+ win.show(1, argv);
+
+ // Create and load the animated gif as image
+ // of the `canvas` widget and start it immediately.
+ // We use the `DONT_RESIZE_CANVAS` flag here to tell the
+ // animation *not* to change the canvas size (which is the default).
+ const char *default_image = "../test/pixmaps/fltk_animated.gif";
+ Fl_Anim_GIF_Image animgif(/*name_=*/ argv[1] ? argv[1] : default_image,
+ /*canvas_=*/ &canvas,
+ /*flags_=*/ Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS);
+ // resize animation to canvas size
+ animgif.scale(canvas.w(), canvas.h(), /*can_expand*/1, /*proportional*/1);
+
+ // check if loading succeeded
+ printf("%s: ld=%d, valid=%d, frames=%d, size=%dx%d\n",
+ animgif.name(), animgif.ld(), animgif.valid(),
+ animgif.frames(), animgif.canvas_w(), animgif.canvas_h());
+ if (animgif.valid())
+ return Fl::run();
+}
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();
+}