From 2ddfd9d9492d9fc1df111ec9211dd1be4d424c35 Mon Sep 17 00:00:00 2001 From: wcout Date: Sat, 21 Jan 2023 17:27:58 +0100 Subject: Animated GIF support (Fl_Anim_GIF_Image class) (#375) --- examples/CMakeLists.txt | 4 + examples/Makefile | 6 +- examples/animgifimage-play.cxx | 237 ++++++++++++++++++++++++++++++ examples/animgifimage-resize.cxx | 185 ++++++++++++++++++++++++ examples/animgifimage-simple.cxx | 40 ++++++ examples/animgifimage.cxx | 303 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 examples/animgifimage-play.cxx create mode 100644 examples/animgifimage-resize.cxx create mode 100644 examples/animgifimage-simple.cxx create mode 100644 examples/animgifimage.cxx (limited to 'examples') 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 [-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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 + +#include +#include +#include +#include +#include + +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 +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(below->image()); + animgif = tiled ? + dynamic_cast(tiled->image()) : + dynamic_cast(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(); +} -- cgit v1.2.3