diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H new file mode 100644 index 0000000000..bb33f3f189 --- /dev/null +++ b/FL/Fl_Anim_GIF_Image.H @@ -0,0 +1,185 @@ +// +// Fl_Anim_GIF_Image class header for the Fast Light Tool Kit (FLTK). +// +// Copyright 2016-2022 by Christian Grabner . +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef Fl_Anim_Gif_Image_H +#define Fl_Anim_Gif_Image_H + +// forward declarations +class Fl_Image; +class Fl_Widget; + +#include + +// Load and display animater GIF images +class FL_EXPORT Fl_Anim_GIF_Image : public Fl_GIF_Image { + + class FrameInfo; // internal helper class + +public: + + /** + When opening an Fl_Anim_GIF_Image there are some options + that can be passed in a `flags` value. + */ + enum Flags { + /** + This flag indicates to the loader that it should not start + the animation immediately after successful load, which is + the default. + It can be started later using the \ref start() method. + */ + DONT_START = 1, + /** + This flag indicates to the loader that it should not + resize the canvas widget of the animation to the dimensions + of the animation, which is the default. + Needed for special use cases. + */ + DONT_RESIZE_CANVAS = 2, + /** + This flag indicates to the loader that it should not + set the animation as \ref image() member of the canvas widget, + which is the default. + Needed for special use cases. + */ + DONT_SET_AS_IMAGE = 4, + /** + Often frames change just a small area of the animation canvas. + This flag indicates to the loader to try using less memory + by storing frame data not as canvas-sized images but use the + sizes defined in the GIF file. + The drawbacks are higher cpu usage during playback and maybe + minor artefacts when resized. + */ + OPTIMIZE_MEMORY = 8, + /** + This flag can be used to print informations about the + decoding process to the console. + */ + LOG_FLAG = 64, + /** + This flag can be used to print even more informations about + the decoding process to the console. + */ + DEBUG_FLAG = 128 + }; + + // -- constructors and destructor + Fl_Anim_GIF_Image(const char *filename, Fl_Widget *canvas = 0, unsigned short flags = 0); + Fl_Anim_GIF_Image(const char* imagename, const unsigned char *data, + const size_t length, Fl_Widget *canvas = 0, + unsigned short flags = 0); + Fl_Anim_GIF_Image(); + ~Fl_Anim_GIF_Image() FL_OVERRIDE; + + // -- file handling + bool load(const char *name, const unsigned char *imgdata=NULL, size_t imglength=0); + bool valid() const; + + // -- getters and setters + void frame_uncache(bool uncache); + bool frame_uncache() const; + double delay(int frame_) const; + void delay(int frame, double delay); + void canvas(Fl_Widget *canvas, unsigned short flags = 0); + Fl_Widget *canvas() const; + int canvas_w() const; + int canvas_h() const; + bool is_animated() const; + const char *name() const; + void speed(double speed); + double speed() const; + + // -- animation + int frames() const; + void frame(int frame); + int frame() const; + Fl_Image *image() const; + Fl_Image *image(int frame) const; + bool start(); + bool stop(); + bool next(); + + /** Return if the animation is currently running or stopped. + \return true if the animation is running + */ + bool playing() const { return valid() && Fl::has_timeout(cb_animate, (void *)this); } + + // -- image data + Fl_Anim_GIF_Image& resize(int w, int h); + Fl_Anim_GIF_Image& resize(double scale); + int frame_x(int frame) const; + int frame_y(int frame) const; + int frame_w(int frame) const; + int frame_h(int frame) const; + + // -- overriden methods + void color_average(Fl_Color c, float i) FL_OVERRIDE; + Fl_Image *copy(int W, int H) const FL_OVERRIDE; + Fl_Image *copy() const { return Fl_Pixmap::copy(); } + void desaturate() FL_OVERRIDE; + void draw(int x, int y, int w, int h, int cx = 0, int cy = 0) FL_OVERRIDE; + void uncache() FL_OVERRIDE; + + // -- debugging and logging + int debug() const; + + // -- static methods + static int frame_count(const char *name, const unsigned char *imgdata = NULL, size_t imglength = 0); + + /** + The loop flag can be used to (dis-)allow loop count. + If set (which is the default), the animation will be + stopped after the number of repeats specified in the + GIF file (typically this count is set to 'forever' anyway). + If cleared the animation will always be 'forever', + regardless of what is specified in the GIF file. + */ + static bool loop; + + /** + The min_delay value can be used to set a minimum value + for the frame delay for playback. This is to prevent + CPU hogs caused by images with very low delay rates. + This is a global value for all Fl_Anim_GIF_Image objects. + */ + static double min_delay; + +protected: + + bool next_frame(); + void clear_frames(); + void set_frame(int frame); + + static void cb_animate(void *d); + void scale_frame(); + void set_frame(); + void on_frame_data(Fl_GIF_Image::GIF_FRAME &f) FL_OVERRIDE; + void on_extension_data(Fl_GIF_Image::GIF_FRAME &f) FL_OVERRIDE; + +private: + + char *name_; + unsigned short flags_; + Fl_Widget *canvas_; + bool uncache_; + bool valid_; + int frame_; // current frame + double speed_; + FrameInfo *fi_; +}; + +#endif // Fl_Anim_Gif_Image_H diff --git a/FL/Fl_GIF_Image.H b/FL/Fl_GIF_Image.H index 4871bf5746..248b61c691 100644 --- a/FL/Fl_GIF_Image.H +++ b/FL/Fl_GIF_Image.H @@ -1,7 +1,7 @@ // // GIF image header file for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2021 by Bill Spitzak and others. +// Copyright 1998-2022 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -36,10 +36,51 @@ public: // constructor with length (since 1.4.0) Fl_GIF_Image(const char* imagename, const unsigned char *data, const size_t length); + static bool is_animated(const char *name_); + /** Sets how the shared image core routine should treat animated GIF files. + The default is to treat them as ordinary GIF's e.g. it creates a Fl_GIF_Image object. + If this variable is set, then an animated GIF object Fl_Anim_GIF_Image is created. + */ + static bool animate; + protected: - void load_gif_(class Fl_Image_Reader &rdr); + // Proteced constructors needed for animated GIF support through Fl_Anim_GIF_Image. + Fl_GIF_Image(const char* filename, bool anim); + Fl_GIF_Image(const char* imagename, const unsigned char *data, const size_t length, bool anim); + // Protected default constructor needed for Fl_Anim_GIF_Image. + Fl_GIF_Image(); + + void load_gif_(class Fl_Image_Reader &rdr, bool anim=false); + + void load(const char* filename, bool anim); + void load(const char* imagename, const unsigned char *data, const size_t length, bool anim); + + // Internal structure to "glue" animated GIF support into Fl_GIF_Image. + // This data is passed during decoding to the Fl_Anim_GIF_Image class. + struct GIF_FRAME { + int ifrm, width, height, x, y, w, h, + clrs, bkgd, trans, + dispose, delay; + const uchar *bptr; + const struct CPAL { + uchar r, g, b; + } *cpal; + GIF_FRAME(int frame, uchar *data) : ifrm(frame), bptr(data) {} + GIF_FRAME(int frame, int W, int H, int fx, int fy, int fw, int fh, uchar *data) : + ifrm(frame), width(W), height(H), x(fx), y(fy), w(fw), h(fh), bptr(data) {} + void disposal(int mode, int delay) { dispose = mode; this->delay = delay; } + void colors(int nclrs, int bg, int tp) { clrs = nclrs; bkgd = bg; trans = tp; } + }; + + // Internal virtual methods, which are called during decoding to pass data + // to the Fl_Anim_GIF_Image class. + virtual void on_frame_data(GIF_FRAME &gf) {} + virtual void on_extension_data(GIF_FRAME &gf) {} + +private: + void lzw_decode(Fl_Image_Reader &rdr, uchar *Image, int Width, int Height, int CodeSize, int ColorMapSize, int Interlace); }; #endif diff --git a/FL/Fl_Image.H b/FL/Fl_Image.H index fb1ddf7340..b296704f98 100644 --- a/FL/Fl_Image.H +++ b/FL/Fl_Image.H @@ -272,6 +272,9 @@ public: An internal copy is made of the original image before changes are applied, to avoid modifying the original image. + + \note The RGB color of \ref FL_BACKGROUND_COLOR may change when the + connection to the display is made. See fl_open_display(). */ void inactive() { color_average(FL_GRAY, .33f); } virtual void desaturate(); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2bc33557a0..faa6d7daa8 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 dc312aa7a6..946eda8081 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 0000000000..4a319f8428 --- /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 0000000000..1417255b36 --- /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 0000000000..19f9732f28 --- /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 0000000000..515b76b651 --- /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(); +} diff --git a/fluid/Fluid_Image.cxx b/fluid/Fluid_Image.cxx index 0340bc7b6b..bc44733467 100644 --- a/fluid/Fluid_Image.cxx +++ b/fluid/Fluid_Image.cxx @@ -30,6 +30,7 @@ #include // fl_fopen() #include #include +#include #include "../src/flstring.h" #include @@ -51,6 +52,7 @@ static int image_header_written = 0; static int jpeg_header_written = 0; static int png_header_written = 0; static int gif_header_written = 0; +static int animated_gif_header_written = 0; static int bmp_header_written = 0; static int svg_header_written = 0; @@ -134,8 +136,19 @@ void Fluid_Image::write_static(int compressed) { if (!img) return; const char *idata_name = unique_id(this, "idata", fl_filename_name(name()), 0); function_name_ = unique_id(this, "image", fl_filename_name(name()), 0); - // TODO: GIF, ICO, BMP - if (compressed && strcmp(fl_filename_ext(name()), ".gif")==0) { + + if (is_animated_gif_) { + // Write animated gif image data... + write_c("\n"); + if (animated_gif_header_written != write_number) { + write_c("#include \n"); + animated_gif_header_written = write_number; + } + write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary("AnimGIF"); + write_c(";\n"); + write_initializer("Fl_Anim_GIF_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".gif")==0) { // Write gif image data... write_c("\n"); if (gif_header_written != write_number) { @@ -146,7 +159,7 @@ void Fluid_Image::write_static(int compressed) { size_t nData = write_static_binary("GIF"); write_c(";\n"); write_initializer("Fl_GIF_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (compressed && strcmp(fl_filename_ext(name()), ".bmp")==0) { + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".bmp")==0) { // Write bmp image data... write_c("\n"); if (bmp_header_written != write_number) { @@ -198,7 +211,7 @@ void Fluid_Image::write_static(int compressed) { write_cdata(img->data()[0], ((img->w() + 7) / 8) * img->h()); write_c(";\n"); write_initializer( "Fl_Bitmap", "%s, %d, %d, %d", idata_name, ((img->w() + 7) / 8) * img->h(), img->w(), img->h()); - } else if (compressed && strcmp(fl_filename_ext(name()), ".jpg")==0) { + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".jpg")==0) { // Write jpeg image data... write_c("\n"); if (jpeg_header_written != write_number) { @@ -209,7 +222,7 @@ void Fluid_Image::write_static(int compressed) { size_t nData = write_static_binary("JPEG"); write_c(";\n"); write_initializer("Fl_JPEG_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (compressed && strcmp(fl_filename_ext(name()), ".png")==0) { + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".png")==0) { // Write png image data... write_c("\n"); if (png_header_written != write_number) { @@ -220,7 +233,7 @@ void Fluid_Image::write_static(int compressed) { size_t nData = write_static_binary("PNG"); write_c(";\n"); write_initializer("Fl_PNG_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (strcmp(fl_filename_ext(name()), ".svg")==0 || strcmp(fl_filename_ext(name()), ".svgz")==0) { + } else if (fl_ascii_strcasecmp(fl_filename_ext(name()), ".svg")==0 || fl_ascii_strcasecmp(fl_filename_ext(name()), ".svgz")==0) { bool gzipped = (strcmp(fl_filename_ext(name()), ".svgz") == 0); // Write svg image data... if (compressed) { @@ -242,7 +255,7 @@ void Fluid_Image::write_static(int compressed) { } } else { // if FLUID runs from the command line, make sure that the image is not - // only loade but also rasterized, so we can write the RGB image data + // only loaded but also rasterized, so we can write the RGB image data Fl_RGB_Image* rgb_image = NULL; Fl_SVG_Image* svg_image = NULL; if (img->d()>0) @@ -279,6 +292,8 @@ void Fluid_Image::write_initializer(const char *type_name, const char *format, . va_list ap; va_start(ap, format); write_c("static Fl_Image *%s() {\n", function_name_); + if (is_animated_gif_) + write_c("%sFl_GIF_Image::animate = true;\n", indent(1)); write_c("%sstatic Fl_Image *image = NULL;\n", indent(1)); write_c("%sif (!image)\n", indent(1)); write_c("%simage = new %s(", indent(2), type_name); @@ -292,7 +307,11 @@ void Fluid_Image::write_initializer(const char *type_name, const char *format, . void Fluid_Image::write_code(int bind, const char *var, int inactive) { /* Outputs code that attaches an image to an Fl_Widget or Fl_Menu_Item. This code calls a function output before by Fluid_Image::write_initializer() */ - if (img) write_c("%s%s->%s%s( %s() );\n", indent(), var, bind ? "bind_" : "", inactive ? "deimage" : "image", function_name_); + if (img) { + write_c("%s%s->%s%s( %s() );\n", indent(), var, bind ? "bind_" : "", inactive ? "deimage" : "image", function_name_); + if (is_animated_gif_) + write_c("%s((Fl_Anim_GIF_Image*)(%s()))->canvas(%s, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS);\n", indent(), function_name_, var); + } } void Fluid_Image::write_inline(int inactive) { @@ -355,11 +374,20 @@ Fluid_Image* Fluid_Image::find(const char *iname) { return ret; } -Fluid_Image::Fluid_Image(const char *iname) { +Fluid_Image::Fluid_Image(const char *iname) + : is_animated_gif_(false) +{ name_ = fl_strdup(iname); written = 0; refcount = 0; img = Fl_Shared_Image::get(iname); + if (img && iname) { + const char *ext = fl_filename_ext(iname); + if (fl_ascii_strcasecmp(ext, ".gif")==0) { + int fc = Fl_Anim_GIF_Image::frame_count(iname); + if (fc > 0) is_animated_gif_ = true; + } + } function_name_ = NULL; } diff --git a/fluid/Fluid_Image.h b/fluid/Fluid_Image.h index 4845160c93..deb9ecfa95 100644 --- a/fluid/Fluid_Image.h +++ b/fluid/Fluid_Image.h @@ -24,6 +24,7 @@ #include class Fluid_Image { + bool is_animated_gif_; const char *name_; int refcount; Fl_Shared_Image *img; diff --git a/fluid/about_panel.cxx b/fluid/about_panel.cxx index 61fcdb6407..b01616edad 100644 --- a/fluid/about_panel.cxx +++ b/fluid/about_panel.cxx @@ -21,236 +21,127 @@ void show_help(const char *name); Fl_Double_Window *about_panel=(Fl_Double_Window *)0; -#include -static const char *idata_fluid[] = { -"96 96 32 1", -" \tc None", -".\tc #000100", -"+\tc #031F3F", -"@\tc #00366C", -"#\tc #2E302D", -"$\tc #0058AC", -"%\tc #0060BF", -"&\tc #4E504D", -"*\tc #14659F", -"=\tc #006DDC", -"-\tc #2C7087", -";\tc #0080FF", -">\tc #407B74", -",\tc #0F85F9", -"\'\tc #268CCD", -")\tc #7C7E7B", -"!\tc #2D92EC", -"~\tc #4498A9", -"{\tc #2F94FE", -"]\tc #5BA18C", -"^\tc #6BA674", -"/\tc #7DAD62", -"(\tc #93BD53", -"_\tc #A4A6A2", -":\tc #6CB6FF", -"<\tc #ABCC3F", -"[\tc #C4DA2A", -"}\tc #CACCC9", -"|\tc #DCE913", -"1\tc #BBDEFF", -"2\tc #FDFE00", -"3\tc #FDFFFC", -" \ - ", -" \ - ", -" \ - ", -" ........... \ - ", -" ...................... \ - ", -" ........................ \ - ", -" ........#&#&#&#&##...... \ - ", -" ....)__}33333333333}_... \ - ", -" ...&33333333333333333... \ - ", -" ...#33311133333333333... \ - ", -" ...&33!,{,;:333333333... \ - ", -" ...&3:,{{{{,13333333}... \ - ", -" ...&3!{{!{{,13333333}... \ - ", -" ...&3:!{{!{;13333333}... \ - ", -" ...&3{{{{{{;133333333... \ - ", -" ...&31,{{{;,33333333}... \ - ", -" ...&331{{{:133333333}... \ - ", -" ...&3333333333333333_... \ - ", -" ...&3333333333333333}... \ - ", -" ...&3333333333333333_... \ - ", -" ...&3333333333333333}... \ - ", -" ...&3333333333333333_... \ - ", -" ...&3333333333333333}... \ - ", -" ...&3333333333333333_... \ - ", -" ...&3333333331!,,;:3}... \ - ", -" ...&333333333{{{{{;:_... \ - ", -" ...&333333331,{!{!{{}... \ - ", -" ...&333333331{{{{{{,_... \ - ", -" ...)333333331{{!{{{{_... \ - ", -" ...)333333333{{{!{;:_... \ - ", -" ...)3333333331{;;;:3_... \ - ", -" ...)3333333333331333_... \ - ", -" ...)3333333333333333_... \ - ", -" ...)3333333333333333_... \ - ", -" ..._3333333333333333_... \ - ", -" ..._3333333333333333_... \ - ", -" ..._3333333333333333_... \ - ", -" ..._3333333333333333}.... \ - ", -" ...._33333333333333333#... \ - ", -" ....&333333333333333333_.... \ - ", -" ....&33333333333333333333).... \ - ", -" ....333333333333333333333}&.... \ - ", -" ...._33333333333333333333333.... \ - ", -" ....&333333333331:11333333333_.... \ - ", -" ....#33333333333:,,,;:333333333&.... \ - ", -" ....}3333333333:,!{{{;1333333333&.... \ - ", -" ....}33333333333{{{!{{,!3333333333.... \ - ", -" ....)333333333333{{{{!{{{3333333333_.... \ - ", -" ....#3333333333333!{{{{{,:33333333333&.... \ - ", -" ...._33333333333331{{!{,;1333333333333#.... \ - ", -" ...._333333333333333:;,;,13333333333333_.... \ - ", -" ...._333333333333333333113333333333333333_.... \ - ", -" ....&33333333333333333333333333331::1333333&.... \ - ", -" ...._333333333333333333333333333{,{{;{133333#... \ - ", -" ...._3333333333333333333333333331,{!{{,:33333}.... \ - ", -" ....&3333333333133333333333333333:{{{{{{:333333).... \ - ", -" ...#333333331{,,;:333333333333333:{!{!{{:3333333&.... \ - ", -" ....}33333333,{{{{;:333333333333331,{!{{;:33333333#... \ - ", -" ...._333333331,!{!{{,333333333333333{,{{;{1333333333.... \ - ", -" ....&3333333331{{{{{{{3333333333333333::::33333333333)....\ - ", -" ....+!:::::::::{{{{!{{;::::::::::::::::::::::::::!:::::+...\ -. ", -" ...+=;;;;;;;;;;;;{{{{;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;=...\ -. ", -" ....%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;%..\ -.. ", -" ....@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$.\ -... ", -" ...+%;;;;;;!!!;;;;;,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;!=;;;+\ -.... ", -" ....%;;;;;!([<|^~]^([%;;;;;;;;;;;;;;;;;,(<\'=;;;;;;;!^/<[|\'=;\ -;=+... ", -" ....$;;;;;\'|2>]22<|22[%=;;;;;;;;;;;;;;;;^22[%=;;;;;;!][22|%=;;\ -;$.... ", -" ....@;;;;;;[2[%^2|*[22(%=;;;;;;;;;;;;;;;,/22|$=;;;;;;;;<22<%=;;;\ -;$.... ", -" ....+=;;;;;~22^$%]~$|22>%=;;;;;;;;;;;;;;;;\'||^%=;;;;;;;,[22^$=;;\ -;;;+.... ", -" ....%;;;;;,[22-%===\'22|*==;;;;;;;;;;;;;;;;;;=%=;;;;;;;;\'22|*%=;\ -;;;;=+... ", -" ....$;;;;;;!22|$%;,;^22<$=;;;;;;;;;;;;;;;;;;===;;;;;;;;;^22|$==;;;\ -;;;%.... ", -" ....@;;;;;\'](22[^]=;;<22^$==!~]/~=;!]]~;;;;{\'~]==;;;;~<<]<22($=;;\ -;;;;;;@.... ", -" ....@;;;;;;]<|22|[<%;!|22-%\'[2222*=;/22(%;~|222(=;;;!<2|^[222>$=;;;\ -;;;;;;+.... ", -" ....=;;;;;;;,[22>$===~22|$==,[22[%=;[22]%=,!|22]%=;![2|*%]22|*==;;;;\ -;;;;;%+... ", -" ....@;;;;;;;;!|22*$=;;/22($=;,[22/$=\'222*%=;!|22-%;;<22>%=]22[$%;;;;\ -;;;;;;;=.... ", -" ....@;;;;;;;;;~22[*==;;[22>%=;\'22|-%,^22[$=;,~22[$%;]22<$%=(22/$=;;;;\ -;;;;;;;;@.... ", -" ....+;;;;;;;;;;^22<$=;;!222*$=;]22[$==[22/$=;;(22/$=![22]$=;|22-%=;;;;;\ -;;;;;;;;+... ", -" ....;;;;;;;;;;;<22^%=;;]22[$=;;(22/$=~222-%=;;[22>%=]22|$%;~22|$==;;;;;\ -;;;;;;;;;.... ", -" ....%;;;;;;;;;;;|22-%=;;(22/$=;{|22-%=<22|$%;;\'22|*%;<22<$==(22<$=;=;;;\ -;;;;;;;;;;$.... ", -" ....+;;;;;;;;;;;!222$==;,|22>%=;~22|$=]|22($=;;]22[$%,|22^%=!|22^$=;;;;;;\ -;;;;;;;;;;@.... ", -" ....+=;;;;;;;;;;;~22[$%;;\'22|*-/;]22($*[<22^$^=;(22/$(-222>$=(222->~;;;;;\ -;;;;;;;;;;;=+.... ", -" ...+;;;;;;;;;;;;;(22/$=;;]22|*<\'=(22/*[~[22>(]=;|22>//=|22/$^(|2|-[%=;;;;\ -;;;;;;;;;;;;=.... ", -" ....$;;;;;;;;;;;;;<22>%=;;]222|>==(222|^=|22|<%=;|222<%=(222|<-222|-==;;;;;\ -;;;;;;;;;;;;$.... ", -" ....@;;;;;;;;;;;;;!|2|$=;;;\'[2[>%=;\'|2[]%=/2|/$==;^2|(*%=!(2|($%<2[-%=;;;;\ -;;;;;;;;;;;;;;;@.... ", -"....@;;;;;;;;;;;;;;\'22($%;;;;=%%==;;;=%%%==;=%%==;;;=%===;;==%%====%%=,;;;;;\ -;;;;;;;;;;;;;;;+... ", -"...+=;;;;;;;;;;!\'=,]22-%=;;;;;;==;=;;;===;=;;===;;;;;===;;;;=;=,;;,=;=;;;;;;\ -;;;;;;;;;;;;;;;=....", -"...+;;;;;;;;;;;[2^=<2<$==;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ -;;;;;;;;;;;;;;;+...", -"...+;;;;;;;;;;;22(\'2|*%=;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ -;;;;;;;;;;;;;;;;+...", -"...+;;;;;;;;;;;^|<[[-%=;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ -;;;;;;;;;;;;;;;+...", -"...+;;;;;;;;;;;;*~*%===;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ -;;;;;;;;;;;;;;;@...", -"...+;;;;;;;;;;;;;====;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ -;;;;;;;;;;;;;;;+...", -"....$=;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ -;;;;;;;;;;;;;=$....", -" .....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\ -+++++++++++++..... ", -" ............................................................................\ -................. ", -" ...........................................................................\ -................. ", -" ........................................................................\ -.............. " -}; +#include +static const unsigned char idata_fluid[] = +{71,73,70,56,57,97,96,0,96,0,132,31,0,0,1,0,3,31,63,46,48,45,0,54,108,78,80, +77,64,123,116,124,126,123,125,173,98,107,166,116,171,204,63,220,233,19,253,254, +0,196,218,42,147,189,83,20,101,159,44,112,135,0,88,172,0,96,191,0,109,220,91, +161,140,68,152,169,38,140,205,0,128,255,15,133,249,45,146,236,47,148,254,108, +182,255,164,166,162,187,222,255,202,204,201,253,255,252,0,0,0,33,255,11,78,69, +84,83,67,65,80,69,50,46,48,3,1,0,0,0,33,249,4,5,10,0,31,0,44,0,0,0,0,96,0,96,0, +0,5,254,224,39,142,100,105,158,104,170,174,108,235,190,112,44,207,116,109,223, +120,110,2,124,239,3,186,160,238,71,36,14,34,61,161,18,86,244,13,32,78,139,5,201, +91,90,87,63,143,118,171,201,112,184,25,11,180,122,45,239,120,219,180,135,147, +241,174,219,223,164,121,46,234,169,211,108,111,87,163,149,211,205,118,119,96,28, +93,95,28,3,100,127,101,129,130,90,95,107,26,93,22,136,138,128,104,120,28,143, +90,124,121,97,80,149,139,151,142,109,164,111,134,153,99,160,87,140,155,145,153, +142,142,113,137,170,66,172,130,108,90,112,126,180,65,182,119,93,163,26,136,64, +188,181,162,183,112,111,25,169,197,189,199,119,121,143,108,204,205,57,190,120, +25,124,91,28,187,213,54,215,120,119,221,222,52,224,141,125,179,228,229,207,231, +105,227,234,49,230,231,239,240,47,242,141,244,245,45,247,130,249,250,88,236,218, +161,35,246,47,222,179,78,110,196,165,43,184,239,24,194,100,106,252,49,60,193, +232,33,41,77,3,39,50,17,101,17,226,22,137,26,73,84,36,69,50,225,199,133,33,254, +81,140,44,233,49,99,74,22,172,186,148,212,230,14,229,203,18,172,44,98,116,121, +51,133,47,66,26,118,158,36,216,147,98,64,129,32,95,242,83,72,180,40,206,163, +237,124,56,61,3,245,156,128,164,19,125,16,16,40,104,131,212,169,31,180,114,109, +100,224,171,83,31,99,149,37,236,64,192,236,77,31,27,198,178,204,48,180,105,72, +177,92,59,62,242,138,213,155,15,1,105,231,182,217,210,182,111,51,31,29,2,11,78, +83,216,38,60,196,105,61,8,166,91,211,49,57,31,6,34,171,37,185,147,175,229,195, +61,0,107,222,108,146,177,219,199,61,226,142,134,228,202,234,105,191,169,87,107, +246,108,23,52,15,213,178,35,211,86,135,55,183,230,198,181,85,193,245,189,250, +234,103,58,195,183,93,36,126,174,195,235,74,189,37,179,100,78,246,249,159,208, +216,88,10,165,238,1,56,45,200,91,38,115,23,100,60,120,168,219,106,196,143,103, +106,126,73,244,92,139,215,171,41,107,184,134,143,0,24,34,233,143,116,161,191, +255,11,251,5,40,224,128,4,6,24,128,117,198,0,254,16,128,4,82,52,232,224,131,16, +70,40,225,132,16,74,128,224,16,61,68,64,225,134,28,118,8,33,21,199,217,215,195, +0,30,150,104,226,132,99,132,56,67,15,1,104,248,32,6,48,62,120,193,137,52,98, +192,160,20,7,214,215,16,15,46,54,136,65,3,12,36,160,0,2,20,76,128,0,144,61,122, +120,65,3,9,84,112,227,139,8,28,144,0,3,10,56,41,133,4,57,182,183,98,15,16,60,88, +129,2,11,20,48,193,2,11,8,73,38,3,17,60,217,33,2,103,166,9,33,6,19,48,64,166,2, +110,74,145,162,150,27,241,64,226,131,114,162,201,166,2,14,200,185,64,3,117,118, +120,193,1,115,66,160,166,131,9,144,153,64,161,98,92,104,15,139,139,82,64,38,2, +16,68,48,1,5,16,128,25,38,164,28,126,57,36,168,82,92,32,40,166,139,102,137,39, +76,25,202,40,232,3,105,74,32,65,5,115,58,32,43,141,18,144,42,5,173,11,0,10,42, +150,58,62,197,67,151,47,38,170,225,140,108,150,169,40,174,183,74,152,172,2,138, +46,58,133,164,42,254,68,225,96,5,19,52,112,38,2,19,220,216,232,2,168,74,128,65, +145,7,80,192,32,156,155,58,152,65,5,69,54,43,5,5,9,36,48,193,183,13,44,27,225, +48,42,10,11,192,158,14,206,235,169,2,65,106,136,129,167,176,86,32,40,153,182, +90,128,232,160,26,82,224,233,160,79,98,208,232,144,7,23,96,111,132,170,202,224, +131,180,22,152,74,166,197,178,74,96,105,175,209,122,188,0,154,12,10,58,65,154, +23,12,76,230,202,231,202,233,235,152,189,218,202,177,20,17,100,105,144,158,18, +186,188,128,3,246,46,92,47,131,38,31,160,40,175,63,187,233,51,172,82,124,91,64, +154,52,51,144,233,134,22,246,101,109,132,35,51,96,179,20,130,62,205,32,175,10, +192,122,65,178,82,19,157,245,212,52,39,144,169,4,218,46,96,244,205,14,226,187, +234,125,20,38,171,246,141,24,144,249,243,178,81,71,43,232,219,82,180,253,54,6, +42,47,75,176,174,16,234,204,42,15,27,126,139,64,157,125,223,40,184,162,35,47,0, +235,141,93,67,109,172,5,35,67,235,238,134,254,146,250,144,100,132,135,75,78,38, +224,25,148,254,45,180,46,130,237,128,134,223,222,205,182,163,203,194,237,224, +157,62,177,184,97,222,100,70,219,177,167,94,115,158,168,4,19,120,58,180,20,125, +71,112,129,167,143,139,203,252,197,29,202,109,20,15,11,110,120,118,235,181,62, +112,128,5,52,215,27,168,227,16,32,192,224,228,13,60,160,55,200,109,91,94,0,5,38, +2,59,142,238,28,78,126,35,205,128,54,57,187,219,129,82,208,117,3,221,90,0,252, +1,7,144,128,167,140,118,36,48,133,13,101,237,59,141,15,136,213,184,143,65,78, +111,10,40,128,172,210,55,36,1,206,233,81,12,122,24,6,41,152,0,243,205,233,1,159, +235,16,238,234,48,162,14,185,204,115,13,50,216,201,130,247,165,147,193,12,81,10, +120,27,131,254,212,128,215,137,75,91,10,168,87,4,26,197,128,203,209,104,18,82, +185,218,134,120,165,67,7,229,42,86,13,58,34,18,143,232,174,92,133,236,74,76,12, +89,4,88,246,67,11,232,140,82,20,194,128,147,46,64,51,31,62,232,86,79,122,148, +226,141,158,104,196,207,49,72,2,51,234,216,25,171,104,129,170,253,32,0,27,146, +147,248,26,37,59,54,218,241,142,86,36,2,28,41,68,166,6,208,202,87,182,195,163, +32,39,164,42,234,109,8,1,10,152,82,15,17,55,200,70,74,168,144,10,226,144,3,40, +96,195,16,58,242,146,247,210,99,135,66,22,72,76,98,18,146,0,128,158,39,71,121, +34,9,224,238,141,1,72,165,42,87,201,202,86,186,242,149,176,140,165,44,103,201, +202,31,144,176,9,184,204,165,46,119,201,203,94,246,82,36,190,12,166,48,135,73, +76,34,76,175,152,200,76,102,49,75,16,2,0,33,249,4,5,10,0,31,0,44,30,0,4,0,38,0, +57,0,0,5,254,224,39,142,31,96,158,104,170,2,100,235,174,112,236,206,174,103, +223,56,158,210,60,205,113,185,91,111,152,201,136,56,25,100,50,232,25,246,126,71, +227,143,233,57,57,121,72,13,105,169,51,93,105,154,204,141,243,225,222,172,95, +151,70,227,209,126,128,65,116,186,85,92,127,168,115,44,156,218,204,147,236,111, +100,83,113,94,126,128,69,101,98,132,44,115,124,124,114,126,45,123,57,145,88,102, +56,149,60,110,153,156,157,158,159,160,161,162,149,142,120,145,165,76,164,168, +148,167,171,152,173,174,54,170,177,125,126,180,178,176,177,179,76,28,107,26,100, +163,34,97,69,69,192,162,74,196,197,163,201,201,155,160,204,196,206,159,208,117, +163,195,204,198,160,200,209,66,161,189,35,147,184,78,183,169,227,228,229,78,189, +191,231,226,62,208,225,186,154,208,108,231,150,212,240,174,238,247,236,181,36, +219,201,217,60,253,35,22,80,224,187,96,111,0,130,226,199,176,161,67,90,219,128, +61,196,49,144,204,68,27,3,139,93,244,144,49,137,175,143,32,67,138,244,149,225, +130,201,147,38,3,51,132,0,0,33,249,4,5,10,0,31,0,44,34,0,7,0,34,0,54,0,0,5,202, +224,39,138,94,105,158,168,57,174,236,154,190,104,43,143,112,237,205,179,13,227, +178,254,242,45,95,10,200,18,198,136,52,163,10,73,82,150,152,77,39,116,74,141,42, +167,206,39,52,123,171,122,191,224,176,120,76,46,155,207,232,244,151,195,230,156, +57,154,140,60,227,94,107,52,245,185,190,62,229,232,229,113,127,114,85,129,130, +134,83,1,126,130,23,134,25,136,138,127,140,135,80,137,141,134,124,76,144,127, +112,43,28,90,80,154,114,158,30,109,163,93,125,123,58,72,92,75,64,172,39,106,177, +76,175,63,69,175,112,119,152,46,172,133,162,61,92,161,190,182,89,141,26,65,92, +197,199,196,134,198,195,78,189,115,186,31,172,193,205,100,112,209,68,144,120, +102,208,116,180,90,193,25,26,224,93,222,114,229,34,231,25,233,31,25,23,240,241, +240,119,244,245,246,247,244,33,0,33,249,4,5,10,0,31,0,44,36,0,13,0,26,0,48,0,0, +5,196,224,39,138,156,102,114,99,170,174,100,230,186,26,43,143,220,107,163,243, +26,104,182,157,235,188,158,235,167,218,9,135,196,84,77,24,75,166,130,55,167, +106,153,209,224,164,88,44,103,123,205,150,162,217,207,177,155,164,246,178,208, +51,54,237,211,30,51,225,177,103,78,247,252,190,175,79,189,46,219,115,57,123, +124,44,129,132,123,125,133,136,42,0,139,136,137,41,139,0,141,30,93,100,31,144, +141,102,46,28,1,131,133,98,71,14,157,132,153,47,26,156,41,152,111,149,57,164, +154,104,76,97,164,171,101,39,78,84,86,97,31,108,25,128,146,83,71,26,146,118,79, +111,194,42,187,46,198,196,71,202,52,114,190,191,54,193,205,74,38,86,194,216,217, +218,219,136,102,171,194,178,43,146,173,188,42,227,170,199,214,234,214,25,23,238, +239,238,112,41,33,0,33,249,4,5,10,0,31,0,44,38,0,5,0,21,0,56,0,0,5,232,224,39, +142,80,52,158,104,202,101,25,151,190,98,166,105,172,11,167,44,171,125,156,125, +139,171,26,173,245,27,245,56,195,93,17,185,11,250,96,131,97,203,89,20,205,106, +181,170,199,99,67,102,127,219,174,231,11,3,132,129,231,159,57,204,217,186,223, +111,145,185,7,175,199,81,109,207,76,249,91,105,182,82,79,48,109,121,71,85,64,68, +135,39,52,127,112,76,26,130,136,132,133,57,58,47,121,34,92,149,57,28,1,153,118, +122,155,57,3,159,118,82,155,164,31,160,161,162,25,169,171,65,155,26,175,171,167, +53,158,170,171,154,58,79,186,160,138,192,193,194,39,190,118,55,73,160,0,0,120, +162,121,111,2,56,173,117,27,203,70,173,45,117,204,173,206,110,218,205,198,39, +215,191,39,177,156,197,110,73,96,231,235,117,71,189,190,167,124,240,162,74,186, +229,149,46,246,215,249,176,251,185,160,247,56,85,177,37,67,81,188,17,231,220,17, +99,199,176,225,186,61,16,35,74,220,19,2,0,59}; static Fl_Image *image_fluid() { - static Fl_Image *image = new Fl_Pixmap(idata_fluid); + Fl_GIF_Image::animate = true; + static Fl_Image *image = NULL; + if (!image) + image = new Fl_Anim_GIF_Image("fluid.animated.gif", idata_fluid, 2545); return image; } @@ -275,6 +166,8 @@ Fl_Double_Window* make_about_panel() { about_panel->hotspot(about_panel); { Fl_Box* o = new Fl_Box(10, 10, 115, 120); o->image( image_fluid() ); + ((Fl_Anim_GIF_Image*)(image_fluid()))->canvas(o, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS); + ((Fl_Anim_GIF_Image*)(o->image()))->speed(0.5f); } // Fl_Box* o { Fl_Box* o = new Fl_Box(135, 10, 205, 75, "FLTK User\nInterface Designer\nVersion x.x.x"); o->color((Fl_Color)12); diff --git a/fluid/about_panel.fl b/fluid/about_panel.fl index 2d465a81de..82c8ce627e 100644 --- a/fluid/about_panel.fl +++ b/fluid/about_panel.fl @@ -36,11 +36,12 @@ if (!cbuf[0]) { }} {} Fl_Window about_panel { label {About FLUID} open - xywh {449 217 345 180} type Double color 50 selection_color 47 hide hotspot - code0 {\#include "../src/flstring.h"} non_modal + xywh {449 217 345 180} type Double color 50 selection_color 47 hotspot + code0 {\#include "../src/flstring.h"} non_modal visible } { - Fl_Box {} { - image {icons/fluid-96.xpm} xywh {10 10 115 120} + Fl_Box {} {selected + image {icons/fluid.animated.gif} compress_image 1 xywh {10 10 115 120} + code0 {((Fl_Anim_GIF_Image*)(o->image()))->speed(0.5f);} } Fl_Box {} { label {FLTK User @@ -59,7 +60,7 @@ Version x.x.x} } Fl_Button {} { label {View License...} - callback {show_help("license.html");} selected + callback {show_help("license.html");} xywh {115 145 123 25} labelcolor 136 } Fl_Return_Button {} { diff --git a/fluid/icons/fluid.animated.gif b/fluid/icons/fluid.animated.gif new file mode 100644 index 0000000000..c213991608 Binary files /dev/null and b/fluid/icons/fluid.animated.gif differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6567d21149..daa66c23d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -471,6 +471,7 @@ set (IMGCPPFILES Fl_BMP_Image.cxx Fl_File_Icon2.cxx Fl_GIF_Image.cxx + Fl_Anim_GIF_Image.cxx Fl_Help_Dialog.cxx Fl_ICO_Image.cxx Fl_JPEG_Image.cxx diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx new file mode 100644 index 0000000000..e0196cfc21 --- /dev/null +++ b/src/Fl_Anim_GIF_Image.cxx @@ -0,0 +1,1279 @@ +// +// Fl_Anim_GIF_Image class for the Fast Light Tool Kit (FLTK). +// +// Copyright 2016-2023 by Christian Grabner . +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include +#include +#include +#include +#include +#include +#include +#include // lround() + +#include + +/** \class Fl_Anim_GIF_Image + + The Fl_Anim_GIF_Image class supports loading, caching, and drawing of animated + Compuserve GIFSM images. + + The class loads all images contained in the file and animates them by cycling + through them as defined by the delay times in the image file. + + The user must supply an FLTK widget as "container" in order to see the + animation by specifying it in the constructor or later using the + canvas() method. +*/ + +/*static*/ +bool Fl_GIF_Image::animate = false; + + +/////////////////////////////////////////////////////////////////////// +// Internal helper classes/structs +/////////////////////////////////////////////////////////////////////// + +class Fl_Anim_GIF_Image::FrameInfo { + friend class Fl_Anim_GIF_Image; + + enum Transparency { + T_NONE = 0xff, + T_FULL = 0 + }; + + struct RGBA_Color { + uchar r, g, b, alpha; + RGBA_Color(uchar r = 0, uchar g = 0, uchar b = 0, uchar a = T_NONE) : + r(r), g(g), b(b), alpha(a) {} + }; + + enum Dispose { + DISPOSE_UNDEF = 0, + DISPOSE_NOT = 1, + DISPOSE_BACKGROUND = 2, + DISPOSE_PREVIOUS = 3 + }; + + struct GifFrame { + GifFrame() : + rgb(0), + scalable(0), + average_color(FL_BLACK), + average_weight(-1), + desaturated(false), + x(0), + y(0), + w(0), + h(0), + delay(0), + dispose(DISPOSE_UNDEF), + transparent_color_index(-1) {} + Fl_RGB_Image *rgb; // full frame image + Fl_Shared_Image *scalable; // used for hardware-accelerated scaling + Fl_Color average_color; // last average color + float average_weight; // last average weight + bool desaturated; // flag if frame is desaturated + unsigned short x, y, w, h; // frame original dimensions + double delay; // delay (already converted to ms) + Dispose dispose; // disposal method + int transparent_color_index; // needed for dispose() + RGBA_Color transparent_color; // needed for dispose() + }; + + FrameInfo(Fl_Anim_GIF_Image *anim) : + anim(anim), + valid(false), + frames_size(0), + frames(0), + loop_count(1), + loop(0), + background_color_index(-1), + canvas_w(0), + canvas_h(0), + desaturate(false), + average_color(FL_BLACK), + average_weight(-1), + scaling((Fl_RGB_Scaling)0), + debug_(0), + optimize_mem(false), + offscreen(0) {} + ~FrameInfo(); + void clear(); + void copy(const FrameInfo& fi); + double convert_delay(int d) const; + int debug() const { return debug_; } + bool load(const char *name, const unsigned char *data, size_t length); + bool push_back_frame(const GifFrame &frame); + void resize(int W, int H); + void scale_frame(int frame); + void set_frame(int frame); +private: + Fl_Anim_GIF_Image *anim; // a pointer to the Image (only needed for name()) + bool valid; // flag if valid data + int frames_size; // number of frames stored in 'frames' + GifFrame *frames; // "vector" for frames + int loop_count; // loop count from file + int loop; // current loop count + int background_color_index; // needed for dispose() + RGBA_Color background_color; // needed for dispose() + GifFrame frame; // current processed frame + int canvas_w; // width of GIF from header + int canvas_h; // height of GIF from header + bool desaturate; // flag if frames should be desaturated + Fl_Color average_color; // color for color_average() + float average_weight; // weight for color_average (negative: none) + Fl_RGB_Scaling scaling; // saved scaling method for scale_frame() + int debug_; // Flag for debug outputs + bool optimize_mem; // Flag to store frames in original dimensions + uchar *offscreen; // internal "offscreen" buffer +private: +private: + void dispose(int frame_); + void on_frame_data(Fl_GIF_Image::GIF_FRAME &gf); + void on_extension_data(Fl_GIF_Image::GIF_FRAME &gf); + void set_to_background(int frame_); +}; + + +#define LOG(x) if (debug()) printf x +#define DEBUG(x) if (debug() >= 2) printf x +#ifndef LOG + #define LOG(x) +#endif +#ifndef DEBUG + #define DEBUG(x) +#endif + + +// +// helper class FrameInfo implementation +// + +Fl_Anim_GIF_Image::FrameInfo::~FrameInfo() { + clear(); +} + + +void Fl_Anim_GIF_Image::FrameInfo::clear() { + // release all allocated memory + while (frames_size-- > 0) { + if (frames[frames_size].scalable) + frames[frames_size].scalable->release(); + delete frames[frames_size].rgb; + } + delete[] offscreen; + offscreen = 0; + free(frames); + frames = 0; + frames_size = 0; +} + + +double Fl_Anim_GIF_Image::FrameInfo::convert_delay(int d) const { + if (d <= 0) + d = loop_count != 1 ? 10 : 0; + return (double)d / 100; +} + + +void Fl_Anim_GIF_Image::FrameInfo::copy(const FrameInfo& fi) { + // copy from source + for (int i = 0; i < fi.frames_size; i++) { + if (!push_back_frame(fi.frames[i])) { + break; + } + double scale_factor_x = (double)canvas_w / (double)fi.canvas_w; + double scale_factor_y = (double)canvas_h / (double)fi.canvas_h; + if (fi.optimize_mem) { + frames[i].x = lround(fi.frames[i].x * scale_factor_x); + frames[i].y = lround(fi.frames[i].y * scale_factor_y); + int new_w = (int)lround(fi.frames[i].w * scale_factor_x); + int new_h = (int)lround(fi.frames[i].h * scale_factor_y); + frames[i].w = new_w; + frames[i].h = new_h; + } + // just copy data 1:1 now - scaling will be done adhoc when frame is displayed + frames[i].rgb = (Fl_RGB_Image *)fi.frames[i].rgb->copy(); + frames[i].scalable = 0; + } + optimize_mem = fi.optimize_mem; + scaling = Fl_Image::RGB_scaling(); // save current scaling mode + loop_count = fi.loop_count; // .. and the loop_count! +} + + +void Fl_Anim_GIF_Image::FrameInfo::dispose(int frame) { + if (frame < 0) { + return; + } + // dispose frame with index 'frame_' to offscreen buffer + switch (frames[frame].dispose) { + case DISPOSE_PREVIOUS: { + // dispose to previous restores to first not DISPOSE_TO_PREVIOUS frame + int prev(frame); + while (prev > 0 && frames[prev].dispose == DISPOSE_PREVIOUS) + prev--; + if (prev == 0 && frames[prev].dispose == DISPOSE_PREVIOUS) { + set_to_background(frame); + return; + } + DEBUG((" dispose frame %d to previous frame %d\n", frame + 1, prev + 1)); + // copy the previous image data.. + uchar *dst = offscreen; + int px = frames[prev].x; + int py = frames[prev].y; + int pw = frames[prev].w; + int ph = frames[prev].h; + const char *src = frames[prev].rgb->data()[0]; + if (px == 0 && py == 0 && pw == canvas_w && ph == canvas_h) + memcpy((char *)dst, (char *)src, canvas_w * canvas_h * 4); + else { + if ( px + pw > canvas_w ) pw = canvas_w - px; + if ( py + ph > canvas_h ) ph = canvas_h - py; + for (int y = 0; y < ph; y++) { + memcpy(dst + ( y + py ) * canvas_w * 4 + px, src + y * frames[prev].w * 4, pw * 4); + } + } + break; + } + case DISPOSE_BACKGROUND: + DEBUG((" dispose frame %d to background\n", frame + 1)); + set_to_background(frame); + break; + + default: { + // nothing to do (keep everything as is) + break; + } + } +} + + +bool Fl_Anim_GIF_Image::FrameInfo::load(const char *name, const unsigned char *data, size_t length) { + // decode using FLTK + valid = false; + if (data) { + anim->Fl_GIF_Image::load(name, data, length, true); // calls on_frame_data() for each frame + } else { + anim->Fl_GIF_Image::load(name, true); // calls on_frame_data() for each frame + } + + delete[] offscreen; + offscreen = 0; + return valid; +} + + +void Fl_Anim_GIF_Image::FrameInfo::on_extension_data(Fl_GIF_Image::GIF_FRAME &gf) { + if (!gf.bptr) + return; + const uchar *ext = gf.bptr; + if (memcmp(ext, "NETSCAPE2.0", 11) == 0) { + const uchar *params = &ext[11]; + loop_count = params[1] | (params[2] << 8); + DEBUG(("netscape loop count: %u\n", loop_count)); + } +} + + +void Fl_Anim_GIF_Image::FrameInfo::on_frame_data(Fl_GIF_Image::GIF_FRAME &gf) { + if (!gf.bptr) + return; + int delay = gf.delay; + if (delay <= 0) + delay = -(delay + 1); + LOG(("on_frame_data: frame #%d/%d, %dx%d at %d/%d, delay: %d, bkgd=%d/%d, trans=%d, dispose=%d\n", + gf.ifrm + 1, -1, gf.w, gf.h, gf.x, gf.y, + gf.delay, gf.bkgd, gf.clrs, gf.trans, gf.dispose)); + + if (!gf.ifrm) { + // first frame, get width/height + valid = true; // may be reset later from loading callback + canvas_w = gf.width; + canvas_h = gf.height; + offscreen = new uchar[canvas_w * canvas_h * 4]; + memset(offscreen, 0, canvas_w * canvas_h * 4); + } + + if (!gf.ifrm) { + // store background_color AFTER color table is set + background_color_index = gf.clrs && gf.bkgd < gf.clrs ? gf.bkgd : -1; + + if (background_color_index >= 0) { + background_color = RGBA_Color(gf.cpal[background_color_index].r, + gf.cpal[background_color_index].g, + gf.cpal[background_color_index].b); + } + } + + // process frame + frame.x = gf.x; + frame.y = gf.y; + frame.w = gf.w; + frame.h = gf.h; + frame.delay = convert_delay(delay); + frame.transparent_color_index = gf.trans && gf.trans < gf.clrs ? gf.trans : -1; + frame.dispose = (Dispose)gf.dispose; + if (frame.transparent_color_index >= 0) { + frame.transparent_color = RGBA_Color(gf.cpal[frame.transparent_color_index].r, + gf.cpal[frame.transparent_color_index].g, + gf.cpal[frame.transparent_color_index].b); + } + DEBUG(("#%d %d/%d %dx%d delay: %d, dispose: %d transparent_color: %d\n", + (int)frames_size + 1, + frame.x, frame.y, frame.w, frame.h, + gf.delay, gf.dispose, gf.trans)); + + // we know now everything we need about the frame.. + dispose(frames_size - 1); + + // copy image data to offscreen + const uchar *bits = gf.bptr; + const uchar *endp = offscreen + canvas_w * canvas_h * 4; + for (int y = frame.y; y < frame.y + frame.h; y++) { + for (int x = frame.x; x < frame.x + frame.w; x++) { + uchar c = *bits++; + if (c == gf.trans) + continue; + uchar *buf = offscreen; + buf += (y * canvas_w * 4 + (x * 4)); + if (buf >= endp) + continue; + *buf++ = gf.cpal[c].r; + *buf++ = gf.cpal[c].g; + *buf++ = gf.cpal[c].b; + *buf = T_NONE; + } + } + + // create RGB image from offscreen + if (optimize_mem) { + uchar *buf = new uchar[frame.w * frame.h * 4]; + uchar *dest = buf; + for (int y = frame.y; y < frame.y + frame.h; y++) { + for (int x = frame.x; x < frame.x + frame.w; x++) { + if (offscreen + y * canvas_w * 4 + x * 4 < endp) + memcpy(dest, &offscreen[y * canvas_w * 4 + x * 4], 4); + dest += 4; + } + } + frame.rgb = new Fl_RGB_Image(buf, frame.w, frame.h, 4); + } + else { + uchar *buf = new uchar[canvas_w * canvas_h * 4]; + memcpy(buf, offscreen, canvas_w * canvas_h * 4); + frame.rgb = new Fl_RGB_Image(buf, canvas_w, canvas_h, 4); + } + frame.rgb->alloc_array = 1; + + if (!push_back_frame(frame)) { + valid = false; + } +} + + +bool Fl_Anim_GIF_Image::FrameInfo::push_back_frame(const GifFrame &frame) { + void *tmp = realloc(frames, sizeof(GifFrame) * (frames_size + 1)); + if (!tmp) { + return false; + } + frames = (GifFrame *)tmp; + memcpy(&frames[frames_size], &frame, sizeof(GifFrame)); + frames_size++; + return true; +} + + +void Fl_Anim_GIF_Image::FrameInfo::resize(int W, int H) { + double scale_factor_x = (double)W / (double)canvas_w; + double scale_factor_y = (double)H / (double)canvas_h; + for (int i=0; i < frames_size; i++) { + if (optimize_mem) { + frames[i].x = lround(frames[i].x * scale_factor_x); + frames[i].y = lround(frames[i].y * scale_factor_y); + int new_w = (int)lround(frames[i].w * scale_factor_x); + int new_h = (int)lround(frames[i].h * scale_factor_y); + frames[i].w = new_w; + frames[i].h = new_h; + } + } + canvas_w = W; + canvas_h = H; +} + + +void Fl_Anim_GIF_Image::FrameInfo::scale_frame(int frame) { + // Do the actual scaling after a resize if neccessary + int new_w = optimize_mem ? frames[frame].w : canvas_w; + int new_h = optimize_mem ? frames[frame].h : canvas_h; + if (frames[frame].scalable && + frames[frame].scalable->w() == new_w && + frames[frame].scalable->h() == new_h) + return; + + Fl_RGB_Scaling old_scaling = Fl_Image::RGB_scaling(); // save current scaling method + Fl_Image::RGB_scaling(scaling); + if (!frames[frame].scalable) { + frames[frame].scalable = Fl_Shared_Image::get(frames[frame].rgb, 0); + } + frames[frame].scalable->scale(new_w, new_h, 0, 1); + Fl_Image::RGB_scaling(old_scaling); // restore scaling method +} + + +void Fl_Anim_GIF_Image::FrameInfo::set_to_background(int frame) { + // reset offscreen to background color + int bg = background_color_index; + int tp = frame >= 0 ? frames[frame].transparent_color_index : bg; + DEBUG((" set_to_background [%d] tp = %d, bg = %d\n", frame, tp, bg)); + RGBA_Color color = background_color; + if (tp >= 0) + color = frames[frame].transparent_color; + if (tp >= 0 && bg >= 0) + bg = tp; + color.alpha = tp == bg ? T_FULL : tp < 0 ? T_FULL : T_NONE; + DEBUG((" set to color %d/%d/%d alpha=%d\n", color.r, color.g, color.b, color.alpha)); + for (uchar *p = offscreen + canvas_w * canvas_h * 4 - 4; p >= offscreen; p -= 4) + memcpy(p, &color, 4); +} + + +void Fl_Anim_GIF_Image::FrameInfo::set_frame(int frame) { + // scaling pending? + scale_frame(frame); + + // color average pending? + if (average_weight >= 0 && average_weight < 1 && + ((average_color != frames[frame].average_color) || + (average_weight != frames[frame].average_weight))) { + frames[frame].rgb->color_average(average_color, average_weight); + frames[frame].average_color = average_color; + frames[frame].average_weight = average_weight; + } + + // desaturate pending? + if (desaturate && !frames[frame].desaturated) { + frames[frame].rgb->desaturate(); + frames[frame].desaturated = true; + } +} + + + +/////////////////////////////////////////////////////////////////////// +// +// Fl_Anim_GIF_Image +// +// An extension to Fl_GIF_Image. +// +/////////////////////////////////////////////////////////////////////// + + +// +// Fl_Anim_GIF_Image global variables +// + +/*static*/ +double Fl_Anim_GIF_Image::min_delay = 0.; +/*static*/ +bool Fl_Anim_GIF_Image::loop = true; + + + +#include +#include +#include +#include +#include + +// +// class Fl_Anim_GIF_Image implementation +// + +/** Load an animated GIF image from a file. + + This constructor creates an animated image form a GIF-formatted file. + Optionally it applies the \ref canvas() method after successful load. + If \ref DONT_START is not specified in the \p flags parameter it calls + \ref start() after successful load. + + \param[in] filename path and name of GIF file in the file system + \param[in] canvas a widget that will show and animate the GIF, or \c NULL + \param[in] flags see \ref Flags for details, or 0 + + \note The GIF image must be decoupled from the canvas by calling + `myGif->canvas(NULL);` before deleting the canvas. + */ +Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *filename, + Fl_Widget *canvas /* = 0*/, + unsigned short flags /* = 0 */) : + Fl_GIF_Image(), + name_(0), + flags_(flags), + canvas_(canvas), + uncache_(false), + valid_(false), + frame_(-1), + speed_(1.), + fi_(new FrameInfo(this)) +{ + fi_->debug_ = ((flags_ & LOG_FLAG) != 0) + 2 * ((flags_ & DEBUG_FLAG) != 0); + fi_->optimize_mem = (flags_ & OPTIMIZE_MEMORY); + valid_ = load(filename, NULL, 0); + if (canvas_w() && canvas_h()) { + if (!w() && !h()) { + w(canvas_w()); + h(canvas_h()); + } + } + this->canvas(canvas, flags); + if (!(flags & DONT_START)) + start(); + else + frame_ = 0; +} + + +/** Load an animated GIF image from memory. + + This constructor creates an animated image form a GIF-formatted block in + memory. Optionally it applies the \ref canvas() method after successful load. + If \ref DONT_START is not specified in the \p flags parameter it calls + \ref start() after successful load. + + \p imagename can be \c NULL. If a name is given, the image is added to the + list of shared images and will be available by that name. + + \param[in] imagename a name given to this image or \c NULL + \param[in] data pointer to the start of the GIF image in memory + \param[in] length length of the GIF image in memory + \param[in] canvas a widget that will show and animate the GIF, or \c NULL + \param[in] flags see \ref Flags for details, or 0 + + \note The GIF image must be decoupled from the canvas by calling + `myGif->canvas(NULL);` before deleting the canvas. + */ +Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char* imagename, const unsigned char *data, + const size_t length, Fl_Widget *canvas /* = 0 */, + unsigned short flags /* = 0 */) : + Fl_GIF_Image(), + name_(0), + flags_(flags), + canvas_(canvas), + uncache_(false), + valid_(false), + frame_(-1), + speed_(1.), + fi_(new FrameInfo(this)) +{ + fi_->debug_ = ((flags_ & LOG_FLAG) != 0) + 2 * ((flags_ & DEBUG_FLAG) != 0); + fi_->optimize_mem = (flags_ & OPTIMIZE_MEMORY); + valid_ = load(imagename, data, length); + if (canvas_w() && canvas_h()) { + if (!w() && !h()) { + w(canvas_w()); + h(canvas_h()); + } + } + this->canvas(canvas, flags); + if (!(flags & DONT_START)) + start(); + else + frame_ = 0; +} + + +/** Create an empty animated GIF image shell. */ +Fl_Anim_GIF_Image::Fl_Anim_GIF_Image() : + Fl_GIF_Image(), + name_(0), + flags_(0), + canvas_(0), + uncache_(false), + valid_(false), + frame_(-1), + speed_(1.), + fi_(new FrameInfo(this)) { +} + + +/** Release the image and all cached data. + + Also removes the animation timer. + */ +Fl_Anim_GIF_Image::~Fl_Anim_GIF_Image() /* override */ { + Fl::remove_timeout(cb_animate, this); + delete fi_; + free(name_); +} + + +/** Link the image back to a widget for automated animation. + + This method sets current widget, that is used to display the frame images. + The \p flags parameter specifies whether the canvas widget is resized to the + animation dimensions and/or its \ref image() method will be used to set the + current frame image during animation. + + \param[in] canvas a pointer to the widget that will show the animation + \param[in] flags see \ref Flags + + \note The GIF image must be decoupled from the canvas by calling + `myGif->canvas(NULL);` before deleting the canvas. + */ +void Fl_Anim_GIF_Image::canvas(Fl_Widget *canvas, unsigned short flags/* = 0*/) { + if (canvas_) + canvas_->image(0); + canvas_ = canvas; + if (canvas_ && !(flags & DONT_SET_AS_IMAGE)) + canvas_->image(this); // set animation as image() of canvas + if (canvas_ && !(flags & DONT_RESIZE_CANVAS)) + canvas_->size(w(), h()); + if (flags_ != flags) { + flags_ = flags; + fi_->debug_ = ((flags & LOG_FLAG) != 0) + 2 * ((flags & DEBUG_FLAG) != 0); + } + // Note: 'Start' flag is *NOT* used here, + // but an already running animation is restarted. + frame_ = -1; + if (Fl::has_timeout(cb_animate, this)) { + Fl::remove_timeout(cb_animate, this); + next_frame(); + } + else if ( fi_->frames_size ) { + set_frame(0); + } +} + + +/** Gets the current widget, that is used to display the frame images. + \return a pointer to a widget + */ +Fl_Widget *Fl_Anim_GIF_Image::canvas() const { + return canvas_; +} + + +/** Return the width of the animation canvas. + \return the width in pixel units + */ +int Fl_Anim_GIF_Image::canvas_w() const { + return fi_->canvas_w; +} + + +/** Return the height of the animation canvas. + \return the width in pixel units + */ +int Fl_Anim_GIF_Image::canvas_h() const { + return fi_->canvas_h; +} + + +/*static*/ +void Fl_Anim_GIF_Image::cb_animate(void *d) { + Fl_Anim_GIF_Image *b = (Fl_Anim_GIF_Image *)d; + b->next_frame(); +} + + +void Fl_Anim_GIF_Image::clear_frames() { + fi_->clear(); + valid_ = false; +} + + +/** Applies a color average to all frames. + + The color_average() method averages the colors in the image with + the provided FLTK color value. + + \param[in] c blend color + \param[in] i a value between 0.0 and 1.0 where 0 results in the blend color, + and 1 returns the original image + */ +void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) /* override */ { + if (i < 0) { + // immediate mode + i = -i; + for (int f=0; f < frames(); f++) { + fi_->frames[f].rgb->color_average(c, i); + } + return; + } + fi_->average_color = c; + fi_->average_weight = i; + // Do not call set_frame()! If this is called with an indexed color before + // the display connection is open, it will possible average *this* frame + // with a different RGB value than the following frames. + // See Fl_Image::inactive(). + // set_frame(); +} + + +/** Copy and resize the animation frames. + + The virtual copy() method makes a copy of the animated image and resizes all + of its frame images to W x H using the current resize method. + + \param[in] W, H new size in FLTK pixel units + \return the resized copy of the animation + */ +Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) const /* override */ { + Fl_Anim_GIF_Image *copied = new Fl_Anim_GIF_Image(); + // copy/resize the base image (Fl_Pixmap) + // Note: this is not really necessary, if the draw() + // method never calls the base class. + if (fi_->frames_size) { + Fl_Pixmap *gif = (Fl_Pixmap *)Fl_GIF_Image::copy(W, H); + copied->Fl_GIF_Image::data(gif->data(), gif->count()); + copied->alloc_data = gif->alloc_data; + gif->alloc_data = 0; + delete gif; + } + + if (name_) copied->name_ = strdup(name_); + copied->flags_ = flags_; + copied->frame_ = frame_; + copied->speed_ = speed_; + + copied->w(W); + copied->h(H); + copied->fi_->canvas_w = W; + copied->fi_->canvas_h = H; + copied->fi_->copy(*fi_); // copy the meta data + + copied->uncache_ = uncache_; // copy 'inherits' frame uncache status + copied->valid_ = valid_ && copied->fi_->frames_size == fi_->frames_size; + copied->scale_frame(); // scale current frame now + if (copied->valid_ && frame_ >= 0 && !Fl::has_timeout(cb_animate, copied)) + copied->start(); // start if original also was started + return copied; +} + + +int Fl_Anim_GIF_Image::debug() const { + return fi_->debug(); +} + + +/** Return the delay of frame `[0-frames() -1]` in seconds. + + \param[in] frame index into frame list + \return delay to next frame in seconds + */ +double Fl_Anim_GIF_Image::delay(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].delay; + return 0.; +} + + +/** Set the delay of frame `[0-frames() -1]` in seconds. + + \param[in] frame index into frame list + \param[in] delay to next frame in seconds + */ +void Fl_Anim_GIF_Image::delay(int frame, double delay) { + if (frame >= 0 && frame < frames()) + fi_->frames[frame].delay = delay; +} + + +/** Desaturate to all frames of the animation. + */ +void Fl_Anim_GIF_Image::desaturate() /* override */ { + fi_->desaturate = true; + set_frame(); +} + + +/** Draw the current frame of the animation. + + \param[in] x, y, w, h target rectangle + \param[in] cx, cy source offset + */ +void Fl_Anim_GIF_Image::draw(int x, int y, int w, int h, + int cx/* = 0*/, int cy/* = 0*/) /* override */ { + if (this->image()) { + if (fi_->optimize_mem) { + int f0 = frame_; + while (f0 > 0 && !(fi_->frames[f0].x == 0 && fi_->frames[f0].y == 0 && + fi_->frames[f0].w == this->w() && fi_->frames[f0].h == this->h())) + --f0; + for (int f = f0; f <= frame_; f++) { + if (f < frame_ && fi_->frames[f].dispose == FrameInfo::DISPOSE_PREVIOUS) continue; + if (f < frame_ && fi_->frames[f].dispose == FrameInfo::DISPOSE_BACKGROUND) continue; + Fl_RGB_Image *rgb = fi_->frames[f].rgb; + if (rgb) { + float s = Fl_Graphics_Driver::default_driver().scale(); + rgb->scale(s*fi_->frames[f].w, s*fi_->frames[f].h, 0, 1); + rgb->draw(x + s*fi_->frames[f].x, y + s*fi_->frames[f].y, w, h, cx, cy); + } + } + } + else { + this->image()->scale(Fl_GIF_Image::w(), Fl_GIF_Image::h(), 0, 1); + this->image()->draw(x, y, w, h, cx, cy); + } + } else { + // Note: should the base class be called here? + // If it is, then the copy() method must also + // copy the base image! + Fl_GIF_Image::draw(x, y, w, h, cx, cy); + } +} + + +/** Return the current frame. + + \return the current frame index in the range for 0 to `frames()-1`. + \return -1 if the image has no frames. + */ +int Fl_Anim_GIF_Image::frame() const { + return frame_; +} + + +/** Set the current frame. + + \param[in] frame index into list of frames + */ +void Fl_Anim_GIF_Image::frame(int frame) { + if (Fl::has_timeout(cb_animate, this)) { + Fl::warning("Fl_Anim_GIF_Image::frame(%d): not idle!\n", frame); + return; + } + if (frame >= 0 && frame < frames()) { + set_frame(frame); + } + else { + Fl::warning("Fl_Anim_GIF_Image::frame(%d): out of range!\n", frame); + } +} + + +/** Get the number of frames in a GIF file or in a GIF compressed data block. + + The static frame_count() method is just a convenience method for + getting the number of images (frames) stored in a GIF file. + + As this count is not readily available in the GIF header, the + whole GIF file has be parsed (which is done here by using a + temporary Fl_Anim_GIF_Image object for simplicity). + So this call may be slow with large files. + + If \p imgdata is \c NULL, the image will be read from the file. Otherwise, it will + be read from memory. + + \param[in] name path and name of GIF file in the file system, ignored + when reading from memeory + \param[in] imgdata pointer to the start of the GIF image in memory, or + \c NULL to read from a file + \param[in] imglength length of the GIF image in memory, or \c 0 + + \return the number of frames in the animation + */ +int Fl_Anim_GIF_Image::frame_count(const char *name, const unsigned char *imgdata /* = NULL */, size_t imglength /* = 0 */) { + Fl_Anim_GIF_Image temp; + temp.load(name, imgdata, imglength); + int frames = temp.valid() ? temp.frames() : 0; + return frames; +} + + +/** Return the frame position of a frame. + + Usefull only if loaded with 'optimize_mem' and + the animation also has size optimized frames. + + \param[in] frame index into frame list + \return x position in FLTK pixle units + */ +int Fl_Anim_GIF_Image::frame_x(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].x; + return -1; +} + + +/** Return the frame position of a frame. + + Usefull only if loaded with 'optimize_mem' and + the animation also has size optimized frames. + + \param[in] frame index into frame list + \return y position in FLTK pixle units + */ +int Fl_Anim_GIF_Image::frame_y(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].y; + return -1; +} + + +/** Return the frame dimensions of a frame. + + Usefull only if loaded with 'optimize_mem' and + the animation also has size optimized frames. + + \param[in] frame index into frame list + \return width in FLTK pixle units + */ +int Fl_Anim_GIF_Image::frame_w(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].w; + return -1; +} + + +/** Return the frame dimensions of a frame. + + Usefull only if loaded with 'optimize_mem' and + the animation also has size optimized frames. + + \param[in] frame index into frame list + \return height in FLTK pixle units + */ +int Fl_Anim_GIF_Image::frame_h(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].h; + return -1; +} + + +/** Use frame_uncache() to set or forbid frame image uncaching. + + If frame uncaching is set, frame images are not offscreen cached + for re-use and will be re-created every time they are displayed. + This saves a lot of memory on the expense of cpu usage and + should be carefully considered. Per default frame caching will + be done. + + \param[in] uncache true to disable caching + */ +void Fl_Anim_GIF_Image::frame_uncache(bool uncache) { + uncache_ = uncache; +} + + +/** Return the active frame_uncache() setting. + \return true if caching is disabled + */ +bool Fl_Anim_GIF_Image::frame_uncache() const { + return uncache_; +} + + +/** Get the number of frames in the animation. + \return the number of frames + */ +int Fl_Anim_GIF_Image::frames() const { + return fi_->frames_size; +} + + +/** Return the current frame image. + \return a pointer to the image or NULL if this is not an animation. + */ +Fl_Image *Fl_Anim_GIF_Image::image() const { + return frame_ >= 0 && frame_ < frames() ? fi_->frames[frame_].rgb : 0; +} + + +/** Return the image of the given frame index. + + \param[in] frame_ index into list of frames + \return image data or NULL if the frame number is not valid. + */ +Fl_Image *Fl_Anim_GIF_Image::image(int frame_) const { + if (frame_ >= 0 && frame_ < frames()) + return fi_->frames[frame_].rgb; + return 0; +} + + +/** Check if this is a valid animation with more than one frame. + + The \c is_animated() method is just a convenience method for testing the valid + flag and the frame count beeing greater 1. + + \return true if the animation is valid and has multiple frames. + */ +bool Fl_Anim_GIF_Image::is_animated() const { + return valid_ && fi_->frames_size > 1; +} + + +/*static*/ +bool Fl_GIF_Image::is_animated(const char *name) { + return Fl_Anim_GIF_Image::frame_count(name) > 1; +} + + +/** Load an animation from a file or from a memory block. + + The load() method is either used from the constructor to load the image from + the given file, or to re-load an existing animation from another file. + + \param[in] name path and name of GIF file in the file system, or the image + name when reading from memory + \param[in] imgdata pointer to the start of the GIF image in memory, or + \c NULL to read from a file + \param[in] imglength length of the GIF image in memory, or \c 0 + \return true if the animation loaded correctly + */ +bool Fl_Anim_GIF_Image::load(const char *name, const unsigned char *imgdata /* =NULL */, size_t imglength /* =0 */) { + DEBUG(("\nFl_Anim_GIF_Image::load '%s'\n", name)); + clear_frames(); + free(name_); + name_ = name ? strdup(name) : 0; + + // as load() can be called multiple times + // we have to replicate the actions of the pixmap destructor here + uncache(); + if (alloc_data) { + for (int i = 0; i < count(); i ++) delete[] (char *)data()[i]; + delete[] (char **)data(); + } + alloc_data = 0; + w(0); + h(0); + + if (name_) { + fi_->load(name, imgdata, imglength); + } + + frame_ = fi_->frames_size - 1; + valid_ = fi_->valid; + + if (!valid_) { + Fl::error("Fl_Anim_GIF_Image: %s has invalid format.\n", name_); + ld(ERR_FORMAT); + } + return valid_; +} // load + + +/** Return the name of the played file as specified in the constructor. + + If read from a memory block, this returns the name of the animation. + + \return pointer to a C string + */ +const char *Fl_Anim_GIF_Image::name() const { + return name_; +} + + +bool Fl_Anim_GIF_Image::next_frame() { + int frame(frame_); + frame++; + if (frame >= fi_->frames_size) { + fi_->loop++; + if (Fl_Anim_GIF_Image::loop && fi_->loop_count > 0 && fi_->loop > fi_->loop_count) { + DEBUG(("loop count %d reached - stopped!\n", fi_->loop_count)); + stop(); + } + else + frame = 0; + } + if (frame >= fi_->frames_size) + return false; + set_frame(frame); + double delay = fi_->frames[frame].delay; + if (min_delay && delay < min_delay) { + DEBUG(("#%d: correct delay %f => %f\n", frame, delay, min_delay)); + delay = min_delay; + } + if (is_animated() && delay > 0 && speed_ > 0) { // normal GIF has no delay + delay /= speed_; + Fl::add_timeout(delay, cb_animate, this); + } + return true; +} + + +/*virtual*/ +void Fl_Anim_GIF_Image::on_frame_data(Fl_GIF_Image::GIF_FRAME &gf) { + fi_->on_frame_data(gf); +} + + +/*virtual*/ +void Fl_Anim_GIF_Image::on_extension_data(Fl_GIF_Image::GIF_FRAME &gf) { + fi_->on_extension_data(gf); +} + + +/** Resizes the image to the specified size, replacing the current image. + + If \ref DONT_RESIZE_CANVAS is not set, the canvas widget will also be resized. + + \param[in] w, h new size of teh naimtion frames + */ +Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(int w, int h) { + int W(w); + int H(h); + if (canvas_ && !W && !H) { + W = canvas_->w(); + H = canvas_->h(); + } + if (!W || !H || ((W == this->w() && H == this->h()))) { + return *this; + } + fi_->resize(W, H); + scale_frame(); // scale current frame now + this->w(fi_->canvas_w); + this->h(fi_->canvas_h); + if (canvas_ && !(flags_ & DONT_RESIZE_CANVAS)) { + canvas_->size(this->w(), this->h()); + } + return *this; +} + + +/** Resizes the image to the specified size, replacing the current image. + + If \ref DONT_RESIZE_CANVAS is not set, the canvas widget will also be resized. + + \param[in] scale rescale factor in relation to current size + */ +Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(double scale) { + return resize((int)lround((double)w() * scale), (int)lround((double)h() * scale)); +} + + +void Fl_Anim_GIF_Image::scale_frame() { + int i(frame_); + if (i < 0) + return; + fi_->scale_frame(i); +} + + +void Fl_Anim_GIF_Image::set_frame() { + int i(frame_); + if (i < 0) + return; + fi_->set_frame(i); +} + + +void Fl_Anim_GIF_Image::set_frame(int frame) { +// int last_frame = frame_; + frame_ = frame; + // NOTE: uncaching decreases performance, but saves a lot of memory + if (uncache_ && this->image()) + this->image()->uncache(); + + fi_->set_frame(frame_); + + Fl_Widget* cv = canvas(); + if (cv) { + Fl_Group* parent = cv->parent(); + bool no_bg = (cv->box() == FL_NO_BOX); + bool outside = (!(cv->align() & FL_ALIGN_INSIDE) && !((cv->align() & FL_ALIGN_POSITION_MASK)==FL_ALIGN_CENTER)); +// bool dispose = (fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_BACKGROUND) +// || (fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_PREVIOUS); + if (parent && (no_bg || outside)) + parent->redraw(); + else + cv->redraw(); + +// Note: the code below did not animate labels with a pixmap outside of the canvas +// canvas()->parent() && +// (frame_ == 0 || (last_frame >= 0 && (fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_BACKGROUND || +// fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_PREVIOUS))) && +// (canvas()->box() == FL_NO_BOX || (canvas()->align() && !(canvas()->align() & FL_ALIGN_INSIDE))) ? +// canvas()->parent()->redraw() : canvas()->redraw(); + + } +} + + +/** Get the animation speed factor. + \return the current speed factor + */ +double Fl_Anim_GIF_Image::speed() const { + return speed_; +} + + +/** Set the animation speed factor. + + The speed() method changes the playing speed to \p speed x original speed. + E.g. to play at half speed call it with 0.5, for double speed with 2. + + \param[in] speed floating point speed factor + */ +void Fl_Anim_GIF_Image::speed(double speed) { + speed_ = speed; +} + + +/** The start() method (re-)starts the playing of the frames. + \return true if the animation has frames + */ +bool Fl_Anim_GIF_Image::start() { + Fl::remove_timeout(cb_animate, this); + if (fi_->frames_size) { + next_frame(); + } + return fi_->frames_size != 0; +} + + +/** The stop() method stops the playing of the frames. + \return true if the animation has frames + */ +bool Fl_Anim_GIF_Image::stop() { + Fl::remove_timeout(cb_animate, this); + return fi_->frames_size != 0; +} + + +/** Show the next frame if the animation is stopped. + \return true if the animation has frames + */ +bool Fl_Anim_GIF_Image::next() { + if (fi_->frames_size && !(Fl::has_timeout(cb_animate, this))) { + int f = frame() + 1; + if (f >= frames()) f = 0; + frame(f); + } + return fi_->frames_size != 0; +} + + +/** Uncache all cached image data now. + Re-implemented from Fl_Pixmap. + */ +void Fl_Anim_GIF_Image::uncache() /* override */ { + Fl_GIF_Image::uncache(); + for (int i=0; i < fi_->frames_size; i++) { + if (fi_->frames[i].rgb) fi_->frames[i].rgb->uncache(); + } +} + + +/** Check if animation is valid. + \return true if the class has successfully loaded and the image has at least + one frame. + */ +bool Fl_Anim_GIF_Image::valid() const { + return valid_; +} diff --git a/src/Fl_GIF_Image.cxx b/src/Fl_GIF_Image.cxx index a8b6250dac..93fe22f36d 100644 --- a/src/Fl_GIF_Image.cxx +++ b/src/Fl_GIF_Image.cxx @@ -1,7 +1,7 @@ // // Fl_GIF_Image routines. // -// Copyright 1997-2021 by Bill Spitzak and others. +// Copyright 1997-2023 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -73,6 +73,16 @@ */ +/* + Internally used structure to hold GIF color map data + during decoding. +*/ +struct ColorMap { + uchar Red[256]; + uchar Green[256]; + uchar Blue[256]; +}; + /* This small helper function checks for read errors or end of file and does some cleanup if an error was found. @@ -137,8 +147,8 @@ Fl_GIF_Image::Fl_GIF_Image(const char *filename) : Construct an image from a block of memory inside the application. Fluid offers "binary data" chunks as a great way to add image data into the C++ source code. - \p imagename can be NULL. If a name is given, the image is added to the list of - shared images and will be available by that name. + \p imagename can be \c NULL. If a name is given, the image is added to the + list of shared images and will be available by that name. If a GIF image is animated, Fl_GIF_Image will only read and display the first frame of the animation. @@ -205,193 +215,49 @@ Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data) : } } -/* - This method reads GIF image data and creates an RGB or RGBA image. The GIF - format supports only 1 bit for alpha. The final image data is stored in - a modified XPM format (Fl_GIF_Image is a subclass of Fl_Pixmap). - To avoid code duplication, we use an Fl_Image_Reader that reads data from - either a file or from memory. -*/ -void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) +Fl_GIF_Image::Fl_GIF_Image(const char *filename, bool anim) : + Fl_Pixmap((char *const*)0) { - char **new_data; // Data array - uchar *Image = 0L; // internal temporary image data array - w(0); h(0); - - // printf("\nFl_GIF_Image::load_gif_ : %s\n", rdr.name()); - - {char b[6] = { 0 }; - for (int i=0; i<6; ++i) b[i] = rdr.read_byte(); - if (b[0]!='G' || b[1]!='I' || b[2] != 'F') { - Fl::error("Fl_GIF_Image: %s is not a GIF file.\n", rdr.name()); - ld(ERR_FORMAT); - return; - } - if (b[3]!='8' || b[4]>'9' || b[5]!= 'a') - Fl::warning("%s is version %c%c%c.",rdr.name(),b[3],b[4],b[5]); - } - - int Width = rdr.read_word(); - int Height = rdr.read_word(); - - uchar ch = rdr.read_byte(); - CHECK_ERROR - char HasColormap = ((ch & 0x80) != 0); - int BitsPerPixel = (ch & 7) + 1; - int ColorMapSize; - if (HasColormap) { - ColorMapSize = 2 << (ch & 7); + Fl_Image_Reader rdr; + if (rdr.open(filename) == -1) { + Fl::error("Fl_GIF_Image: Unable to open %s!", filename); + ld(ERR_FILE_ACCESS); } else { - ColorMapSize = 0; - } - // int OriginalResolution = ((ch>>4)&7)+1; - // int SortedTable = (ch&8)!=0; - ch = rdr.read_byte(); // Background Color index - ch = rdr.read_byte(); // Aspect ratio is N/64 - CHECK_ERROR - - // Read in global colormap: - uchar transparent_pixel = 0; - char has_transparent = 0; - uchar Red[256], Green[256], Blue[256]; /* color map */ - if (HasColormap) { - for (int i=0; i < ColorMapSize; i++) { - Red[i] = rdr.read_byte(); - Green[i] = rdr.read_byte(); - Blue[i] = rdr.read_byte(); - } - } - CHECK_ERROR - - int CodeSize; /* Code size, init from GIF header, increases... */ - char Interlace; - - // Main parser loop: parse "blocks" until an image is found or error - - for (;;) { - - int i = rdr.read_byte(); - CHECK_ERROR - int blocklen; - - if (i == 0x21) { // a "gif extension" - ch = rdr.read_byte(); // extension type - blocklen = rdr.read_byte(); - CHECK_ERROR - - if (ch == 0xF9 && blocklen == 4) { // Graphic Control Extension - // printf("Graphic Control Extension at offset %ld\n", rdr.tell()-2); - char bits = rdr.read_byte(); // Packed Fields - rdr.read_word(); // Delay Time - transparent_pixel = rdr.read_byte(); // Transparent Color Index - blocklen = rdr.read_byte(); // Block Terminator (must be zero) - CHECK_ERROR - if (bits & 1) has_transparent = 1; - } - else if (ch == 0xFF) { // Application Extension - // printf("Application Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen); - ; // skip data - } - else if (ch == 0xFE) { // Comment Extension - // printf("Comment Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen); - ; // skip data - } - else if (ch == 0x01) { // Plain Text Extension - // printf("Plain Text Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen); - ; // skip data - } - else { - Fl::warning("%s: unknown GIF extension 0x%02x at offset %ld, length = %d", - rdr.name(), ch, rdr.tell()-3, blocklen); - ; // skip data - } - } else if (i == 0x2c) { // an image: Image Descriptor follows - // printf("Image Descriptor at offset %ld\n", rdr.tell()); - rdr.read_word(); // Image Left Position - rdr.read_word(); // Image Top Position - Width = rdr.read_word(); // Image Width - Height = rdr.read_word(); // Image Height - ch = rdr.read_byte(); // Packed Fields - CHECK_ERROR - Interlace = ((ch & 0x40) != 0); - if (ch & 0x80) { // image has local color table - // printf("Local Color Table at offset %ld\n", rdr.tell()); - BitsPerPixel = (ch & 7) + 1; - ColorMapSize = 2 << (ch & 7); - for (i=0; i < ColorMapSize; i++) { - Red[i] = rdr.read_byte(); - Green[i] = rdr.read_byte(); - Blue[i] = rdr.read_byte(); - } - } - CHECK_ERROR - break; // okay, this is the image we want - } else if (i == 0x3b) { // Trailer (end of GIF data) - // printf("Trailer found at offset %ld\n", rdr.tell()); - Fl::error("%s: no image data found.", rdr.name()); - ld(ERR_NO_IMAGE); // this GIF file is "empty" (no image) - return; // terminate - } else { - Fl::error("%s: unknown GIF code 0x%02x at offset %ld", rdr.name(), i, rdr.tell()-1); - ld(ERR_FORMAT); // broken file - return; // terminate - } - CHECK_ERROR - - // skip all data (sub)blocks: - while (blocklen > 0) { - rdr.skip(blocklen); - blocklen = rdr.read_byte(); - } - // printf("End of data (sub)blocks at offset %ld\n", rdr.tell()); - } - - // read image data - - // printf("Image Data at offset %ld\n", rdr.tell()); - - CodeSize = rdr.read_byte() + 1; // LZW Minimum Code Size - CHECK_ERROR - - if (BitsPerPixel >= CodeSize) { // Workaround for broken GIF files... - BitsPerPixel = CodeSize - 1; - ColorMapSize = 1 << BitsPerPixel; + load_gif_(rdr, anim); } +} - // Fix images w/o color table. The standard allows this and lets the - // decoder choose a default color table. The standard recommends the - // first two color table entries should be black and white. - - if (ColorMapSize == 0) { // no global and no local color table - Fl::warning("%s does not have a color table, using default.\n", rdr.name()); - BitsPerPixel = CodeSize - 1; - ColorMapSize = 1 << BitsPerPixel; - Red[0] = Green[0] = Blue[0] = 0; // black - Red[1] = Green[1] = Blue[1] = 255; // white - for (int i = 2; i < ColorMapSize; i++) { - Red[i] = Green[i] = Blue[i] = (uchar)(255 * i / (ColorMapSize - 1)); - } +Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data, const size_t length, bool anim) : + Fl_Pixmap((char *const*)0) +{ + Fl_Image_Reader rdr; + if (rdr.open(imagename, data, length) == -1) { + ld(ERR_FILE_ACCESS); + } else { + load_gif_(rdr, anim); } +} - // Fix transparent pixel index outside ColorMap (Issue #271) - if (has_transparent && transparent_pixel >= ColorMapSize) { - for (int k = ColorMapSize; k <= transparent_pixel; k++) - Red[k] = Green[k] = Blue[k] = 0xff; // white (color is irrelevant) - ColorMapSize = transparent_pixel + 1; - } +/** + The default constructor creates an empty GIF image. -#if (0) // TEST/DEBUG: fill color table to maximum size - for (int i = ColorMapSize; i < 256; i++) { - Red[i] = Green[i] = Blue[i] = 0; // black - } -#endif +*/ +Fl_GIF_Image::Fl_GIF_Image() : + Fl_Pixmap((char *const*)0) +{ +} - CHECK_ERROR - // now read the LZW compressed image data +/* + Internally used method to read from the LZW compressed data + stream 'rdr' and decode it to 'Image' buffer. - Image = new uchar[Width*Height]; + NOTE: This methode has been extracted from load_gif_() + in order to make the code more read/hand-able. +*/ +void Fl_GIF_Image::lzw_decode(Fl_Image_Reader &rdr, uchar *Image, + int Width, int Height, int CodeSize, int ColorMapSize, int Interlace) { int YC = 0, Pass = 0; /* Used to de-interlace the picture */ uchar *p = Image; uchar *eol = p+Width; @@ -419,10 +285,10 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) for (;;) { /* Fetch the next code from the raster data stream. The codes can be - * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to - * maintain our location as a pointer and a bit offset. - * In addition, GIF adds totally useless and annoying block counts - * that must be correctly skipped over. */ + * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to + * maintain our location as a pointer and a bit offset. + * In addition, GIF adds totally useless and annoying block counts + * that must be correctly skipped over. */ int CurCode = thisbyte; if (frombit+CodeSize > 7) { if (blocklen <= 0) { @@ -455,8 +321,11 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) continue; } - if (CurCode == EOFCode) + if (CurCode == EOFCode) { + rdr.skip(blocklen); + blocklen = rdr.read_byte(); // Block-Terminator must follow! break; + } uchar OutCode[4097]; // temporary array for reversing codes uchar *tp = OutCode; @@ -515,47 +384,52 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) } OldCode = CurCode; } +} + - // We are done reading the image, now convert to xpm +/* + Internally used function to convert raw 'Image' data + to XPM format in an allocated buffer 'new_data'. - w(Width); - h(Height); - d(1); + NOTE: This function has been extracted from load_gif_() + in order to make the code more read/hand-able. +*/ +static char ** convert_to_xpm(uchar *Image, int Width, int Height, ColorMap &CMap, int ColorMapSize, int transparent_pixel) { // allocate line pointer arrays: - new_data = new char*[Height+2]; + char **new_data = new char*[Height+2]; // transparent pixel must be zero, swap if it isn't: - if (has_transparent && transparent_pixel != 0) { + if (transparent_pixel > 0) { // swap transparent pixel with zero - p = Image+Width*Height; + uchar *p = Image+Width*Height; while (p-- > Image) { if (*p==transparent_pixel) *p = 0; else if (!*p) *p = transparent_pixel; } uchar t; - t = Red[0]; - Red[0] = Red[transparent_pixel]; - Red[transparent_pixel] = t; + t = CMap.Red[0]; + CMap.Red[0] = CMap.Red[transparent_pixel]; + CMap.Red[transparent_pixel] = t; - t = Green[0]; - Green[0] = Green[transparent_pixel]; - Green[transparent_pixel] = t; + t = CMap.Green[0]; + CMap.Green[0] = CMap.Green[transparent_pixel]; + CMap.Green[transparent_pixel] = t; - t = Blue[0]; - Blue[0] = Blue[transparent_pixel]; - Blue[transparent_pixel] = t; + t = CMap.Blue[0]; + CMap.Blue[0] = CMap.Blue[transparent_pixel]; + CMap.Blue[transparent_pixel] = t; } // find out what colors are actually used: uchar used[256]; uchar remap[256]; int i; for (i = 0; i < ColorMapSize; i++) used[i] = 0; - p = Image+Width*Height; + uchar *p = Image+Width*Height; while (p-- > Image) used[*p] = 1; // remap them to start with printing characters: - int base = has_transparent && used[0] ? ' ' : ' '+1; + int base = transparent_pixel >= 0 && used[0] ? ' ' : ' '+1; int numcolors = 0; for (i = 0; i < ColorMapSize; i++) if (used[i]) { remap[i] = (uchar)(base++); @@ -563,6 +437,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) } // write the first line of xpm data (use suffix as temp array): + uchar Suffix[4096]; int length = snprintf((char*)(Suffix), sizeof(Suffix), "%d %d %d %d",Width,Height,-numcolors,1); new_data[0] = new char[length+1]; @@ -572,9 +447,9 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) new_data[1] = (char*)(p = new uchar[4*numcolors]); for (i = 0; i < ColorMapSize; i++) if (used[i]) { *p++ = remap[i]; - *p++ = Red[i]; - *p++ = Green[i]; - *p++ = Blue[i]; + *p++ = CMap.Red[i]; + *p++ = CMap.Green[i]; + *p++ = CMap.Blue[i]; } // remap the image data: @@ -588,9 +463,344 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr) new_data[i + 2][Width] = 0; } - data((const char **)new_data, Height + 2); - alloc_data = 1; + return new_data; +} + + +/* + This method reads GIF image data and creates an RGB or RGBA image. The GIF + format supports only 1 bit for alpha. The final image data is stored in + a modified XPM format (Fl_GIF_Image is a subclass of Fl_Pixmap). + To avoid code duplication, we use an Fl_Image_Reader that reads data from + either a file or from memory. + + wcout 2022/01/20 - Added animated GIF support: + If the load_gif_() method is called with 'anim=true', then not only the first + image is decoded (as with Fl_GIF_Image), but all contained images are read. + The new Fl_Anim_GIF_Image class is derived from Fl_GIF_Image and utilises this + feature in order to avoid code duplication of the GIF decoding routines. + The first image is in this case (additionally) stored in the usual way as described + above (making the Fl_Anim_GIF_Image a normal Fl_GIF_Image too). + All subsequent images are only decoded (and not converted to XPM) and passed + to Fl_Anim_GIF_Image, which stores them on its own (in RGBA format). +*/ +void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr, bool anim/*=false*/) +{ + uchar *Image = 0L; // internal temporary image data array + int frame = 0; + + // Needed for proper decoding of *all* images in file (Fl_Anim_GIF_Image) + uchar background_color_index = 0; + GIF_FRAME::CPAL GlobalColorTable[256]; + bool HasGlobalColorTable = false; + GIF_FRAME::CPAL LocalColorTable[256]; + bool HasLocalColorTable = false; + + w(0); h(0); + + // printf("\nFl_GIF_Image::load_gif_ : %s\n", rdr.name()); + + {char b[6] = { 0 }; + for (int i=0; i<6; ++i) b[i] = rdr.read_byte(); + if (b[0]!='G' || b[1]!='I' || b[2] != 'F') { + Fl::error("Fl_GIF_Image: %s is not a GIF file.\n", rdr.name()); + ld(ERR_FORMAT); + return; + } + if (b[3]!='8' || b[4]>'9' || b[5]!= 'a') + Fl::warning("%s is version %c%c%c.",rdr.name(),b[3],b[4],b[5]); + } + + int ScreenWidth = rdr.read_word(); + int ScreenHeight = rdr.read_word(); + int Width = ScreenWidth; + int Height = ScreenHeight; + + uchar ch = rdr.read_byte(); + CHECK_ERROR + char HasColormap = ((ch & 0x80) != 0); + HasGlobalColorTable = HasColormap; + memset(GlobalColorTable, 0, sizeof(GlobalColorTable)); + int BitsPerPixel = (ch & 7) + 1; + int ColorMapSize = HasColormap ? 2 << (ch & 7) : 0; + // int OriginalResolution = ((ch>>4)&7)+1; + // int SortedTable = (ch&8)!=0; + background_color_index = rdr.read_byte(); // Background Color index + ch = rdr.read_byte(); // Aspect ratio is N/64 + CHECK_ERROR + + // Read in global colormap: + uchar transparent_pixel = 0; + char has_transparent = 0; + char user_input = 0; + int delay = 0; + int dispose = 0; + int XPos = 0; + int YPos = 0; + struct ColorMap CMap; /* color map */ + if (HasColormap) { + for (int i=0; i < ColorMapSize; i++) { + CMap.Red[i] = rdr.read_byte(); + CMap.Green[i] = rdr.read_byte(); + CMap.Blue[i] = rdr.read_byte(); + // store away for reading of further images in file (Fl_Anim_GIF_Image) + // because the values are changed during processing. + GlobalColorTable[i].r = CMap.Red[i]; + GlobalColorTable[i].g = CMap.Green[i]; + GlobalColorTable[i].b = CMap.Blue[i]; + } + } + CHECK_ERROR + + char Interlace = 0; + + // Main parser loop: parse "blocks" until an image is found or error + + for (;;) { + + int i = rdr.read_byte(); + CHECK_ERROR + int blocklen = 0; + + if (i == 0x21) { // a "gif extension" + ch = rdr.read_byte(); // extension type + blocklen = rdr.read_byte(); + CHECK_ERROR + + if (ch == 0xF9 && blocklen == 4) { // Graphic Control Extension + // printf("Graphic Control Extension at offset %ld\n", rdr.tell()-2); + uchar bits = rdr.read_byte(); // Packed Fields xxxDDDUT + dispose = (bits >> 2) & 7; + delay = rdr.read_word(); // Delay Time + transparent_pixel = rdr.read_byte(); // Transparent Color Index + blocklen = rdr.read_byte(); // Block Terminator (must be zero) + CHECK_ERROR + has_transparent = (bits & 1) ? 1 : 0; + user_input = (bits & 2) ? 1 : 0; + } + else if (ch == 0xFF) { // Application Extension + // printf("Application Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen); + uchar buf[512]; + memset(buf, 0, sizeof(buf)); + for (i=0; i 8) { // though invalid, other decoders accept an use it + Fl::warning("Fl_GIF_Image: %s invalid LZW-initial code size %d.\n", rdr.name(), CodeSize); + } + CodeSize++; + + // Fix images w/o color table. The standard allows this and lets the + // decoder choose a default color table. The standard recommends the + // first two color table entries should be black and white. + + if (ColorMapSize == 0) { // no global and no local color table + Fl::warning("%s does not have a color table, using default.\n", rdr.name()); + BitsPerPixel = CodeSize - 1; + ColorMapSize = 1 << BitsPerPixel; + CMap.Red[0] = CMap.Green[0] = CMap.Blue[0] = 0; // black + CMap.Red[1] = CMap.Green[1] = CMap.Blue[1] = 255; // white + for (int i = 2; i < ColorMapSize; i++) { + CMap.Red[i] = CMap.Green[i] = CMap.Blue[i] = (uchar)(255 * i / (ColorMapSize - 1)); + } + } + + // Workaround for broken GIF files... + BitsPerPixel = CodeSize - 1; + if (1 << BitsPerPixel <= 256) + ColorMapSize = 1 << BitsPerPixel; + + // Fix transparent pixel index outside ColorMap (Issue #271) + if (has_transparent && transparent_pixel >= ColorMapSize) { + for (int k = ColorMapSize; k <= transparent_pixel; k++) + CMap.Red[k] = CMap.Green[k] = CMap.Blue[k] = 0xff; // white (color is irrelevant) + ColorMapSize = transparent_pixel + 1; + } + +#if (0) // TEST/DEBUG: fill color table to maximum size + for (int i = ColorMapSize; i < 256; i++) { + CMap.Red[i] = CMap.Green[i] = CMap.Blue[i] = 0; // black + } +#endif + + CHECK_ERROR + + // now read the LZW compressed image data + + Image = new uchar[Width*Height]; + lzw_decode(rdr, Image, Width, Height, CodeSize, ColorMapSize, Interlace); + if (ld()) return; // CHECK_ERROR aborted already + + // Notify derived class on loaded image data + + GIF_FRAME gf(frame, ScreenWidth, ScreenHeight, XPos, YPos, Width, Height, Image); + gf.disposal(dispose, user_input ? -delay - 1 : delay); + gf.colors(ColorMapSize, background_color_index, has_transparent ? transparent_pixel : -1); + GIF_FRAME::CPAL cpal[256] = { { 0 } }; + if (HasLocalColorTable) + gf.cpal = LocalColorTable; + else if (HasGlobalColorTable) + gf.cpal = GlobalColorTable; + else { + for (i=0; i < ColorMapSize; i++) { + cpal[i].r = CMap.Red[i]; cpal[i].g = CMap.Green[i]; cpal[i].b = CMap.Blue[i]; + } + gf.cpal = cpal; + } +#if (0) // TEST/DEBUG: output palette values + printf("palette:\n"); + for (i=0; i ScreenWidth) xmax = ScreenWidth; + int ymax = YPos + Height; if (ymax > ScreenHeight) ymax = ScreenHeight; + for (int y = ystart; y 0) { + rdr.skip(blocklen); + blocklen = rdr.read_byte(); + } + // printf("End of data (sub)blocks at offset %ld\n", rdr.tell()); + } - delete[] Image; } // load_gif_() + + +/** + The protected load() methods are used by Fl_Anim_GIF_Image + to request loading of animated GIF's. + +*/ +void Fl_GIF_Image::load(const char* filename, bool anim) +{ + Fl_Image_Reader rdr; + if (rdr.open(filename) == -1) { + Fl::error("Fl_GIF_Image: Unable to open %s!", filename); + ld(ERR_FILE_ACCESS); + } else { + load_gif_(rdr, anim); + } +} + +void Fl_GIF_Image::load(const char *imagename, const unsigned char *data, const size_t len, bool anim) +{ + Fl_Image_Reader rdr; + if (rdr.open(imagename, data, len) == -1) { + ld(ERR_FILE_ACCESS); + } else { + load_gif_(rdr, anim); + } +} diff --git a/src/Makefile b/src/Makefile index cbba4076d9..f491ce2d8f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -230,6 +230,7 @@ IMGCPPFILES = \ Fl_BMP_Image.cxx \ Fl_File_Icon2.cxx \ Fl_GIF_Image.cxx \ + Fl_Anim_GIF_Image.cxx \ Fl_Help_Dialog.cxx \ Fl_ICO_Image.cxx \ Fl_JPEG_Image.cxx \ diff --git a/src/fl_images_core.cxx b/src/fl_images_core.cxx index 5c2a7e98bd..431e7c98c7 100644 --- a/src/fl_images_core.cxx +++ b/src/fl_images_core.cxx @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -89,7 +90,8 @@ fl_check_images(const char *name, // I - Filename if (memcmp(header, "GIF87a", 6) == 0 || memcmp(header, "GIF89a", 6) == 0) // GIF file - return new Fl_GIF_Image(name); + return Fl_GIF_Image::animate ? new Fl_Anim_GIF_Image(name) : + new Fl_GIF_Image(name); // BMP diff --git a/src/makedepend b/src/makedepend index 5db28d5a82..108b02437d 100644 --- a/src/makedepend +++ b/src/makedepend @@ -1954,6 +1954,11 @@ Fl_get_system_colors.o: flstring.h Fl_get_system_colors.o: Fl_Screen_Driver.H Fl_get_system_colors.o: Fl_System_Driver.H Fl_get_system_colors.o: tile.xpm +Fl_Anim_GIF_Image.o: ../config.h +Fl_Anim_GIF_Image.o: ../FL/Fl_Anim_GIF_Image.H +Fl_Anim_GIF_Image.o: ../FL/Fl_GIF_Image.H +Fl_Anim_GIF_Image.o: ../FL/Fl_Image.H +Fl_Anim_GIF_Image.o: ../FL/Fl_Pixmap.H Fl_GIF_Image.o: ../config.h Fl_GIF_Image.o: ../FL/Enumerations.H Fl_GIF_Image.o: ../FL/Fl.H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fc8e26ed1b..67c36fb00c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -113,7 +113,7 @@ CREATE_EXAMPLE (navigation navigation.cxx fltk) CREATE_EXAMPLE (output output.cxx fltk) CREATE_EXAMPLE (overlay overlay.cxx fltk) CREATE_EXAMPLE (pack pack.cxx fltk) -CREATE_EXAMPLE (pixmap pixmap.cxx fltk) +CREATE_EXAMPLE (pixmap pixmap.cxx "fltk_images;fltk") CREATE_EXAMPLE (pixmap_browser pixmap_browser.cxx "fltk_images;fltk") CREATE_EXAMPLE (preferences preferences.fl fltk) CREATE_EXAMPLE (offscreen offscreen.cxx fltk) diff --git a/test/Makefile b/test/Makefile index df0aee0175..193cd9d17c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -512,7 +512,10 @@ overlay$(EXEEXT): overlay.o pack$(EXEEXT): pack.o -pixmap$(EXEEXT): pixmap.o +pixmap$(EXEEXT): pixmap.o $(IMGLIBNAME) + echo Linking $@... + $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ pixmap.o $(LINKFLTKIMG) $(LDLIBS) + $(OSX_ONLY) ../fltk-config --post $@ pixmap_browser$(EXEEXT): pixmap_browser.o $(IMGLIBNAME) echo Linking $@... diff --git a/test/help_dialog.cxx b/test/help_dialog.cxx index 5970792bb1..e87c5e9226 100644 --- a/test/help_dialog.cxx +++ b/test/help_dialog.cxx @@ -24,9 +24,18 @@ // #include +#include #include /* FL_PATH_MAX */ +#include /* Fl::first_window(), etc */ #include /* strcpy(), etc */ +static void cb_refresh(void *d) { + // trigger a redraw of the window to see animated GIF's + if (Fl::first_window()) + Fl::first_window()->redraw(); + Fl::repeat_timeout(1./10, cb_refresh, d); +} + // // 'main()' - Display the help GUI... // @@ -35,6 +44,7 @@ int // O - Exit status main(int argc, // I - Number of command-line arguments char *argv[]) // I - Command-line arguments { + Fl_GIF_Image::animate = true; // create animated shared .GIF images Fl_Help_Dialog *help = new Fl_Help_Dialog; int i; if (!Fl::args(argc, argv, i)) Fl::fatal(Fl::help); @@ -44,6 +54,7 @@ main(int argc, // I - Number of command-line arguments help->show(1, argv); + Fl::add_timeout(1./10, cb_refresh, help); // to animate GIF's Fl::run(); delete help; diff --git a/test/help_dialog.html b/test/help_dialog.html index f7d7fc05ac..06492e600a 100644 --- a/test/help_dialog.html +++ b/test/help_dialog.html @@ -263,6 +263,9 @@
This is H6 text
Tiny FLTK logo Tiny FLTK logo. + + Animated FLTK logo + Animated FLTK logo. Fl_Value_Input This is an image of Fl_Value_Input diff --git a/test/pixmap.cxx b/test/pixmap.cxx index c8fb54429c..bc08164ea3 100644 --- a/test/pixmap.cxx +++ b/test/pixmap.cxx @@ -18,17 +18,20 @@ #include #include #include +#include #include -#include "pixmaps/porsche.xpm" +#include "pixmaps/animated_fluid_gif.h" #include Fl_Toggle_Button *leftb,*rightb,*topb,*bottomb,*insideb,*overb,*inactb; Fl_Button *b; Fl_Double_Window *w; +Fl_Anim_GIF_Image *pixmap; +Fl_Anim_GIF_Image *depixmap; -void button_cb(Fl_Widget *,void *) { +void button_cb(Fl_Widget *wgt,void *) { int i = 0; if (leftb->value()) i |= FL_ALIGN_LEFT; if (rightb->value()) i |= FL_ALIGN_RIGHT; @@ -42,6 +45,21 @@ void button_cb(Fl_Widget *,void *) { w->redraw(); } +void play_cb(Fl_Widget *wgt,void *) { + pixmap->start(); + depixmap->start(); +} + +void stop_cb(Fl_Widget *wgt,void *) { + pixmap->stop(); + depixmap->stop(); +} + +void step_cb(Fl_Widget *wgt,void *) { + pixmap->next(); + depixmap->next(); +} + int dvisual = 0; int arg(int, char **argv, int &i) { if (argv[i][1] == '8') {dvisual = 1; i++; return 1;} @@ -53,18 +71,24 @@ int arg(int, char **argv, int &i) { int main(int argc, char **argv) { int i = 1; if (Fl::args(argc,argv,i,arg) < argc) - Fl::fatal(" -8 # : use default visual\n%s\n",Fl::help); + Fl::fatal(" -8 # : use default visual\n%s\n", Fl::help); + if (!dvisual) Fl::visual(FL_RGB); - Fl_Double_Window window(400,400); ::w = &window; - Fl_Button b(140,160,120,120,"Pixmap"); ::b = &b; - Fl_Pixmap *pixmap = new Fl_Pixmap(porsche_xpm); - Fl_Pixmap *depixmap; - depixmap = (Fl_Pixmap *)pixmap->copy(); + Fl_Double_Window window(400,440); ::w = &window; + Fl_Button b(130,170,140,140,"Pixmap"); ::b = &b; + + Fl_Anim_GIF_Image::animate = true; + pixmap = new Fl_Anim_GIF_Image("fluid", animated_fluid_gif, animated_fluid_gif_size, + &b, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS); + pixmap->speed(0.5); + b.image(pixmap); + + depixmap = (Fl_Anim_GIF_Image*)pixmap->copy(); depixmap->inactive(); + b.deimage(depixmap); // "bind" images to avoid memory leak reports (valgrind, asan) // note: these reports are benign because they appear at exit, but anyway - b.bind_image(pixmap); b.bind_deimage(depixmap); @@ -82,7 +106,17 @@ int main(int argc, char **argv) { overb->callback(button_cb); inactb = new Fl_Toggle_Button(125,75,100,25,"inactive"); inactb->callback(button_cb); - if (!dvisual) Fl::visual(FL_RGB); + + Fl_Button* play = new Fl_Button(300, 50, 25, 25, "@>"); + play->labelcolor(FL_DARK2); + play->callback(play_cb); + Fl_Button* stop = new Fl_Button(325, 50, 25, 25, "@||"); + stop->labelcolor(FL_DARK2); + stop->callback(stop_cb); + Fl_Button* step = new Fl_Button(350, 50, 25, 25, "@|>"); + step->labelcolor(FL_DARK2); + step->callback(step_cb); + window.resizable(window); window.end(); window.show(argc,argv); diff --git a/test/pixmap_browser.cxx b/test/pixmap_browser.cxx index e9fc1ccf1e..dca13316e9 100644 --- a/test/pixmap_browser.cxx +++ b/test/pixmap_browser.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,17 @@ Fl_Shared_Image *img; static char name[1024]; +void cb_forced_redraw(void *) { + 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(1./10, cb_forced_redraw); +} + void load_file(const char *n) { if (img) { ((Fl_Shared_Image*)b->image())->release(); @@ -118,8 +130,10 @@ void svg_cb(Fl_Widget *widget, void *) { } int dvisual = 0; +int animate = 1; int arg(int, char **argv, int &i) { if (argv[i][1] == '8') {dvisual = 1; i++; return 1;} + if (argv[i][1] == 'a') {animate = 1; i++; return 1;} return 0; } @@ -131,6 +145,9 @@ int main(int argc, char **argv) { Fl::args(argc,argv,i,arg); + if (animate) + Fl_GIF_Image::animate = true; // create animated shared .GIF images (e.g. file chooser) + Fl_Double_Window window(400,450); ::w = &window; Fl_Box b(10,45,380,380); ::b = &b; b.box(FL_THIN_DOWN_BOX); @@ -146,5 +163,7 @@ int main(int argc, char **argv) { svg.callback(svg_cb); window.show(argc,argv); + if (animate) + Fl::add_timeout(1./10, cb_forced_redraw); // force periodic redraw return Fl::run(); } diff --git a/test/pixmaps/animated_fluid_gif.h b/test/pixmaps/animated_fluid_gif.h new file mode 100644 index 0000000000..19d62b0415 --- /dev/null +++ b/test/pixmaps/animated_fluid_gif.h @@ -0,0 +1,118 @@ + +static const size_t animated_fluid_gif_size = 2545; +static const unsigned char animated_fluid_gif[] = +{71,73,70,56,57,97,96,0,96,0,132,31,0,0,1,0,3,31,63,46,48,45,0,54,108,78,80, +77,64,123,116,124,126,123,125,173,98,107,166,116,171,204,63,220,233,19,253,254, +0,196,218,42,147,189,83,20,101,159,44,112,135,0,88,172,0,96,191,0,109,220,91, +161,140,68,152,169,38,140,205,0,128,255,15,133,249,45,146,236,47,148,254,108, +182,255,164,166,162,187,222,255,202,204,201,253,255,252,0,0,0,33,255,11,78,69, +84,83,67,65,80,69,50,46,48,3,1,0,0,0,33,249,4,5,10,0,31,0,44,0,0,0,0,96,0,96,0, +0,5,254,224,39,142,100,105,158,104,170,174,108,235,190,112,44,207,116,109,223, +120,110,2,124,239,3,186,160,238,71,36,14,34,61,161,18,86,244,13,32,78,139,5,201, +91,90,87,63,143,118,171,201,112,184,25,11,180,122,45,239,120,219,180,135,147, +241,174,219,223,164,121,46,234,169,211,108,111,87,163,149,211,205,118,119,96,28, +93,95,28,3,100,127,101,129,130,90,95,107,26,93,22,136,138,128,104,120,28,143, +90,124,121,97,80,149,139,151,142,109,164,111,134,153,99,160,87,140,155,145,153, +142,142,113,137,170,66,172,130,108,90,112,126,180,65,182,119,93,163,26,136,64, +188,181,162,183,112,111,25,169,197,189,199,119,121,143,108,204,205,57,190,120, +25,124,91,28,187,213,54,215,120,119,221,222,52,224,141,125,179,228,229,207,231, +105,227,234,49,230,231,239,240,47,242,141,244,245,45,247,130,249,250,88,236,218, +161,35,246,47,222,179,78,110,196,165,43,184,239,24,194,100,106,252,49,60,193, +232,33,41,77,3,39,50,17,101,17,226,22,137,26,73,84,36,69,50,225,199,133,33,254, +81,140,44,233,49,99,74,22,172,186,148,212,230,14,229,203,18,172,44,98,116,121, +51,133,47,66,26,118,158,36,216,147,98,64,129,32,95,242,83,72,180,40,206,163, +237,124,56,61,3,245,156,128,164,19,125,16,16,40,104,131,212,169,31,180,114,109, +100,224,171,83,31,99,149,37,236,64,192,236,77,31,27,198,178,204,48,180,105,72, +177,92,59,62,242,138,213,155,15,1,105,231,182,217,210,182,111,51,31,29,2,11,78, +83,216,38,60,196,105,61,8,166,91,211,49,57,31,6,34,171,37,185,147,175,229,195, +61,0,107,222,108,146,177,219,199,61,226,142,134,228,202,234,105,191,169,87,107, +246,108,23,52,15,213,178,35,211,86,135,55,183,230,198,181,85,193,245,189,250, +234,103,58,195,183,93,36,126,174,195,235,74,189,37,179,100,78,246,249,159,208, +216,88,10,165,238,1,56,45,200,91,38,115,23,100,60,120,168,219,106,196,143,103, +106,126,73,244,92,139,215,171,41,107,184,134,143,0,24,34,233,143,116,161,191, +255,11,251,5,40,224,128,4,6,24,128,117,198,0,254,16,128,4,82,52,232,224,131,16, +70,40,225,132,16,74,128,224,16,61,68,64,225,134,28,118,8,33,21,199,217,215,195, +0,30,150,104,226,132,99,132,56,67,15,1,104,248,32,6,48,62,120,193,137,52,98, +192,160,20,7,214,215,16,15,46,54,136,65,3,12,36,160,0,2,20,76,128,0,144,61,122, +120,65,3,9,84,112,227,139,8,28,144,0,3,10,56,41,133,4,57,182,183,98,15,16,60,88, +129,2,11,20,48,193,2,11,8,73,38,3,17,60,217,33,2,103,166,9,33,6,19,48,64,166,2, +110,74,145,162,150,27,241,64,226,131,114,162,201,166,2,14,200,185,64,3,117,118, +120,193,1,115,66,160,166,131,9,144,153,64,161,98,92,104,15,139,139,82,64,38,2, +16,68,48,1,5,16,128,25,38,164,28,126,57,36,168,82,92,32,40,166,139,102,137,39, +76,25,202,40,232,3,105,74,32,65,5,115,58,32,43,141,18,144,42,5,173,11,0,10,42, +150,58,62,197,67,151,47,38,170,225,140,108,150,169,40,174,183,74,152,172,2,138, +46,58,133,164,42,254,68,225,96,5,19,52,112,38,2,19,220,216,232,2,168,74,128,65, +145,7,80,192,32,156,155,58,152,65,5,69,54,43,5,5,9,36,48,193,183,13,44,27,225, +48,42,10,11,192,158,14,206,235,169,2,65,106,136,129,167,176,86,32,40,153,182, +90,128,232,160,26,82,224,233,160,79,98,208,232,144,7,23,96,111,132,170,202,224, +131,180,22,152,74,166,197,178,74,96,105,175,209,122,188,0,154,12,10,58,65,154, +23,12,76,230,202,231,202,233,235,152,189,218,202,177,20,17,100,105,144,158,18, +186,188,128,3,246,46,92,47,131,38,31,160,40,175,63,187,233,51,172,82,124,91,64, +154,52,51,144,233,134,22,246,101,109,132,35,51,96,179,20,130,62,205,32,175,10, +192,122,65,178,82,19,157,245,212,52,39,144,169,4,218,46,96,244,205,14,226,187, +234,125,20,38,171,246,141,24,144,249,243,178,81,71,43,232,219,82,180,253,54,6, +42,47,75,176,174,16,234,204,42,15,27,126,139,64,157,125,223,40,184,162,35,47,0, +235,141,93,67,109,172,5,35,67,235,238,134,254,146,250,144,100,132,135,75,78,38, +224,25,148,254,45,180,46,130,237,128,134,223,222,205,182,163,203,194,237,224, +157,62,177,184,97,222,100,70,219,177,167,94,115,158,168,4,19,120,58,180,20,125, +71,112,129,167,143,139,203,252,197,29,202,109,20,15,11,110,120,118,235,181,62, +112,128,5,52,215,27,168,227,16,32,192,224,228,13,60,160,55,200,109,91,94,0,5,38, +2,59,142,238,28,78,126,35,205,128,54,57,187,219,129,82,208,117,3,221,90,0,252, +1,7,144,128,167,140,118,36,48,133,13,101,237,59,141,15,136,213,184,143,65,78, +111,10,40,128,172,210,55,36,1,206,233,81,12,122,24,6,41,152,0,243,205,233,1,159, +235,16,238,234,48,162,14,185,204,115,13,50,216,201,130,247,165,147,193,12,81,10, +120,27,131,254,212,128,215,137,75,91,10,168,87,4,26,197,128,203,209,104,18,82, +185,218,134,120,165,67,7,229,42,86,13,58,34,18,143,232,174,92,133,236,74,76,12, +89,4,88,246,67,11,232,140,82,20,194,128,147,46,64,51,31,62,232,86,79,122,148, +226,141,158,104,196,207,49,72,2,51,234,216,25,171,104,129,170,253,32,0,27,146, +147,248,26,37,59,54,218,241,142,86,36,2,28,41,68,166,6,208,202,87,182,195,163, +32,39,164,42,234,109,8,1,10,152,82,15,17,55,200,70,74,168,144,10,226,144,3,40, +96,195,16,58,242,146,247,210,99,135,66,22,72,76,98,18,146,0,128,158,39,71,121, +34,9,224,238,141,1,72,165,42,87,201,202,86,186,242,149,176,140,165,44,103,201, +202,31,144,176,9,184,204,165,46,119,201,203,94,246,82,36,190,12,166,48,135,73, +76,34,76,175,152,200,76,102,49,75,16,2,0,33,249,4,5,10,0,31,0,44,30,0,4,0,38,0, +57,0,0,5,254,224,39,142,31,96,158,104,170,2,100,235,174,112,236,206,174,103, +223,56,158,210,60,205,113,185,91,111,152,201,136,56,25,100,50,232,25,246,126,71, +227,143,233,57,57,121,72,13,105,169,51,93,105,154,204,141,243,225,222,172,95, +151,70,227,209,126,128,65,116,186,85,92,127,168,115,44,156,218,204,147,236,111, +100,83,113,94,126,128,69,101,98,132,44,115,124,124,114,126,45,123,57,145,88,102, +56,149,60,110,153,156,157,158,159,160,161,162,149,142,120,145,165,76,164,168, +148,167,171,152,173,174,54,170,177,125,126,180,178,176,177,179,76,28,107,26,100, +163,34,97,69,69,192,162,74,196,197,163,201,201,155,160,204,196,206,159,208,117, +163,195,204,198,160,200,209,66,161,189,35,147,184,78,183,169,227,228,229,78,189, +191,231,226,62,208,225,186,154,208,108,231,150,212,240,174,238,247,236,181,36, +219,201,217,60,253,35,22,80,224,187,96,111,0,130,226,199,176,161,67,90,219,128, +61,196,49,144,204,68,27,3,139,93,244,144,49,137,175,143,32,67,138,244,149,225, +130,201,147,38,3,51,132,0,0,33,249,4,5,10,0,31,0,44,34,0,7,0,34,0,54,0,0,5,202, +224,39,138,94,105,158,168,57,174,236,154,190,104,43,143,112,237,205,179,13,227, +178,254,242,45,95,10,200,18,198,136,52,163,10,73,82,150,152,77,39,116,74,141,42, +167,206,39,52,123,171,122,191,224,176,120,76,46,155,207,232,244,151,195,230,156, +57,154,140,60,227,94,107,52,245,185,190,62,229,232,229,113,127,114,85,129,130, +134,83,1,126,130,23,134,25,136,138,127,140,135,80,137,141,134,124,76,144,127, +112,43,28,90,80,154,114,158,30,109,163,93,125,123,58,72,92,75,64,172,39,106,177, +76,175,63,69,175,112,119,152,46,172,133,162,61,92,161,190,182,89,141,26,65,92, +197,199,196,134,198,195,78,189,115,186,31,172,193,205,100,112,209,68,144,120, +102,208,116,180,90,193,25,26,224,93,222,114,229,34,231,25,233,31,25,23,240,241, +240,119,244,245,246,247,244,33,0,33,249,4,5,10,0,31,0,44,36,0,13,0,26,0,48,0,0, +5,196,224,39,138,156,102,114,99,170,174,100,230,186,26,43,143,220,107,163,243, +26,104,182,157,235,188,158,235,167,218,9,135,196,84,77,24,75,166,130,55,167, +106,153,209,224,164,88,44,103,123,205,150,162,217,207,177,155,164,246,178,208, +51,54,237,211,30,51,225,177,103,78,247,252,190,175,79,189,46,219,115,57,123, +124,44,129,132,123,125,133,136,42,0,139,136,137,41,139,0,141,30,93,100,31,144, +141,102,46,28,1,131,133,98,71,14,157,132,153,47,26,156,41,152,111,149,57,164, +154,104,76,97,164,171,101,39,78,84,86,97,31,108,25,128,146,83,71,26,146,118,79, +111,194,42,187,46,198,196,71,202,52,114,190,191,54,193,205,74,38,86,194,216,217, +218,219,136,102,171,194,178,43,146,173,188,42,227,170,199,214,234,214,25,23,238, +239,238,112,41,33,0,33,249,4,5,10,0,31,0,44,38,0,5,0,21,0,56,0,0,5,232,224,39, +142,80,52,158,104,202,101,25,151,190,98,166,105,172,11,167,44,171,125,156,125, +139,171,26,173,245,27,245,56,195,93,17,185,11,250,96,131,97,203,89,20,205,106, +181,170,199,99,67,102,127,219,174,231,11,3,132,129,231,159,57,204,217,186,223, +111,145,185,7,175,199,81,109,207,76,249,91,105,182,82,79,48,109,121,71,85,64,68, +135,39,52,127,112,76,26,130,136,132,133,57,58,47,121,34,92,149,57,28,1,153,118, +122,155,57,3,159,118,82,155,164,31,160,161,162,25,169,171,65,155,26,175,171,167, +53,158,170,171,154,58,79,186,160,138,192,193,194,39,190,118,55,73,160,0,0,120, +162,121,111,2,56,173,117,27,203,70,173,45,117,204,173,206,110,218,205,198,39, +215,191,39,177,156,197,110,73,96,231,235,117,71,189,190,167,124,240,162,74,186, +229,149,46,246,215,249,176,251,185,160,247,56,85,177,37,67,81,188,17,231,220,17, +99,199,176,225,186,61,16,35,74,220,19,2,0,59}; + diff --git a/test/pixmaps/fltk_animated.gif b/test/pixmaps/fltk_animated.gif new file mode 100644 index 0000000000..bff543a050 Binary files /dev/null and b/test/pixmaps/fltk_animated.gif differ diff --git a/test/pixmaps/fltk_animated2.gif b/test/pixmaps/fltk_animated2.gif new file mode 100644 index 0000000000..52b8a8359f Binary files /dev/null and b/test/pixmaps/fltk_animated2.gif differ diff --git a/test/pixmaps/fltk_animated3.gif b/test/pixmaps/fltk_animated3.gif new file mode 100644 index 0000000000..8656c4d6a8 Binary files /dev/null and b/test/pixmaps/fltk_animated3.gif differ diff --git a/test/tiled_image.cxx b/test/tiled_image.cxx index 54e4043079..4b313827d0 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -47,11 +47,10 @@ int arg(int argc, char **argv, int &i) { } int main(int argc, char **argv) { -#ifdef FLTK_USE_X11 int i = 1; - Fl::args(argc,argv,i,arg); - + +#ifdef FLTK_USE_X11 if (visid >= 0) { fl_open_display(); XVisualInfo templt; int num; diff --git a/test/utf8.cxx b/test/utf8.cxx index 2f2d928a18..8cd8763eb9 100644 --- a/test/utf8.cxx +++ b/test/utf8.cxx @@ -609,7 +609,7 @@ int main(int argc, char** argv) end_list /= 16; } argc = 1; - for (long y = off; y < end_list; y++) { + for (int y = off; y < end_list; y++) { int o = 0; char bu[25]; // index label char buf[16 * 6]; // utf8 text @@ -622,7 +622,7 @@ int main(int argc, char** argv) i++; } buf[o] = '\0'; - snprintf(bu, sizeof(bu), "0x%06lX", y * 16); + snprintf(bu, sizeof(bu), "0x%06X", y * 16); Fl_Input *b = new Fl_Input(200,(y-off)*25,80,25); b->textfont(FL_COURIER); b->value(fl_strdup(bu));