From 8a418f36848bb9653e354955f0558053f9a93700 Mon Sep 17 00:00:00 2001 From: wcout Date: Wed, 2 Feb 2022 19:13:34 +0100 Subject: [PATCH 01/13] Animated GIF support (Fl_Anim_GIF_Image class) and test programs. This class is an extension to Fl_GIF_Image and allows loading and viewing of animated GIF images. Fl_Anim_GIF_Image is a complete implementation of an FLTK image class (derived from Fl_GIF_Image) capable to be used as shared image, tiled image, scalable image, like any other FLTK image class. Fl_GIF_Image's code has been modified to load/parse *all* images of a GIF image file (instead of only the first image) when instructed. Fl_Anim_GIF_Image utilises this and stores all images (frames) on its own as RGB images and automatically starts animating them. It needs be told a 'canvas' widget, wherein to draw the images to. Demo program 'test/animgifimage' and example programs are included. --- FL/Fl_Anim_GIF_Image.H | 295 ++++++++++ FL/Fl_GIF_Image.H | 45 +- examples/CMakeLists.txt | 3 + examples/Makefile | 6 +- examples/animgifimage-play.cxx | 241 ++++++++ examples/animgifimage-resize.cxx | 185 ++++++ examples/animgifimage-simple.cxx | 40 ++ src/CMakeLists.txt | 1 + src/Fl_Anim_GIF_Image.cxx | 967 +++++++++++++++++++++++++++++++ src/Fl_GIF_Image.cxx | 600 ++++++++++++------- src/Makefile | 1 + src/fl_images_core.cxx | 4 +- src/makedepend | 5 + test/CMakeLists.txt | 5 +- test/Makefile | 15 +- test/animgifimage.cxx | 302 ++++++++++ test/demo.menu | 3 + test/help_dialog.cxx | 11 + test/help_dialog.html | 3 + test/pixmap.cxx | 19 +- test/pixmap_browser.cxx | 19 + test/pixmaps/fltk_animated.gif | Bin 0 -> 116547 bytes test/pixmaps/fltk_animated2.gif | Bin 0 -> 12618 bytes test/pixmaps/fltk_animated3.gif | Bin 0 -> 60554 bytes test/tiled_image.cxx | 12 +- 25 files changed, 2564 insertions(+), 218 deletions(-) create mode 100644 FL/Fl_Anim_GIF_Image.H create mode 100644 examples/animgifimage-play.cxx create mode 100644 examples/animgifimage-resize.cxx create mode 100644 examples/animgifimage-simple.cxx create mode 100644 src/Fl_Anim_GIF_Image.cxx create mode 100644 test/animgifimage.cxx create mode 100644 test/pixmaps/fltk_animated.gif create mode 100644 test/pixmaps/fltk_animated2.gif create mode 100644 test/pixmaps/fltk_animated3.gif diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H new file mode 100644 index 0000000000..da5300a9ac --- /dev/null +++ b/FL/Fl_Anim_GIF_Image.H @@ -0,0 +1,295 @@ +// +// 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 + +/** + 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. + + You 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. + */ +class FL_EXPORT Fl_Anim_GIF_Image : public Fl_GIF_Image { + + class FrameInfo; // internal helper class + +public: + + /** + When opening a 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 start() method. + */ + DontStart = 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. + */ + DontResizeCanvas = 2, + /** + This flag indicates to the loader that it should not + set the animation as image() member of the canvas widget, + which is the default. + Needed for special use cases. + */ + DontSetAsImage = 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. + */ + OptimizeMemory = 8, + /** + This flag can be used to print informations about the + decoding process to the console. + */ + Log = 64, + /** + This flag can be used to print even more informations about + the decoding process to the console. + */ + Debug = 128 + }; + /** + The constructor creates an new animated gif object from + the given file. + Optionally it applies the canvas() method after successful load. + If not 'DontStart' is specified in the 'flags' parameter it calls start() + after successful load. + */ + Fl_Anim_GIF_Image(const char *name, Fl_Widget *canvas = 0, unsigned short flags = 0); + Fl_Anim_GIF_Image(); + virtual ~Fl_Anim_GIF_Image(); + /** + The canvas() method sets or gets the current widget, that + is used for the display of the frame images. + The _flags_ parameter specifies wheather the canvas widget + is resized to the animation dimensions and/or its image() + method will be used to set the current frame image + during animation. + */ + void canvas(Fl_Widget *canvas, unsigned short flags = 0); + Fl_Widget *canvas() const; + /** + Return the width and height of the animation canvas as + specified in the GIF file header + */ + int canvas_w() const; + /** + Return the width and height of the animation canvas as + specified in the GIF file header + */ + int canvas_h() const; + /** + The color_average() method applies the specified color_average + to all frames of the animation. + */ + virtual void color_average(Fl_Color c, float i); + /** + 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. + */ + virtual Fl_Image *copy(int W, int H); + int debug() const; + /** + The desaturate() method applies desaturate() to all frames + of the animation. + */ + virtual void desaturate(); + virtual void draw(int x, int y, int w, int h, int cx = 0, int cy = 0); + /** + Return the delay of frame 'frame_' `[0-frames() -1]` in seconds + */ + double delay(int frame_) const; + /** + Set the delay of frame 'frame_' `[0-frames() -1]` in seconds + */ + void delay(int frame, double delay); + /** + Return the number of frames. + */ + int frames() const; + /** + Set the current frame in the range index `[0-frames() -1]` + */ + void frame(int frame); + /** + Return the current frame in the range index `[0-frames() -1]` + or -1 if the image has no frames. + */ + int frame() const; + /** + Return the current frame image. + */ + Fl_Image *image() const; + /** + Return the frame image of frame 'frame_' + */ + Fl_Image *image(int frame) const; + /** + The is_animated() method is just a convenience method for + testing the valid flag and the frame count beeing greater 1. + */ + bool is_animated() const; + /** + 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. + */ + static int frame_count(const char *name); + /** + 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. + */ + void frame_uncache(bool uncache); + /** + Return the active frame_uncache() setting. + */ + bool frame_uncache() const; + /** + 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. + */ + bool load(const char *name); + /** + 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; + /** + Return the name of the played file as specified in the constructor. + */ + const char *name() const; + /** + Return if the animation is currently running or stopped. + */ + bool playing() const { return valid() && Fl::has_timeout(cb_animate, (void *)this); } + /** + The start() method (re-)starts the playing of the frames. + */ + bool start(); + /** + The stop() method stops the playing of the frames. + */ + bool stop(); + /** + The resize() method resizes the image to the + specified size, replacing the current image. + */ + Fl_Anim_GIF_Image& resize(int w, int h); + Fl_Anim_GIF_Image& resize(double scale); + /** + The speed() method changes the playing speed + to 'speed' x original speed. E.g. to play at half + speed call it with 0.5, for double speed with 2. + */ + void speed(double speed); + double speed() const; + /** + Uncache all cached image data now. Re-implemented from Fl_Pixmap. + */ + virtual void uncache(); + /** + The valid() method returns if the class has + successfully loaded and the image has at least + one frame. + */ + bool valid() const; + /** + Return the frame position of frame 'frame' + Usefull only if loaded with 'optimize_mem' and + the animation also has size optimized frames. + */ + int frame_x(int frame) const; + int frame_y(int frame) const; + /** + Return the frame dimensions of frame 'frame'. + Usefull only if loaded with 'optimize_mem' and + the animation also has size optimized frames. + */ + int frame_w(int frame) const; + int frame_h(int frame) const; + +protected: + + bool next_frame(); + void clear_frames(); + void set_frame(int frame); + +private: + static void cb_animate(void *d); + void scale_frame(); + void set_frame(); + virtual void on_frame_data(Fl_GIF_Image::GIF_FRAME &f); + virtual void on_extension_data(Fl_GIF_Image::GIF_FRAME &f); + +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/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2bc33557a0..2ecb09a7a4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -81,6 +81,9 @@ set (FLUID_SOURCES ############################################################ set (IMAGE_SOURCES + animgifimage-play + animgifimage-resize + animgifimage-simple howto-simple-svg ) diff --git a/examples/Makefile b/examples/Makefile index dc312aa7a6..aab1c99d25 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -22,7 +22,11 @@ SHELL = /bin/sh .SILENT: # Executables -ALL = browser-simple$(EXEEXT) \ +ALL = animgifimage-play$(EXEEXT) \ + animgifimage-simple$(EXEEXT) \ + animgifimage-resize$(EXEEXT) \ + browser-simple$(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..b11c065e88 --- /dev/null +++ b/examples/animgifimage-play.cxx @@ -0,0 +1,241 @@ +// +// 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()) { + if (Fl::get_key('+')) + change_speed(1); + else if (Fl::get_key('-')) + change_speed(-1); + else if (Fl::get_key(FL_Enter)) + change_speed(0); + else if (Fl::get_key('n')) + load_next(); + else if (Fl::get_key('z')) + zoom(Fl::event_shift()); + else if (Fl::get_key('i')) + toggle_info(); // Note: this can raise cpu usage considerably! + else if (Fl::get_key('r')) + toggle_reverse(); + else if (Fl::get_key(' ')) + toggle_pause(); + else if (paused && Fl::get_key(FL_Right)) + next_frame(); + else + return 0; + Fl::first_window()->redraw(); + } + return 1; +} + +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 'DontResizeCanvas' flag to tell the animation + // not to change the canvas size (which is the default). + unsigned short flags = Fl_Anim_GIF_Image::DontResizeCanvas; +// flags |= Fl_Anim_GIF_Image::Debug|Fl_Anim_GIF_Image::Log; + 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..acacc4da78 --- /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::get_key('g')) { + draw_grid = !draw_grid; + printf("grid: %s\n", (draw_grid ? "ON" : "OFF")); + } + else if (Fl::get_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) {} + virtual void draw() { + 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::DontResizeCanvas); + } + window()->cursor(FL_CURSOR_DEFAULT); + } + static void do_resize_cb(void *d) { + Canvas *c = (Canvas *)d; + c->do_resize(c->w(), c->h()); + } + virtual void resize(int x, int y, int w, int h) { + 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 'DontResizeCanvas' 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::DontResizeCanvas; + if (optimize) { + flags |= Fl_Anim_GIF_Image::OptimizeMemory; + printf("Using memory optimization (if image supports)\n"); + } + if (debug) { + flags |= Fl_Anim_GIF_Image::Debug; + } + 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..9b16661386 --- /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 `DontResizeCanvas` 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::DontResizeCanvas); + // 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/src/CMakeLists.txt b/src/CMakeLists.txt index c263639724..428c3a6303 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -469,6 +469,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..c8a4dc7e45 --- /dev/null +++ b/src/Fl_Anim_GIF_Image.cxx @@ -0,0 +1,967 @@ +// +// Fl_Anim_GIF_Image class 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 +// + +#include +#include +#include +#include +#include +#include +#include +#include // lround() + +#include + +/*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); + 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 ig 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 = lround(fi.frames[i].w * scale_factor_x); + int new_h = 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) { + // decode using FLTK + valid = false; + 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 = lround(frames[i].w * scale_factor_x); + int new_h = 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 +// + +Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *name, + 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) != 0) + 2 * ((flags_ & Debug) != 0); + fi_->optimize_mem = (flags_ & OptimizeMemory); + valid_ = load(name); + if (canvas_w() && canvas_h()) { + if (!w() && !h()) { + w(canvas_w()); + h(canvas_h()); + } + } + this->canvas(canvas, flags); + if (!(flags & DontStart)) + start(); + else + frame_ = 0; +} + + +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)) { +} + + +/*virtual*/ +Fl_Anim_GIF_Image::~Fl_Anim_GIF_Image() { + Fl::remove_timeout(cb_animate, this); + delete fi_; + free(name_); +} + + +void Fl_Anim_GIF_Image::canvas(Fl_Widget *canvas, unsigned short flags/* = 0*/) { + if (canvas_) + canvas_->image(0); + canvas_ = canvas; + if (canvas_ && !(flags & DontSetAsImage)) + canvas_->image(this); // set animation as image() of canvas + if (canvas_ && !(flags & DontResizeCanvas)) + canvas_->size(w(), h()); + if (flags_ != flags) { + flags_ = flags; + fi_->debug_ = ((flags & Log) != 0) + 2 * ((flags & Debug) != 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); + } +} + + +Fl_Widget *Fl_Anim_GIF_Image::canvas() const { + return canvas_; +} + + +int Fl_Anim_GIF_Image::canvas_w() const { + return fi_->canvas_w; +} + + +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; +} + + +/*virtual*/ +void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) { + 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; + set_frame(); +} + + +/*virtual*/ +Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) { + 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) { + int ow = w(); + int oh = h(); + w(fi_->frames[0].w); + h(fi_->frames[0].h); + 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; + w(ow); + h(oh); + } + + 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; + 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(); +} + + +double Fl_Anim_GIF_Image::delay(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].delay; + return 0.; +} + + +void Fl_Anim_GIF_Image::delay(int frame, double delay) { + if (frame >= 0 && frame < frames()) + fi_->frames[frame].delay = delay; +} + + +/*virtual*/ +void Fl_Anim_GIF_Image::desaturate() { + fi_->desaturate = true; + set_frame(); +} + + +/*virtual*/ +void Fl_Anim_GIF_Image::draw(int x, int y, int w, int h, int cx/* = 0*/, int cy/* = 0*/) { + 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); + } +} + + +int Fl_Anim_GIF_Image::frame() const { + return frame_; +} + + +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); + } +} + + +/*static*/ +int Fl_Anim_GIF_Image::frame_count(const char *name) { + Fl_Anim_GIF_Image temp; + temp.load(name); + int frames = temp.valid() ? temp.frames() : 0; + return frames; +} + + +int Fl_Anim_GIF_Image::frame_x(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].x; + return -1; +} + + +int Fl_Anim_GIF_Image::frame_y(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].y; + return -1; +} + + +int Fl_Anim_GIF_Image::frame_w(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].w; + return -1; +} + +int Fl_Anim_GIF_Image::frame_h(int frame) const { + if (frame >= 0 && frame < frames()) + return fi_->frames[frame].h; + return -1; +} + + +void Fl_Anim_GIF_Image::frame_uncache(bool uncache) { + uncache_ = uncache; +} + + +bool Fl_Anim_GIF_Image::frame_uncache() const { + return uncache_; +} + + +int Fl_Anim_GIF_Image::frames() const { + return fi_->frames_size; +} + + +Fl_Image *Fl_Anim_GIF_Image::image() const { + return frame_ >= 0 && frame_ < frames() ? fi_->frames[frame_].rgb : 0; +} + + +Fl_Image *Fl_Anim_GIF_Image::image(int frame_) const { + if (frame_ >= 0 && frame_ < frames()) + return fi_->frames[frame_].rgb; + return 0; +} + + +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; +} + + +bool Fl_Anim_GIF_Image::load(const char *name) { + 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); + } + + 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 + + +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); +} + + +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_ & DontResizeCanvas)) { + canvas_->size(this->w(), this->h()); + } + return *this; +} + + +Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(double scale) { + return resize(lround((double)w() * scale), 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_); + + if (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(); + } +} + + +double Fl_Anim_GIF_Image::speed() const { + return speed_; +} + + +void Fl_Anim_GIF_Image::speed(double speed) { + speed_ = speed; +} + + +bool Fl_Anim_GIF_Image::start() { + Fl::remove_timeout(cb_animate, this); + if (fi_->frames_size) { + next_frame(); + } + return fi_->frames_size != 0; +} + + +bool Fl_Anim_GIF_Image::stop() { + Fl::remove_timeout(cb_animate, this); + return fi_->frames_size != 0; +} + + +/*virtual*/ +void Fl_Anim_GIF_Image::uncache() { + Fl_GIF_Image::uncache(); + for (int i=0; i < fi_->frames_size; i++) { + if (fi_->frames[i].rgb) fi_->frames[i].rgb->uncache(); + } +} + + +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..40a5e5e405 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-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 @@ -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. @@ -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 - w(Width); - h(Height); - d(1); +/* + Internally used function to convert raw 'Image' data + to XPM format in an allocated buffer 'new_data'. + + 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,322 @@ 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; + + 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 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 dddb7fcb4f..4ebc70816d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -228,6 +228,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..7ce12ac085 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,6 +58,7 @@ set (extra_tests) ####################################################################### CREATE_EXAMPLE (adjuster adjuster.cxx fltk) +CREATE_EXAMPLE (animgifimage animgifimage.cxx "fltk_images;fltk") CREATE_EXAMPLE (arc arc.cxx fltk) CREATE_EXAMPLE (animated animated.cxx fltk) CREATE_EXAMPLE (ask ask.cxx fltk) @@ -113,7 +114,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) @@ -139,7 +140,7 @@ CREATE_EXAMPLE (tabs tabs.fl fltk) CREATE_EXAMPLE (table table.cxx fltk) CREATE_EXAMPLE (threads threads.cxx fltk) CREATE_EXAMPLE (tile tile.cxx fltk) -CREATE_EXAMPLE (tiled_image tiled_image.cxx fltk) +CREATE_EXAMPLE (tiled_image tiled_image.cxx "fltk_images;fltk") CREATE_EXAMPLE (tree tree.fl fltk) CREATE_EXAMPLE (twowin twowin.cxx fltk) CREATE_EXAMPLE (utf8 utf8.cxx fltk) diff --git a/test/Makefile b/test/Makefile index df0aee0175..9455f8ed32 100644 --- a/test/Makefile +++ b/test/Makefile @@ -51,6 +51,7 @@ OBJUNITTEST = \ CPPFILES =\ adjuster.cxx \ animated.cxx \ + animgifimage.cxx \ arc.cxx \ ask.cxx \ bitmap.cxx \ @@ -145,6 +146,7 @@ CPPFILES =\ ALL = \ animated$(EXEEXT) \ + animgifimage$(EXEEXT) \ adjuster$(EXEEXT) \ arc$(EXEEXT) \ ask$(EXEEXT) \ @@ -343,6 +345,10 @@ adjuster$(EXEEXT): adjuster.o animated$(EXEEXT): animated.o +animgifimage$(EXEEXT): animgifimage.o $(IMGLIBNAME) + echo Linking $@... + $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) animgifimage.o -o $@ $(LINKFLTKIMG) $(LDLIBS) + arc$(EXEEXT): arc.o ask$(EXEEXT): ask.o @@ -512,7 +518,9 @@ 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) pixmap_browser$(EXEEXT): pixmap_browser.o $(IMGLIBNAME) echo Linking $@... @@ -616,7 +624,10 @@ threads.o: threads.h tile$(EXEEXT): tile.o -tiled_image$(EXEEXT): tiled_image.o +tiled_image$(EXEEXT): tiled_image.o $(IMGLIBNAME) + echo Linking $@... + $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) tiled_image.o -o $@ $(LINKFLTKIMG) $(LDLIBS) + $(OSX_ONLY) ../fltk-config --post $@ tree$(EXEEXT): tree.o tree.cxx: tree.fl ../fluid/fluid$(EXEEXT) diff --git a/test/animgifimage.cxx b/test/animgifimage.cxx new file mode 100644 index 0000000000..c5dc9082d9 --- /dev/null +++ b/test/animgifimage.cxx @@ -0,0 +1,302 @@ +// +// 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 : 0; + if (debug > 1) + gif_flags |= Fl_Anim_GIF_Image::Debug; + if (optimize_mem) + gif_flags |= Fl_Anim_GIF_Image::OptimizeMemory; + + // 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::DontResizeCanvas | Fl_Anim_GIF_Image::DontSetAsImage ); + 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; +} + +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/test/demo.menu b/test/demo.menu index 16cce02da3..02666c7acb 100644 --- a/test/demo.menu +++ b/test/demo.menu @@ -39,10 +39,13 @@ @d:Images...:@di @di:Fl_Bitmap:bitmap @di:Fl_Pixmap:pixmap + @di:Fl_Pixmap\nanimated:pixmap -a @di:Fl_RGB\n_Image:image @di:Fl_Shared\n_Image:pixmap_browser @di:Fl_Tiled\n_Image:tiled_image + @di:Fl_Tiled\n_Image\nanimated:tiled_image -a @di:transparency:animated + @di:Fl_Anim\n_GIF_Image:animgifimage -t pixmaps @d:cursor:cursor @d:labels:label @d:offscreen:offscreen 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 fe79f87588..0c6b14ff34 100644 --- a/test/pixmap.cxx +++ b/test/pixmap.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "pixmaps/porsche.xpm" @@ -28,7 +29,7 @@ Fl_Toggle_Button *leftb,*rightb,*topb,*bottomb,*insideb,*overb,*inactb; Fl_Button *b; Fl_Double_Window *w; -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; @@ -43,8 +44,10 @@ void button_cb(Fl_Widget *,void *) { } int dvisual = 0; +int animate = 0; 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; } @@ -53,11 +56,21 @@ 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" + " -a : use animated GIF as pixmap\n%s\n",Fl::help); 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 *pixmap = 0; + if (!animate) + pixmap = new Fl_Pixmap(porsche_xpm); + else { + Fl_Anim_GIF_Image *anim = new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif"); + anim->canvas(&b, Fl_Anim_GIF_Image::DontResizeCanvas); + anim->scale(96,96,1,1); + pixmap = anim; + anim->start(); + } Fl_Pixmap *depixmap; depixmap = (Fl_Pixmap *)pixmap->copy(); depixmap->inactive(); diff --git a/test/pixmap_browser.cxx b/test/pixmap_browser.cxx index e9fc1ccf1e..7ae23b9791 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 = 0; 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/fltk_animated.gif b/test/pixmaps/fltk_animated.gif new file mode 100644 index 0000000000000000000000000000000000000000..bff543a050a9213473a2851fbfd60c07d78e44d8 GIT binary patch literal 116547 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 Ar2qf` literal 0 HcmV?d00001 diff --git a/test/pixmaps/fltk_animated2.gif b/test/pixmaps/fltk_animated2.gif new file mode 100644 index 0000000000000000000000000000000000000000..52b8a8359f6a250a774970cd5a9b9a1a08ecefe7 GIT binary patch literal 12618 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 C4sOx_ literal 0 HcmV?d00001 diff --git a/test/pixmaps/fltk_animated3.gif b/test/pixmaps/fltk_animated3.gif new file mode 100644 index 0000000000000000000000000000000000000000..8656c4d6a8750de1faa08833d9b1ed5cf9352374 GIT binary patch literal 60554 zcmYhCRa6uV+qEa??v9}whM`lsJEU>wMnXCqx;uv)y1P+y=tiVLKw3aVL{LKId)~GF zga7P4+iRa*_rBCM)g-0tgn&rEtt?m~0P+BU3IL!90O$jN z&#?ho0DvPFzyJWW0RTL)0cN-WTL3Tu0P@EH1ONamU?3wi#GRktSW?2$(J2N1h!qzP zhrt}YUV6vG#xgLxW@HRuVF?lwD^O4@($UFwa?JJc=uAlH&&X^;A{&s!$mW*$_O|}E z&NlQ2dUSMec7AqkV|DA(`N7ft<<%vGC7Xvk=ked8g_R{0DGUnL;^2VE$jYi~YCb(Z zu{`1HshSuoD;lW61fgKye}!1?F);A}vH<@7ZovO)0co0ngZOOk*fB_$$2l~$4xR9Dqi#x#ZzITSgixq5kJV`JqM4h+62jEyX< zs78&BjgJveOpl{cM72$A$tmt`o>@59n^;(HcXmJPCyb0tOc9)%d?Eh&^?d5$YVxdR zbgulcG0G*?!v}YL(~}aQ-o97!VtTR1>W-*vG??f^l)XfAsfyF=1~C@kZ~W>A#1a02<>c&vKV$Y`-7i;-h6sRPfGex3NQAFj2A{9-tJ=p=HVVqh*` zl@{eev-LuNIP+YIfr!UifCyvc5S?#@bkfi40Sv0dJX*ERMjipTuSbZ6;5Ve9NnN$i z11%i3{+#!GjcH9JCV!rq*Sy*779)HodjF;EnQ~HOcy6v6dNA<&31dXUwMt=vahR#> zhR@eEOU%#wvi-sitLZ_Oq7!Smb<2-z7q?c$*1$!wUMM!bsb-RGUm=si>765aqOK6`}?PB1$eWQz3q4Z zjq8wvasa%RMozb36GfwD4v9(pyHSdevfn81GAj+t4}oMK%;mhhVJY!}UyWKA0ega% z{GbaPC9)R;Q>9soYd42T%-U&G5g{m~7U?9v!HA@#3fwOx4yY#xt=VQX5p9b#JZ9^5 zyx%m|7f2{CFjTCo2u!tD&nxooJUgk$cOK--%o#x*#@MgLTV;PQnK*hG@k?q-J$k}r zTcga)A=@t*4*;mbxW_I;sJ+{CD?(#^+G{89Cb=joa^?k^DdB~f$0iciS9sCyc311) z)T^^XiU+nnBwOO;o&xU16Fmg0|NMSxwkMiM;^{!sy*a}gFXG|v^df5FuPuy!X!fXl zUd(R!_U{+~16>o1q)kTmVS$L-xSJvl4}!i9@Eg)5 zJhf6v=w#ypzH5#Y8(^M3Pk7s!lo~fgU-sjMOR8;^#3Dy~`?#p5wxnS`Bs&}Vfno&x z8E_0=IE~4b_miL)-DOj?7p%wsegU6R=|QSmNhts?{XGR=(pxlrzecEyv? zru+*`H^p+gJanKEG}WLspA-Ri-VouYmTgb?3aV??2*^IDk7&mi5h(kDI#+El zjy2i&!^5A64fTbjurB@;McAXtM(@Vv*jo-K9vWF5*Aq>BKyxmD()Fxh1TViRiPWDZ zg-@@*&A2->ze@~50RK(x;(++e93g-5_@W+htpm4`%S0>nM~JEArDC*dgFF3A{#0$M zev_VmwH$bF^(}&ukK7MA6o0M*8_bF?PrGzk*U~Mo^{sz!XZVx*ruo+@sTWbvn)~i^ zUQSAfa_r@&UA{^nS@+Qm34a+$U3e|O66|_sLv%S=K$u(5Cg+n~FeGk6LrgeWpiZeHgL#wt<9hwV>{I1+!QA19mbo;w_ zN;+i80gRM}p5u?tA)Q!?+Gq@#j~Cg^lhvyoMeEi!UC-C&YHL2v_j_1CT6IA92i*sTg?2!(G1N&zs~$55+2rTgDna5W$=H7%!`SHldZ%OEzgj$W{S zCyEWW0LD_>OKU!ojh7!j0K=-qrtZ#c=#=PsMjde_}@OEA;bqLx)K zv5o8Xsb?2*`0R~@6-oT(=L;XRP9?g;RX!ReP)J+_zHAHo-PYpzA#CaKXlx-SE%WiI z>6o(^`~wu z4=+7{EBEeN^uZLW_9m#-1kc4iHDYIZo$Gb2ARFDIS-`h(Q z&XwpYF0>lUc2Y z;h2YDr;|0A^=sn*44+{01V1Yu>!%L38FE8-ovvHn>j!Kvf)lmh>aV3I9YBRbm!rx} zC$ip?s-I6-R2`vbrZ_Zxuio4QeT_Gds&h(FeUYZ>XO<9bs}OB%rY>FW=@JlFycX@w z88X%v?tT1h>qZGgqab?|Ms+Ji1%rySN=Jwj>;(BntuUE#GWQ@{g+SKHAV8l>z*tQf zYa-^v5w*Lr;fTGgb3#aIO`M>lzkPziteEnZgEH4CpE@QFHHXT60ApoB1pbQp=pq?& z5BAuiUD3y&2s2X>WwPcN*OZzl`hM42QEUCg0JdN{>80RpOayQDYp1%zr+LhnB%F|Z zFaop$d2)(*#tNC~Po1kz-S|jDx#liy7*wu75Z*4J%o0_5s)q*<=Cya#JhejKN-(io zb4=Rb{z@>0dy2zozJcr<$OC`4g1MA4#Gyd(%nWfK@Sly0KawB-Z6-hygkh11WdUOM z0pTJuIeb7w_n9QLS!6s}+<+{~kSrq2Owyz*nh+4>Sr!)@ghxw12V&aoby2~4&*y-<~t;TB@Mtn zJOv^KV863`F@pjz0GJz^7iv*}&;*MpfTK0RFeo^Ir;yVJ{5IQUYTSfdikR95#JdQ} zKLh1kAbH!7B@M{Z0VFdD$mx@FR-4lvtvI;mjW!8OoiNo)Oh4O*)I|YU4T>2IK!k1} z4nUBC=<`AgswosnPnH7WoKs~AwanxwEX8Lh!+K{4oR-N)4RwoO1`ne zgao8urHs@>j#61rWui^k4Qg1D`GBD$r3_mYOb-NL1>NgV_8=5FkOBT*K-pJh)#YZ{wdQ|$C`KQk>Rr$tdDy@E z@}~Rb)-w%^E5(H0u91?MS8m7z+6ovPgrChQFOllp`)mZa5>8iZPgmftSsSET8;%5V zHkN)$D&-rj6v+e$;RPvEBMA(u_^&|FE7bXB-DOf4qNohvUQS0+ZQ{(a`;*r!o?|G^ z$ci;Ukv8SGi64ZfxHAdk^d7_ys36q>^8-LCOUeySmU^_Z;_Z13MU9P(B`#Ve;~}N4 zTBQyRrLU0%UX}%AS|EaU#wRKmRr@%A6<#KQt_zn2pAD4B@YesZDCdP1g?$N)ix*8; zsi*B%{L<%YL*@DWO9GdKr+z!=tN~O)S0U0~jLjNRxSE@JSJSlBdRbJ`&BPC=(U~&g%!UivdgF7@Z`x|w0;@+ir&1PS3+Ko$ zX2BgafKQgWxQ!@XXg#re4G#ch`jhh$sklhI5cgH9AYZM^R%4)Mt?zUJhkL1SQfdE8 zTi)tjE3z)6>cA8Ol!Ssz2A~B^K zu#3{q%`u_}>kf-RFmhwCc}xjRc4DOUmDQVO_u78#E`QAeNqCosds9DN9Y5V$C`tco zMZ4HHLLrtYbSx*wY$9%#{^3KrcL=o*PUHloLvBH(+i5#~$>vw3dfZzt_` z&}m37$zV530VKJ^^F3PVmk7T&8WieQfWUnl(vbfTKfmR?BCH|L+@cMO0Bnjx2 zBhe9OSx@g%DtQ|#V>dxjvoTc(1g!9?Mn3SOReR`GS;m`oy-&>ySMmKO<|e-ccIzdm z>ov4#-VLjwY-L`K-Bx2ibZ8;U^3H~zKQuJ32$INrP1mq?;W%IaQYXt7l4keDGN$c! zhGoU>rbNwec$i z;GM{DNu`kx(c5Crz!?nj@qjSUO<`c!gjgK6VWRK^w7v2{<}Eq@Y@8+-3I{1_i%kJQ zCj)iw-p)gsyC;g85$+>|Xwp8Xx!QhTEg-H&p_A~1$AOHWY0zkm$6#ja`~n)-_=sA@ zO|x4HC=77+?ABe+T>??V#eKTqjSJG4W1)G4kGPfAAV?yOW*eA_2YeBZ8oT z#tq&11VcmoBN5UgLV#-4MKY!}(wF;8k0BiUiPbdPd>|;Q75P!u(of2I{>i}#L&*Vz z=`me+k$$-xFjeRYRC|td;#L*_=JqLJlm>I5pGk2kLzNMftCZsCnV8LGx9#O;LmyZG z#$OG}z}_;>et;A&{rV~=96Slnb^x;>-ovHA?ClG@gb2`az>E#KqxHs3>cl<& zEEB`Z1pt)rrUB#IC~M1#PH{W-x9-(h;8`{FfkZLfg~CiXO4EV+ovN_HW)HjiU}WK_ zofSr8c%9VNJ8l$Fsgc`&vhzTiTUo6d(j|OY8D#RO0ty99%r88VQNm`^Xdvhr82rPt zovvsea4apD6AFSDtZ-)5B}_MvNG~#PuY~c|8_fd4prT5{P!b4t1kH!EYw?fFR{1|J{`R8(;H0AHD-2$4g9^THg) ze6XC{L_1hNe@Gjl-#YYheGVK!0^E}A6xu7bA6ET7B@}R*_~TBqW^k!OY&>|rf*%UP zEjXTcFwEPag!q62ND7@)>}Gef&SiB_Bc;UzXFHgr-6B5^3PVhT%7s(h9-9mfA&0oD_N7>{LcpA5laLo2@ zxl2sE`zG8<_}dq%%Y3+*zl&v^H%0D0lPgSgE4;fLeY_k_o;|_bSqYV%WA(ucn#3h+ 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 #include #include +#include #include #include #include @@ -36,6 +37,7 @@ void button_cb(Fl_Widget *,void *) { #include "list_visuals.cxx" int visid = -1; +int anim = 0; int arg(int argc, char **argv, int &i) { if (argv[i][1] == 'v') { if (i+1 >= argc) return 0; @@ -43,6 +45,11 @@ int arg(int argc, char **argv, int &i) { i += 2; return 2; } + else if (argv[i][1] == 'a') { + anim = 1; + i ++; + return 1; + } return 0; } @@ -72,7 +79,10 @@ int main(int argc, char **argv) { Fl_Double_Window window(400,400); ::w = &window; Fl_Group group(0,0,400,400); - group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); + if (anim) + group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DontResizeCanvas))); + else + group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); group.align(FL_ALIGN_INSIDE); Fl_Button b(340,365,50,25,"Close"); ::b = &b; From a2ed2d7b3e584fbe4cf8a83377ee89ec1042f8e2 Mon Sep 17 00:00:00 2001 From: wcout Date: Thu, 21 Apr 2022 19:38:51 +0200 Subject: [PATCH 02/13] Fix compilation after change #10537b7143a06a4c0c1c4caba4f0150f9e290090 which made Fl_Image::copy() a 'const' method --- FL/Fl_Anim_GIF_Image.H | 2 +- src/Fl_Anim_GIF_Image.cxx | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H index da5300a9ac..1c706829ee 100644 --- a/FL/Fl_Anim_GIF_Image.H +++ b/FL/Fl_Anim_GIF_Image.H @@ -126,7 +126,7 @@ public: and resizes all of its frame images to W x H using the current resize method. */ - virtual Fl_Image *copy(int W, int H); + virtual Fl_Image *copy(int W, int H) const; int debug() const; /** The desaturate() method applies desaturate() to all frames diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx index c8a4dc7e45..837e369ca3 100644 --- a/src/Fl_Anim_GIF_Image.cxx +++ b/src/Fl_Anim_GIF_Image.cxx @@ -606,23 +606,17 @@ void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) { /*virtual*/ -Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) { +Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) const { 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) { - int ow = w(); - int oh = h(); - w(fi_->frames[0].w); - h(fi_->frames[0].h); 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; - w(ow); - h(oh); } copied->w(W); @@ -633,7 +627,7 @@ Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) { copied->uncache_ = uncache_; // copy 'inherits' frame uncache status copied->valid_ = valid_ && copied->fi_->frames_size == fi_->frames_size; - scale_frame(); // scale current frame now + 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; From 9eb3c329db32fd47e345b161b702a56119267956 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 6 Jan 2023 00:40:48 +0100 Subject: [PATCH 03/13] Using event_key instead of get_key Returning 0 in handler if event was not used to make ESC key work --- examples/animgifimage-play.cxx | 38 ++++++++++++++------------------ examples/animgifimage-resize.cxx | 4 ++-- test/animgifimage.cxx | 3 ++- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/examples/animgifimage-play.cxx b/examples/animgifimage-play.cxx index b11c065e88..9a1362e300 100644 --- a/examples/animgifimage-play.cxx +++ b/examples/animgifimage-play.cxx @@ -163,29 +163,25 @@ static void load_next() { static int events(int event_) { if (event_ == FL_SHORTCUT && Fl::first_window()) { - if (Fl::get_key('+')) - change_speed(1); - else if (Fl::get_key('-')) - change_speed(-1); - else if (Fl::get_key(FL_Enter)) - change_speed(0); - else if (Fl::get_key('n')) - load_next(); - else if (Fl::get_key('z')) - zoom(Fl::event_shift()); - else if (Fl::get_key('i')) - toggle_info(); // Note: this can raise cpu usage considerably! - else if (Fl::get_key('r')) - toggle_reverse(); - else if (Fl::get_key(' ')) - toggle_pause(); - else if (paused && Fl::get_key(FL_Right)) - next_frame(); - else - return 0; + 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 1; + return 0; } int main(int argc, char *argv[]) { diff --git a/examples/animgifimage-resize.cxx b/examples/animgifimage-resize.cxx index acacc4da78..ff27ccf301 100644 --- a/examples/animgifimage-resize.cxx +++ b/examples/animgifimage-resize.cxx @@ -15,11 +15,11 @@ static bool draw_grid = true; static int events(int event_) { if (event_ == FL_SHORTCUT && Fl::first_window()) { - if (Fl::get_key('g')) { + if (Fl::event_key()=='g') { draw_grid = !draw_grid; printf("grid: %s\n", (draw_grid ? "ON" : "OFF")); } - else if (Fl::get_key('b')) { + else if (Fl::event_key()=='b') { if (Fl_Image::scaling_algorithm() != FL_RGB_SCALING_BILINEAR) Fl_Image::scaling_algorithm(FL_RGB_SCALING_BILINEAR); else diff --git a/test/animgifimage.cxx b/test/animgifimage.cxx index c5dc9082d9..93fc377983 100644 --- a/test/animgifimage.cxx +++ b/test/animgifimage.cxx @@ -235,8 +235,9 @@ static int events(int event) { change_speed(0); else return 0; + return 1; } - return 1; + return 0; } static const char testsuite[] = "testsuite"; From 1c9706465d4381232bead88752c598859051a41e Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 6 Jan 2023 01:22:48 +0100 Subject: [PATCH 04/13] Changing flags to CAPS to match the CMP --- FL/Fl_Anim_GIF_Image.H | 31 +++++++++++++++---------------- examples/animgifimage-play.cxx | 6 +++--- examples/animgifimage-resize.cxx | 10 +++++----- examples/animgifimage-simple.cxx | 4 ++-- src/Fl_Anim_GIF_Image.cxx | 24 ++++++++++++------------ src/Fl_GIF_Image.cxx | 2 +- test/animgifimage.cxx | 8 ++++---- test/pixmap.cxx | 2 +- test/tiled_image.cxx | 2 +- 9 files changed, 44 insertions(+), 45 deletions(-) diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H index 1c706829ee..b3004dd07c 100644 --- a/FL/Fl_Anim_GIF_Image.H +++ b/FL/Fl_Anim_GIF_Image.H @@ -51,21 +51,21 @@ public: the default. It can be started later using the start() method. */ - DontStart = 1, + 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. */ - DontResizeCanvas = 2, + DONT_RESIZE_CANVAS = 2, /** This flag indicates to the loader that it should not set the animation as image() member of the canvas widget, which is the default. Needed for special use cases. */ - DontSetAsImage = 4, + 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, @@ -74,28 +74,28 @@ public: The drawbacks are higher cpu usage during playback and maybe minor artefacts when resized. */ - OptimizeMemory = 8, + OPTIMIZE_MEMORY = 8, /** This flag can be used to print informations about the decoding process to the console. */ - Log = 64, + LOG_FLAG = 64, /** This flag can be used to print even more informations about the decoding process to the console. */ - Debug = 128 + DEBUG_FLAG = 128 }; /** The constructor creates an new animated gif object from the given file. Optionally it applies the canvas() method after successful load. - If not 'DontStart' is specified in the 'flags' parameter it calls start() + If not 'DONT_START' is specified in the 'flags' parameter it calls start() after successful load. */ Fl_Anim_GIF_Image(const char *name, Fl_Widget *canvas = 0, unsigned short flags = 0); Fl_Anim_GIF_Image(); - virtual ~Fl_Anim_GIF_Image(); + ~Fl_Anim_GIF_Image() FL_OVERRIDE; /** The canvas() method sets or gets the current widget, that is used for the display of the frame images. @@ -120,20 +120,20 @@ public: The color_average() method applies the specified color_average to all frames of the animation. */ - virtual void color_average(Fl_Color c, float i); + void color_average(Fl_Color c, float i) FL_OVERRIDE; /** 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. */ - virtual Fl_Image *copy(int W, int H) const; + Fl_Image *copy(int W, int H) const FL_OVERRIDE; int debug() const; /** The desaturate() method applies desaturate() to all frames of the animation. */ - virtual void desaturate(); - virtual void draw(int x, int y, int w, int h, int cx = 0, int cy = 0); + void desaturate() FL_OVERRIDE; + void draw(int x, int y, int w, int h, int cx = 0, int cy = 0) FL_OVERRIDE; /** Return the delay of frame 'frame_' `[0-frames() -1]` in seconds */ @@ -245,7 +245,7 @@ public: /** Uncache all cached image data now. Re-implemented from Fl_Pixmap. */ - virtual void uncache(); + void uncache() FL_OVERRIDE; /** The valid() method returns if the class has successfully loaded and the image has at least @@ -273,12 +273,11 @@ protected: void clear_frames(); void set_frame(int frame); -private: static void cb_animate(void *d); void scale_frame(); void set_frame(); - virtual void on_frame_data(Fl_GIF_Image::GIF_FRAME &f); - virtual void on_extension_data(Fl_GIF_Image::GIF_FRAME &f); + 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: diff --git a/examples/animgifimage-play.cxx b/examples/animgifimage-play.cxx index 9a1362e300..4a319f8428 100644 --- a/examples/animgifimage-play.cxx +++ b/examples/animgifimage-play.cxx @@ -226,10 +226,10 @@ int main(int argc, char *argv[]) { win.show(); Fl::add_handler(events); - // use the 'DontResizeCanvas' flag to tell the animation + // 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::DontResizeCanvas; -// flags |= Fl_Anim_GIF_Image::Debug|Fl_Anim_GIF_Image::Log; + 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(); diff --git a/examples/animgifimage-resize.cxx b/examples/animgifimage-resize.cxx index ff27ccf301..700454c0a4 100644 --- a/examples/animgifimage-resize.cxx +++ b/examples/animgifimage-resize.cxx @@ -70,7 +70,7 @@ class Canvas : public Fl_Box { else { printf("resized to %d x %d\n", copied->w(), copied->h()); } - copied->canvas(this, Fl_Anim_GIF_Image::DontResizeCanvas); + copied->canvas(this, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS); } window()->cursor(FL_CURSOR_DEFAULT); } @@ -133,15 +133,15 @@ int main(int argc, char *argv[]) { win.show(); // create/load the animated gif and start it immediately. - // We use the 'DontResizeCanvas' flag here to tell the + // 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::DontResizeCanvas; + int flags = Fl_Anim_GIF_Image::Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS; if (optimize) { - flags |= Fl_Anim_GIF_Image::OptimizeMemory; + flags |= Fl_Anim_GIF_Image::OPTIMIZE_MEMORY; printf("Using memory optimization (if image supports)\n"); } if (debug) { - flags |= Fl_Anim_GIF_Image::Debug; + flags |= Fl_Anim_GIF_Image::DEBUG_FLAG; } orig = new Fl_Anim_GIF_Image(/*name_=*/ fileName, /*canvas_=*/ &canvas, diff --git a/examples/animgifimage-simple.cxx b/examples/animgifimage-simple.cxx index 9b16661386..19f9732f28 100644 --- a/examples/animgifimage-simple.cxx +++ b/examples/animgifimage-simple.cxx @@ -22,12 +22,12 @@ int main(int argc, char *argv[]) { // Create and load the animated gif as image // of the `canvas` widget and start it immediately. - // We use the `DontResizeCanvas` flag here to tell the + // 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::DontResizeCanvas); + /*flags_=*/ Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS); // resize animation to canvas size animgif.scale(canvas.w(), canvas.h(), /*can_expand*/1, /*proportional*/1); diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx index 837e369ca3..092c4b5106 100644 --- a/src/Fl_Anim_GIF_Image.cxx +++ b/src/Fl_Anim_GIF_Image.cxx @@ -187,8 +187,8 @@ void Fl_Anim_GIF_Image::FrameInfo::copy(const FrameInfo& fi) { 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 = lround(fi.frames[i].w * scale_factor_x); - int new_h = lround(fi.frames[i].h * 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; } @@ -387,8 +387,8 @@ void Fl_Anim_GIF_Image::FrameInfo::resize(int W, int H) { 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 = lround(frames[i].w * scale_factor_x); - int new_h = lround(frames[i].h * 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; } @@ -498,8 +498,8 @@ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *name, frame_(-1), speed_(1.), fi_(new FrameInfo(this)) { - fi_->debug_ = ((flags_ & Log) != 0) + 2 * ((flags_ & Debug) != 0); - fi_->optimize_mem = (flags_ & OptimizeMemory); + fi_->debug_ = ((flags_ & LOG_FLAG) != 0) + 2 * ((flags_ & DEBUG_FLAG) != 0); + fi_->optimize_mem = (flags_ & OPTIMIZE_MEMORY); valid_ = load(name); if (canvas_w() && canvas_h()) { if (!w() && !h()) { @@ -508,7 +508,7 @@ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *name, } } this->canvas(canvas, flags); - if (!(flags & DontStart)) + if (!(flags & DONT_START)) start(); else frame_ = 0; @@ -540,13 +540,13 @@ void Fl_Anim_GIF_Image::canvas(Fl_Widget *canvas, unsigned short flags/* = 0*/) if (canvas_) canvas_->image(0); canvas_ = canvas; - if (canvas_ && !(flags & DontSetAsImage)) + if (canvas_ && !(flags & DONT_SET_AS_IMAGE)) canvas_->image(this); // set animation as image() of canvas - if (canvas_ && !(flags & DontResizeCanvas)) + if (canvas_ && !(flags & DONT_RESIZE_CANVAS)) canvas_->size(w(), h()); if (flags_ != flags) { flags_ = flags; - fi_->debug_ = ((flags & Log) != 0) + 2 * ((flags & Debug) != 0); + 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. @@ -875,7 +875,7 @@ Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(int w, int h) { scale_frame(); // scale current frame now this->w(fi_->canvas_w); this->h(fi_->canvas_h); - if (canvas_ && !(flags_ & DontResizeCanvas)) { + if (canvas_ && !(flags_ & DONT_RESIZE_CANVAS)) { canvas_->size(this->w(), this->h()); } return *this; @@ -883,7 +883,7 @@ Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(int w, int h) { Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(double scale) { - return resize(lround((double)w() * scale), lround((double)h() * scale)); + return resize((int)lround((double)w() * scale), (int)lround((double)h() * scale)); } diff --git a/src/Fl_GIF_Image.cxx b/src/Fl_GIF_Image.cxx index 40a5e5e405..31a20cb244 100644 --- a/src/Fl_GIF_Image.cxx +++ b/src/Fl_GIF_Image.cxx @@ -694,7 +694,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr, bool anim/*=false*/) 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 }; + GIF_FRAME::CPAL cpal[256] = { { 0 } }; if (HasLocalColorTable) gf.cpal = LocalColorTable; else if (HasGlobalColorTable) diff --git a/test/animgifimage.cxx b/test/animgifimage.cxx index 93fc377983..515b76b651 100644 --- a/test/animgifimage.cxx +++ b/test/animgifimage.cxx @@ -72,11 +72,11 @@ Fl_Window *openFile(const char *name, char *flags, bool close = false) { // 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 : 0; + unsigned short gif_flags = debug ? Fl_Anim_GIF_Image::LOG_FLAG : 0; if (debug > 1) - gif_flags |= Fl_Anim_GIF_Image::Debug; + gif_flags |= Fl_Anim_GIF_Image::DEBUG_FLAG; if (optimize_mem) - gif_flags |= Fl_Anim_GIF_Image::OptimizeMemory; + 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); @@ -116,7 +116,7 @@ Fl_Window *openFile(const char *name, char *flags, bool close = false) { 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::DontResizeCanvas | Fl_Anim_GIF_Image::DontSetAsImage ); + 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: diff --git a/test/pixmap.cxx b/test/pixmap.cxx index 0c6b14ff34..e045c54a7a 100644 --- a/test/pixmap.cxx +++ b/test/pixmap.cxx @@ -66,7 +66,7 @@ int main(int argc, char **argv) { pixmap = new Fl_Pixmap(porsche_xpm); else { Fl_Anim_GIF_Image *anim = new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif"); - anim->canvas(&b, Fl_Anim_GIF_Image::DontResizeCanvas); + anim->canvas(&b, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS); anim->scale(96,96,1,1); pixmap = anim; anim->start(); diff --git a/test/tiled_image.cxx b/test/tiled_image.cxx index e5c71844e5..79c632e4b4 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -80,7 +80,7 @@ int main(int argc, char **argv) { Fl_Double_Window window(400,400); ::w = &window; Fl_Group group(0,0,400,400); if (anim) - group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DontResizeCanvas))); + group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS))); else group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); group.align(FL_ALIGN_INSIDE); From 19297d776c72a12a6c9829f9522cc5a91a49fe75 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 6 Jan 2023 02:14:58 +0100 Subject: [PATCH 05/13] Fixing some typos --- FL/Fl_Anim_GIF_Image.H | 14 +++++++------- examples/Makefile | 1 - src/Fl_Anim_GIF_Image.cxx | 4 ++-- src/Fl_GIF_Image.cxx | 2 +- test/tiled_image.cxx | 5 ++--- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H index b3004dd07c..ccbd936140 100644 --- a/FL/Fl_Anim_GIF_Image.H +++ b/FL/Fl_Anim_GIF_Image.H @@ -41,7 +41,7 @@ class FL_EXPORT Fl_Anim_GIF_Image : public Fl_GIF_Image { public: /** - When opening a Fl_Anim_GIF_Image there are some options + When opening an Fl_Anim_GIF_Image there are some options that can be passed in a 'flags' value. */ enum Flags { @@ -68,7 +68,7 @@ public: 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, + 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 @@ -90,7 +90,7 @@ public: The constructor creates an new animated gif object from the given file. Optionally it applies the canvas() method after successful load. - If not 'DONT_START' is specified in the 'flags' parameter it calls start() + If 'DONT_START' is not specified in the 'flags' parameter it calls start() after successful load. */ Fl_Anim_GIF_Image(const char *name, Fl_Widget *canvas = 0, unsigned short flags = 0); @@ -98,8 +98,8 @@ public: ~Fl_Anim_GIF_Image() FL_OVERRIDE; /** The canvas() method sets or gets the current widget, that - is used for the display of the frame images. - The _flags_ parameter specifies wheather the canvas widget + is used to display the frame images. + The _flags_ parameter specifies whether the canvas widget is resized to the animation dimensions and/or its image() method will be used to set the current frame image during animation. @@ -182,7 +182,7 @@ public: 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 + This saves a lot of memory on the expense of cpu usage and should be carefully considered. Per default frame caching will be done. */ @@ -209,7 +209,7 @@ public: /** 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. + 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; diff --git a/examples/Makefile b/examples/Makefile index aab1c99d25..2534d6ed0e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -26,7 +26,6 @@ ALL = animgifimage-play$(EXEEXT) \ animgifimage-simple$(EXEEXT) \ animgifimage-resize$(EXEEXT) \ browser-simple$(EXEEXT) \ - browser-simple$(EXEEXT) \ cairo-draw-x$(EXEEXT) \ chart-simple$(EXEEXT) \ draggable-group$(EXEEXT) \ diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx index 092c4b5106..657512cd9f 100644 --- a/src/Fl_Anim_GIF_Image.cxx +++ b/src/Fl_Anim_GIF_Image.cxx @@ -1,7 +1,7 @@ // // Fl_Anim_GIF_Image class for the Fast Light Tool Kit (FLTK). // -// Copyright 2016-2022 by Christian Grabner . +// 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 @@ -109,7 +109,7 @@ class Fl_Anim_GIF_Image::FrameInfo { void set_frame(int frame); private: Fl_Anim_GIF_Image *anim; // a pointer to the Image (only needed for name()) - bool valid; // flag ig valid data + 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 diff --git a/src/Fl_GIF_Image.cxx b/src/Fl_GIF_Image.cxx index 31a20cb244..7b9ee05a65 100644 --- a/src/Fl_GIF_Image.cxx +++ b/src/Fl_GIF_Image.cxx @@ -1,7 +1,7 @@ // // Fl_GIF_Image routines. // -// Copyright 1997-2022 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 diff --git a/test/tiled_image.cxx b/test/tiled_image.cxx index 79c632e4b4..6e981caaad 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -54,11 +54,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; From 4bde0186ded3c5beab2b7ca9b05b753e5ca080f7 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 6 Jan 2023 20:44:06 +0100 Subject: [PATCH 06/13] Animated GIF from inline data --- FL/Fl_Anim_GIF_Image.H | 10 ++++--- src/Fl_Anim_GIF_Image.cxx | 55 ++++++++++++++++++++++++++++++++------- test/tiled_image.cxx | 7 ++--- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H index ccbd936140..60a0f70a9e 100644 --- a/FL/Fl_Anim_GIF_Image.H +++ b/FL/Fl_Anim_GIF_Image.H @@ -93,7 +93,11 @@ public: If 'DONT_START' is not specified in the 'flags' parameter it calls start() after successful load. */ - Fl_Anim_GIF_Image(const char *name, Fl_Widget *canvas = 0, unsigned short flags = 0); + Fl_Anim_GIF_Image(const char *filename, Fl_Widget *canvas = 0, unsigned short flags = 0); + // constructor with length + 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; /** @@ -177,7 +181,7 @@ public: temporary Fl_Anim_GIF_Image object for simplicity). So this call may be slow with large files. */ - static int frame_count(const char *name); + static int frame_count(const char *name, const unsigned char *imgdata = NULL, size_t imglength = 0); /** Use frame_uncache() to set or forbid frame image uncaching. If frame uncaching is set, frame images are not offscreen cached @@ -196,7 +200,7 @@ public: the image from the given file, or to re-load an existing animation from another file. */ - bool load(const char *name); + bool load(const char *name, const unsigned char *imgdata, size_t imglength); /** The loop flag can be used to (dis-)allow loop count. If set (which is the default), the animation will be diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx index 657512cd9f..65dfdb8f9a 100644 --- a/src/Fl_Anim_GIF_Image.cxx +++ b/src/Fl_Anim_GIF_Image.cxx @@ -102,7 +102,7 @@ class Fl_Anim_GIF_Image::FrameInfo { void copy(const FrameInfo& fi); double convert_delay(int d) const; int debug() const { return debug_; } - bool load(const char *name); + 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); @@ -249,10 +249,14 @@ void Fl_Anim_GIF_Image::FrameInfo::dispose(int frame) { } -bool Fl_Anim_GIF_Image::FrameInfo::load(const char *name) { +bool Fl_Anim_GIF_Image::FrameInfo::load(const char *name, const unsigned char *data, size_t length) { // decode using FLTK valid = false; - anim->Fl_GIF_Image::load(name, true); // calls on_frame_data() for each frame + 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; @@ -486,7 +490,7 @@ bool Fl_Anim_GIF_Image::loop = true; // class Fl_Anim_GIF_Image implementation // -Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *name, +Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *filename, Fl_Widget *canvas/* = 0*/, unsigned short flags/* = 0 */) : Fl_GIF_Image(), @@ -497,10 +501,41 @@ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *name, valid_(false), frame_(-1), speed_(1.), - fi_(new FrameInfo(this)) { + 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; +} + + +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(name); + valid_ = load(imagename, data, length); if (canvas_w() && canvas_h()) { if (!w() && !h()) { w(canvas_w()); @@ -711,9 +746,9 @@ void Fl_Anim_GIF_Image::frame(int frame) { /*static*/ -int Fl_Anim_GIF_Image::frame_count(const char *name) { +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); + temp.load(name, imgdata, imglength); int frames = temp.valid() ? temp.frames() : 0; return frames; } @@ -784,7 +819,7 @@ bool Fl_GIF_Image::is_animated(const char *name) { } -bool Fl_Anim_GIF_Image::load(const char *name) { +bool Fl_Anim_GIF_Image::load(const char *name, const unsigned char *imgdata, size_t imglength) { DEBUG(("\nFl_Anim_GIF_Image::load '%s'\n", name)); clear_frames(); free(name_); @@ -802,7 +837,7 @@ bool Fl_Anim_GIF_Image::load(const char *name) { h(0); if (name_) { - fi_->load(name); + fi_->load(name, imgdata, imglength); } frame_ = fi_->frames_size - 1; diff --git a/test/tiled_image.cxx b/test/tiled_image.cxx index 6e981caaad..e5c71844e5 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -54,10 +54,11 @@ 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; @@ -79,7 +80,7 @@ int main(int argc, char **argv) { Fl_Double_Window window(400,400); ::w = &window; Fl_Group group(0,0,400,400); if (anim) - group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS))); + group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DontResizeCanvas))); else group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); group.align(FL_ALIGN_INSIDE); From 44a1b0f2b40883ee2fb80f449b9123fa5641c4cc Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 6 Jan 2023 20:44:35 +0100 Subject: [PATCH 07/13] Undo constant name to all CAPS --- test/tiled_image.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tiled_image.cxx b/test/tiled_image.cxx index e5c71844e5..79c632e4b4 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -80,7 +80,7 @@ int main(int argc, char **argv) { Fl_Double_Window window(400,400); ::w = &window; Fl_Group group(0,0,400,400); if (anim) - group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DontResizeCanvas))); + group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS))); else group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); group.align(FL_ALIGN_INSIDE); From 617678d83e83e658940e34a7c264a7723b0b6752 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 6 Jan 2023 22:31:27 +0100 Subject: [PATCH 08/13] Moved Doxygen comments to the source --- FL/Fl_Anim_GIF_Image.H | 213 +++++++---------------------- src/Fl_Anim_GIF_Image.cxx | 280 +++++++++++++++++++++++++++++++++++--- src/Fl_GIF_Image.cxx | 4 +- test/tiled_image.cxx | 5 +- 4 files changed, 317 insertions(+), 185 deletions(-) diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H index 60a0f70a9e..f6f49539ce 100644 --- a/FL/Fl_Anim_GIF_Image.H +++ b/FL/Fl_Anim_GIF_Image.H @@ -23,17 +23,7 @@ class Fl_Widget; #include -/** - 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. - - You 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. - */ +// Load and display animater GIF images class FL_EXPORT Fl_Anim_GIF_Image : public Fl_GIF_Image { class FrameInfo; // internal helper class @@ -42,14 +32,14 @@ public: /** When opening an Fl_Anim_GIF_Image there are some options - that can be passed in a 'flags' value. + 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 start() method. + It can be started later using the \ref start() method. */ DONT_START = 1, /** @@ -61,7 +51,7 @@ public: DONT_RESIZE_CANVAS = 2, /** This flag indicates to the loader that it should not - set the animation as image() member of the canvas widget, + set the animation as \ref image() member of the canvas widget, which is the default. Needed for special use cases. */ @@ -86,121 +76,68 @@ public: */ DEBUG_FLAG = 128 }; - /** - The constructor creates an new animated gif object from - the given file. - Optionally it applies the canvas() method after successful load. - If 'DONT_START' is not specified in the 'flags' parameter it calls start() - after successful load. - */ + + // -- constructors and destructor Fl_Anim_GIF_Image(const char *filename, Fl_Widget *canvas = 0, unsigned short flags = 0); - // constructor with length 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; - /** - The canvas() method sets or gets the current widget, that - is used to display the frame images. - The _flags_ parameter specifies whether the canvas widget - is resized to the animation dimensions and/or its image() - method will be used to set the current frame image - during animation. - */ + + // -- file handling + bool load(const char *name, const unsigned char *imgdata, size_t imglength); + 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; - /** - Return the width and height of the animation canvas as - specified in the GIF file header - */ int canvas_w() const; - /** - Return the width and height of the animation canvas as - specified in the GIF file header - */ int canvas_h() const; - /** - The color_average() method applies the specified color_average - to all frames of the animation. - */ - void color_average(Fl_Color c, float i) FL_OVERRIDE; - /** - 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. - */ - Fl_Image *copy(int W, int H) const FL_OVERRIDE; - int debug() const; - /** - The desaturate() method applies desaturate() to all frames - of the animation. - */ - void desaturate() FL_OVERRIDE; - void draw(int x, int y, int w, int h, int cx = 0, int cy = 0) FL_OVERRIDE; - /** - Return the delay of frame 'frame_' `[0-frames() -1]` in seconds - */ - double delay(int frame_) const; - /** - Set the delay of frame 'frame_' `[0-frames() -1]` in seconds - */ - void delay(int frame, double delay); - /** - Return the number of frames. - */ + bool is_animated() const; + const char *name() const; + void speed(double speed); + double speed() const; + + // -- animation int frames() const; - /** - Set the current frame in the range index `[0-frames() -1]` - */ void frame(int frame); - /** - Return the current frame in the range index `[0-frames() -1]` - or -1 if the image has no frames. - */ int frame() const; - /** - Return the current frame image. - */ Fl_Image *image() const; - /** - Return the frame image of frame 'frame_' - */ Fl_Image *image(int frame) const; - /** - The is_animated() method is just a convenience method for - testing the valid flag and the frame count beeing greater 1. - */ - bool is_animated() const; - /** - The static frame_count() method is just a convenience method for - getting the number of images (frames) stored in a GIF file. + bool start(); + bool stop(); - 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. + /** 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; + 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); - /** - 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. - */ - void frame_uncache(bool uncache); - /** - Return the active frame_uncache() setting. - */ - bool frame_uncache() const; - /** - 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. - */ - bool load(const char *name, const unsigned char *imgdata, size_t imglength); + /** The loop flag can be used to (dis-)allow loop count. If set (which is the default), the animation will be @@ -210,6 +147,7 @@ public: 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 @@ -217,59 +155,6 @@ public: This is a global value for all Fl_Anim_GIF_Image objects. */ static double min_delay; - /** - Return the name of the played file as specified in the constructor. - */ - const char *name() const; - /** - Return if the animation is currently running or stopped. - */ - bool playing() const { return valid() && Fl::has_timeout(cb_animate, (void *)this); } - /** - The start() method (re-)starts the playing of the frames. - */ - bool start(); - /** - The stop() method stops the playing of the frames. - */ - bool stop(); - /** - The resize() method resizes the image to the - specified size, replacing the current image. - */ - Fl_Anim_GIF_Image& resize(int w, int h); - Fl_Anim_GIF_Image& resize(double scale); - /** - The speed() method changes the playing speed - to 'speed' x original speed. E.g. to play at half - speed call it with 0.5, for double speed with 2. - */ - void speed(double speed); - double speed() const; - /** - Uncache all cached image data now. Re-implemented from Fl_Pixmap. - */ - void uncache() FL_OVERRIDE; - /** - The valid() method returns if the class has - successfully loaded and the image has at least - one frame. - */ - bool valid() const; - /** - Return the frame position of frame 'frame' - Usefull only if loaded with 'optimize_mem' and - the animation also has size optimized frames. - */ - int frame_x(int frame) const; - int frame_y(int frame) const; - /** - Return the frame dimensions of frame 'frame'. - Usefull only if loaded with 'optimize_mem' and - the animation also has size optimized frames. - */ - int frame_w(int frame) const; - int frame_h(int frame) const; protected: diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx index 65dfdb8f9a..cf412f1a42 100644 --- a/src/Fl_Anim_GIF_Image.cxx +++ b/src/Fl_Anim_GIF_Image.cxx @@ -25,6 +25,19 @@ #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; @@ -490,9 +503,23 @@ bool Fl_Anim_GIF_Image::loop = true; // 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_Widget *canvas /* = 0*/, + unsigned short flags /* = 0 */) : Fl_GIF_Image(), name_(0), flags_(flags), @@ -520,6 +547,25 @@ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *filename, } +/** 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 */) : @@ -550,6 +596,7 @@ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char* imagename, const unsigned char } +/** Create an empty animated GIF image shell. */ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image() : Fl_GIF_Image(), name_(0), @@ -563,14 +610,30 @@ Fl_Anim_GIF_Image::Fl_Anim_GIF_Image() : } -/*virtual*/ -Fl_Anim_GIF_Image::~Fl_Anim_GIF_Image() { +/** 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); @@ -596,16 +659,25 @@ void Fl_Anim_GIF_Image::canvas(Fl_Widget *canvas, unsigned short flags/* = 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; } @@ -624,8 +696,16 @@ void Fl_Anim_GIF_Image::clear_frames() { } -/*virtual*/ -void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) { +/** 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; @@ -640,8 +720,15 @@ void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) { } -/*virtual*/ -Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) const { +/** 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() @@ -674,6 +761,11 @@ int Fl_Anim_GIF_Image::debug() const { } +/** 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; @@ -681,21 +773,32 @@ double Fl_Anim_GIF_Image::delay(int frame) const { } +/** 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; } -/*virtual*/ -void Fl_Anim_GIF_Image::desaturate() { +/** Desaturate to all frames of the animation. + */ +void Fl_Anim_GIF_Image::desaturate() /* override */ { fi_->desaturate = true; set_frame(); } -/*virtual*/ -void Fl_Anim_GIF_Image::draw(int x, int y, int w, int h, int cx/* = 0*/, int cy/* = 0*/) { +/** 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_; @@ -708,7 +811,7 @@ void Fl_Anim_GIF_Image::draw(int x, int y, int w, int h, int cx/* = 0*/, int cy/ 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->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); } } @@ -726,11 +829,20 @@ void Fl_Anim_GIF_Image::draw(int x, int y, int w, int h, int cx/* = 0*/, int 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); @@ -745,7 +857,27 @@ void Fl_Anim_GIF_Image::frame(int frame) { } -/*static*/ +/** 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); @@ -754,6 +886,14 @@ int Fl_Anim_GIF_Image::frame_count(const char *name, const unsigned char *imgdat } +/** 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; @@ -761,6 +901,14 @@ int Fl_Anim_GIF_Image::frame_x(int frame) const { } +/** 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; @@ -768,12 +916,29 @@ int Fl_Anim_GIF_Image::frame_y(int frame) const { } +/** 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; @@ -781,26 +946,50 @@ int Fl_Anim_GIF_Image::frame_h(int frame) const { } +/** 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; @@ -808,6 +997,13 @@ Fl_Image *Fl_Anim_GIF_Image::image(int frame_) const { } +/** 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; } @@ -819,6 +1015,18 @@ bool Fl_GIF_Image::is_animated(const char *name) { } +/** 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, size_t imglength) { DEBUG(("\nFl_Anim_GIF_Image::load '%s'\n", name)); clear_frames(); @@ -851,6 +1059,12 @@ bool Fl_Anim_GIF_Image::load(const char *name, const unsigned char *imgdata, siz } // 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_; } @@ -896,6 +1110,12 @@ void Fl_Anim_GIF_Image::on_extension_data(Fl_GIF_Image::GIF_FRAME &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); @@ -917,6 +1137,12 @@ Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(int w, int h) { } +/** 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)); } @@ -957,16 +1183,29 @@ void Fl_Anim_GIF_Image::set_frame(int frame) { } +/** 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) { @@ -976,14 +1215,19 @@ bool Fl_Anim_GIF_Image::start() { } +/** 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; } -/*virtual*/ -void Fl_Anim_GIF_Image::uncache() { +/** 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(); @@ -991,6 +1235,10 @@ void Fl_Anim_GIF_Image::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 7b9ee05a65..87a1b70870 100644 --- a/src/Fl_GIF_Image.cxx +++ b/src/Fl_GIF_Image.cxx @@ -147,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. diff --git a/test/tiled_image.cxx b/test/tiled_image.cxx index 79c632e4b4..6e981caaad 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -54,11 +54,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; From 9140f393462177237add59818b22665a5a41fb1a Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Sat, 7 Jan 2023 02:58:29 +0100 Subject: [PATCH 09/13] Animated FLUID logo in About... dialog FLUID recognises animated GIFs in widgets and always inlines and animates them --- fluid/Fluid_Image.cxx | 46 ++++- fluid/Fluid_Image.h | 1 + fluid/about_panel.cxx | 350 ++++++++++++--------------------- fluid/about_panel.fl | 10 +- fluid/icons/fluid.animated.gif | Bin 0 -> 2545 bytes test/pixmap_browser.cxx | 2 +- 6 files changed, 165 insertions(+), 244 deletions(-) create mode 100644 fluid/icons/fluid.animated.gif 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..f60d14bc6c 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,7 @@ 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_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..0dc875bdb8 100644 --- a/fluid/about_panel.fl +++ b/fluid/about_panel.fl @@ -36,11 +36,11 @@ 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} } Fl_Box {} { label {FLTK User @@ -59,7 +59,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 0000000000000000000000000000000000000000..c213991608a09f61acf8eaef45cd4dca70c64a50 GIT binary patch literal 2545 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 H69ND`27HW} literal 0 HcmV?d00001 diff --git a/test/pixmap_browser.cxx b/test/pixmap_browser.cxx index 7ae23b9791..dca13316e9 100644 --- a/test/pixmap_browser.cxx +++ b/test/pixmap_browser.cxx @@ -130,7 +130,7 @@ void svg_cb(Fl_Widget *widget, void *) { } int dvisual = 0; -int animate = 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;} From 00a27d08b9ab897fb42069f4cd80784cd30a83fa Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Sat, 7 Jan 2023 16:57:29 +0100 Subject: [PATCH 10/13] Fixing tests and examples Moved animgifimage from test to examples Changed test/pixmaps to show an animated gif Multiple small fixes --- FL/Fl_Anim_GIF_Image.H | 4 +- FL/Fl_Image.H | 3 + examples/CMakeLists.txt | 1 + examples/Makefile | 3 +- examples/animgifimage-resize.cxx | 4 +- {test => examples}/animgifimage.cxx | 0 fluid/about_panel.cxx | 1 + fluid/about_panel.fl | 1 + src/Fl_Anim_GIF_Image.cxx | 53 ++++++++++--- test/CMakeLists.txt | 3 +- test/Makefile | 12 +-- test/demo.menu | 3 - test/pixmap.cxx | 65 +++++++++------ test/pixmaps/animated_fluid_gif.h | 118 ++++++++++++++++++++++++++++ test/tiled_image.cxx | 12 +-- test/utf8.cxx | 4 +- 16 files changed, 223 insertions(+), 64 deletions(-) rename {test => examples}/animgifimage.cxx (100%) create mode 100644 test/pixmaps/animated_fluid_gif.h diff --git a/FL/Fl_Anim_GIF_Image.H b/FL/Fl_Anim_GIF_Image.H index f6f49539ce..bb33f3f189 100644 --- a/FL/Fl_Anim_GIF_Image.H +++ b/FL/Fl_Anim_GIF_Image.H @@ -86,7 +86,7 @@ public: ~Fl_Anim_GIF_Image() FL_OVERRIDE; // -- file handling - bool load(const char *name, const unsigned char *imgdata, size_t imglength); + bool load(const char *name, const unsigned char *imgdata=NULL, size_t imglength=0); bool valid() const; // -- getters and setters @@ -111,6 +111,7 @@ public: 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 @@ -128,6 +129,7 @@ public: // -- 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; 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 2ecb09a7a4..faa6d7daa8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -81,6 +81,7 @@ set (FLUID_SOURCES ############################################################ set (IMAGE_SOURCES + animgifimage animgifimage-play animgifimage-resize animgifimage-simple diff --git a/examples/Makefile b/examples/Makefile index 2534d6ed0e..946eda8081 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -22,7 +22,8 @@ SHELL = /bin/sh .SILENT: # Executables -ALL = animgifimage-play$(EXEEXT) \ +ALL = animgifimage$(EXEEXT) \ + animgifimage-play$(EXEEXT) \ animgifimage-simple$(EXEEXT) \ animgifimage-resize$(EXEEXT) \ browser-simple$(EXEEXT) \ diff --git a/examples/animgifimage-resize.cxx b/examples/animgifimage-resize.cxx index 700454c0a4..1417255b36 100644 --- a/examples/animgifimage-resize.cxx +++ b/examples/animgifimage-resize.cxx @@ -38,7 +38,7 @@ class Canvas : public Fl_Box { public: Canvas(int x, int y, int w, int h) : Inherited(x, y, w, h) {} - virtual void draw() { + void draw() FL_OVERRIDE { if (draw_grid) { // draw a transparency grid as background static const Fl_Color C1 = fl_rgb_color(0xcc, 0xcc, 0xcc); @@ -78,7 +78,7 @@ class Canvas : public Fl_Box { Canvas *c = (Canvas *)d; c->do_resize(c->w(), c->h()); } - virtual void resize(int x, int y, int w, int 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.. diff --git a/test/animgifimage.cxx b/examples/animgifimage.cxx similarity index 100% rename from test/animgifimage.cxx rename to examples/animgifimage.cxx diff --git a/fluid/about_panel.cxx b/fluid/about_panel.cxx index f60d14bc6c..b01616edad 100644 --- a/fluid/about_panel.cxx +++ b/fluid/about_panel.cxx @@ -167,6 +167,7 @@ Fl_Double_Window* make_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 0dc875bdb8..82c8ce627e 100644 --- a/fluid/about_panel.fl +++ b/fluid/about_panel.fl @@ -41,6 +41,7 @@ if (!cbuf[0]) { } { 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 diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx index cf412f1a42..e0196cfc21 100644 --- a/src/Fl_Anim_GIF_Image.cxx +++ b/src/Fl_Anim_GIF_Image.cxx @@ -716,7 +716,11 @@ void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) /* override */ { } fi_->average_color = c; fi_->average_weight = i; - set_frame(); + // 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(); } @@ -741,6 +745,11 @@ Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) const /* override */ { 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; @@ -1027,7 +1036,7 @@ bool Fl_GIF_Image::is_animated(const char *name) { \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, size_t imglength) { +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_); @@ -1165,7 +1174,7 @@ void Fl_Anim_GIF_Image::set_frame() { void Fl_Anim_GIF_Image::set_frame(int frame) { - int last_frame = frame_; +// int last_frame = frame_; frame_ = frame; // NOTE: uncaching decreases performance, but saves a lot of memory if (uncache_ && this->image()) @@ -1173,12 +1182,25 @@ void Fl_Anim_GIF_Image::set_frame(int frame) { fi_->set_frame(frame_); - if (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(); + 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(); + } } @@ -1224,6 +1246,19 @@ bool Fl_Anim_GIF_Image::stop() { } +/** 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. */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7ce12ac085..67c36fb00c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,7 +58,6 @@ set (extra_tests) ####################################################################### CREATE_EXAMPLE (adjuster adjuster.cxx fltk) -CREATE_EXAMPLE (animgifimage animgifimage.cxx "fltk_images;fltk") CREATE_EXAMPLE (arc arc.cxx fltk) CREATE_EXAMPLE (animated animated.cxx fltk) CREATE_EXAMPLE (ask ask.cxx fltk) @@ -140,7 +139,7 @@ CREATE_EXAMPLE (tabs tabs.fl fltk) CREATE_EXAMPLE (table table.cxx fltk) CREATE_EXAMPLE (threads threads.cxx fltk) CREATE_EXAMPLE (tile tile.cxx fltk) -CREATE_EXAMPLE (tiled_image tiled_image.cxx "fltk_images;fltk") +CREATE_EXAMPLE (tiled_image tiled_image.cxx fltk) CREATE_EXAMPLE (tree tree.fl fltk) CREATE_EXAMPLE (twowin twowin.cxx fltk) CREATE_EXAMPLE (utf8 utf8.cxx fltk) diff --git a/test/Makefile b/test/Makefile index 9455f8ed32..193cd9d17c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -51,7 +51,6 @@ OBJUNITTEST = \ CPPFILES =\ adjuster.cxx \ animated.cxx \ - animgifimage.cxx \ arc.cxx \ ask.cxx \ bitmap.cxx \ @@ -146,7 +145,6 @@ CPPFILES =\ ALL = \ animated$(EXEEXT) \ - animgifimage$(EXEEXT) \ adjuster$(EXEEXT) \ arc$(EXEEXT) \ ask$(EXEEXT) \ @@ -345,10 +343,6 @@ adjuster$(EXEEXT): adjuster.o animated$(EXEEXT): animated.o -animgifimage$(EXEEXT): animgifimage.o $(IMGLIBNAME) - echo Linking $@... - $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) animgifimage.o -o $@ $(LINKFLTKIMG) $(LDLIBS) - arc$(EXEEXT): arc.o ask$(EXEEXT): ask.o @@ -521,6 +515,7 @@ pack$(EXEEXT): pack.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 $@... @@ -624,10 +619,7 @@ threads.o: threads.h tile$(EXEEXT): tile.o -tiled_image$(EXEEXT): tiled_image.o $(IMGLIBNAME) - echo Linking $@... - $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) tiled_image.o -o $@ $(LINKFLTKIMG) $(LDLIBS) - $(OSX_ONLY) ../fltk-config --post $@ +tiled_image$(EXEEXT): tiled_image.o tree$(EXEEXT): tree.o tree.cxx: tree.fl ../fluid/fluid$(EXEEXT) diff --git a/test/demo.menu b/test/demo.menu index 02666c7acb..16cce02da3 100644 --- a/test/demo.menu +++ b/test/demo.menu @@ -39,13 +39,10 @@ @d:Images...:@di @di:Fl_Bitmap:bitmap @di:Fl_Pixmap:pixmap - @di:Fl_Pixmap\nanimated:pixmap -a @di:Fl_RGB\n_Image:image @di:Fl_Shared\n_Image:pixmap_browser @di:Fl_Tiled\n_Image:tiled_image - @di:Fl_Tiled\n_Image\nanimated:tiled_image -a @di:transparency:animated - @di:Fl_Anim\n_GIF_Image:animgifimage -t pixmaps @d:cursor:cursor @d:labels:label @d:offscreen:offscreen diff --git a/test/pixmap.cxx b/test/pixmap.cxx index e045c54a7a..c4bb096c23 100644 --- a/test/pixmap.cxx +++ b/test/pixmap.cxx @@ -1,7 +1,7 @@ // // Pixmap label test program for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2010 by Bill Spitzak and others. +// Copyright 1998-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 @@ -21,13 +21,15 @@ #include #include -#include "pixmaps/porsche.xpm" +#include "test/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 *wgt,void *) { int i = 0; @@ -43,11 +45,24 @@ void button_cb(Fl_Widget *wgt,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 animate = 0; 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; } @@ -56,26 +71,20 @@ 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" - " -a : use animated GIF as pixmap\n%s\n",Fl::help); - - Fl_Double_Window window(400,400); ::w = &window; - Fl_Button b(140,160,120,120,"Pixmap"); ::b = &b; - Fl_Pixmap *pixmap = 0; - if (!animate) - pixmap = new Fl_Pixmap(porsche_xpm); - else { - Fl_Anim_GIF_Image *anim = new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif"); - anim->canvas(&b, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS); - anim->scale(96,96,1,1); - pixmap = anim; - anim->start(); - } - Fl_Pixmap *depixmap; - depixmap = (Fl_Pixmap *)pixmap->copy(); - depixmap->inactive(); + Fl::fatal(" -8 # : use default visual\n", Fl::help); + if (!dvisual) Fl::visual(FL_RGB); + + 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); leftb = new Fl_Toggle_Button(25,50,50,25,"left"); @@ -92,7 +101,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/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/tiled_image.cxx b/test/tiled_image.cxx index 6e981caaad..4b313827d0 100644 --- a/test/tiled_image.cxx +++ b/test/tiled_image.cxx @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -37,7 +36,6 @@ void button_cb(Fl_Widget *,void *) { #include "list_visuals.cxx" int visid = -1; -int anim = 0; int arg(int argc, char **argv, int &i) { if (argv[i][1] == 'v') { if (i+1 >= argc) return 0; @@ -45,11 +43,6 @@ int arg(int argc, char **argv, int &i) { i += 2; return 2; } - else if (argv[i][1] == 'a') { - anim = 1; - i ++; - return 1; - } return 0; } @@ -78,10 +71,7 @@ int main(int argc, char **argv) { Fl_Double_Window window(400,400); ::w = &window; Fl_Group group(0,0,400,400); - if (anim) - group.image(new Fl_Tiled_Image(new Fl_Anim_GIF_Image("pixmaps/fltk_animated2.gif", &group, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS))); - else - group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); + group.image(new Fl_Tiled_Image(new Fl_Pixmap((const char * const *)tile_xpm))); group.align(FL_ALIGN_INSIDE); Fl_Button b(340,365,50,25,"Close"); ::b = &b; 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)); From b46b3fe51ae47851996302f71a296d91e0324157 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Mon, 9 Jan 2023 14:39:27 +0100 Subject: [PATCH 11/13] Fixes uninitialised variables --- src/Fl_GIF_Image.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Fl_GIF_Image.cxx b/src/Fl_GIF_Image.cxx index 87a1b70870..d62bd3f5c0 100644 --- a/src/Fl_GIF_Image.cxx +++ b/src/Fl_GIF_Image.cxx @@ -560,7 +560,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr, bool anim/*=false*/) int i = rdr.read_byte(); CHECK_ERROR - int blocklen; + int blocklen = 0; if (i == 0x21) { // a "gif extension" ch = rdr.read_byte(); // extension type @@ -726,6 +726,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr, bool anim/*=false*/) } delete[] Image; + Image = NULL; if (!anim) break; // okay, it is only the first image we want From 5d9a70707256dbc8e66151cb1190a09e1e0d4652 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Mon, 9 Jan 2023 16:00:48 +0100 Subject: [PATCH 12/13] Attempt to fix offset first image --- src/Fl_GIF_Image.cxx | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Fl_GIF_Image.cxx b/src/Fl_GIF_Image.cxx index d62bd3f5c0..93fe22f36d 100644 --- a/src/Fl_GIF_Image.cxx +++ b/src/Fl_GIF_Image.cxx @@ -715,14 +715,35 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr, bool anim/*=false*/) // We are done reading the image, now convert to xpm (first image only) if (!frame) { - w(anim ? ScreenWidth : Width); - h(anim ? ScreenHeight : Height); - d(1); - - char **new_data = convert_to_xpm(Image, Width, Height, CMap, ColorMapSize, has_transparent ? transparent_pixel : -1); - - data((const char **)new_data, Height + 2); - alloc_data = 1; + if (anim && ( (Width != ScreenWidth) || (Height != ScreenHeight) )) { + // if we are reading this for Fl_Anim_GIF_Image, we must apply offsets + w(ScreenWidth); + h(ScreenHeight); + d(1); + uchar *moved_image = new uchar[ScreenWidth*ScreenHeight]; + memset(moved_image, has_transparent ? transparent_pixel : 0, ScreenWidth*ScreenHeight); + int xstart = XPos; if (xstart < 0) xstart = 0; + int ystart = YPos; if (ystart < 0) ystart = 0; + int xmax = XPos + Width; if (xmax > ScreenWidth) xmax = ScreenWidth; + int ymax = YPos + Height; if (ymax > ScreenHeight) ymax = ScreenHeight; + for (int y = ystart; y Date: Sat, 21 Jan 2023 17:24:36 +0100 Subject: [PATCH 13/13] Complete help message --- test/pixmap.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pixmap.cxx b/test/pixmap.cxx index 69ab791941..bc08164ea3 100644 --- a/test/pixmap.cxx +++ b/test/pixmap.cxx @@ -21,7 +21,7 @@ #include #include -#include "test/pixmaps/animated_fluid_gif.h" +#include "pixmaps/animated_fluid_gif.h" #include @@ -71,7 +71,7 @@ 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", Fl::help); + Fl::fatal(" -8 # : use default visual\n%s\n", Fl::help); if (!dvisual) Fl::visual(FL_RGB); Fl_Double_Window window(400,440); ::w = &window;