summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorwcout <wcout@users.noreply.github.com>2023-01-21 17:27:58 +0100
committerGitHub <noreply@github.com>2023-01-21 17:27:58 +0100
commit2ddfd9d9492d9fc1df111ec9211dd1be4d424c35 (patch)
treec766d0dfb3a2d7a75c275db2821d5bcf0e935a15 /src
parent1fc269b0d4c79b256cc57740d318f95dded8c340 (diff)
Animated GIF support (Fl_Anim_GIF_Image class) (#375)
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/Fl_Anim_GIF_Image.cxx1279
-rw-r--r--src/Fl_GIF_Image.cxx626
-rw-r--r--src/Makefile1
-rw-r--r--src/fl_images_core.cxx4
-rw-r--r--src/makedepend5
6 files changed, 1707 insertions, 209 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6567d2114..daa66c23d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -471,6 +471,7 @@ set (IMGCPPFILES
Fl_BMP_Image.cxx
Fl_File_Icon2.cxx
Fl_GIF_Image.cxx
+ Fl_Anim_GIF_Image.cxx
Fl_Help_Dialog.cxx
Fl_ICO_Image.cxx
Fl_JPEG_Image.cxx
diff --git a/src/Fl_Anim_GIF_Image.cxx b/src/Fl_Anim_GIF_Image.cxx
new file mode 100644
index 000000000..e0196cfc2
--- /dev/null
+++ b/src/Fl_Anim_GIF_Image.cxx
@@ -0,0 +1,1279 @@
+//
+// Fl_Anim_GIF_Image class for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2016-2023 by Christian Grabner <wcout@gmx.net>.
+//
+// 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 <FL/Fl.H>
+#include <FL/Fl_GIF_Image.H>
+#include <FL/Fl_Shared_Image.H>
+#include <FL/Fl_Graphics_Driver.H>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h> // lround()
+
+#include <FL/Fl_Anim_GIF_Image.H>
+
+/** \class Fl_Anim_GIF_Image
+
+ The Fl_Anim_GIF_Image class supports loading, caching, and drawing of animated
+ Compuserve GIF<SUP>SM</SUP> images.
+
+ The class loads all images contained in the file and animates them by cycling
+ through them as defined by the delay times in the image file.
+
+ The user must supply an FLTK widget as "container" in order to see the
+ animation by specifying it in the constructor or later using the
+ canvas() method.
+*/
+
+/*static*/
+bool Fl_GIF_Image::animate = false;
+
+
+///////////////////////////////////////////////////////////////////////
+// Internal helper classes/structs
+///////////////////////////////////////////////////////////////////////
+
+class Fl_Anim_GIF_Image::FrameInfo {
+ friend class Fl_Anim_GIF_Image;
+
+ enum Transparency {
+ T_NONE = 0xff,
+ T_FULL = 0
+ };
+
+ struct RGBA_Color {
+ uchar r, g, b, alpha;
+ RGBA_Color(uchar r = 0, uchar g = 0, uchar b = 0, uchar a = T_NONE) :
+ r(r), g(g), b(b), alpha(a) {}
+ };
+
+ enum Dispose {
+ DISPOSE_UNDEF = 0,
+ DISPOSE_NOT = 1,
+ DISPOSE_BACKGROUND = 2,
+ DISPOSE_PREVIOUS = 3
+ };
+
+ struct GifFrame {
+ GifFrame() :
+ rgb(0),
+ scalable(0),
+ average_color(FL_BLACK),
+ average_weight(-1),
+ desaturated(false),
+ x(0),
+ y(0),
+ w(0),
+ h(0),
+ delay(0),
+ dispose(DISPOSE_UNDEF),
+ transparent_color_index(-1) {}
+ Fl_RGB_Image *rgb; // full frame image
+ Fl_Shared_Image *scalable; // used for hardware-accelerated scaling
+ Fl_Color average_color; // last average color
+ float average_weight; // last average weight
+ bool desaturated; // flag if frame is desaturated
+ unsigned short x, y, w, h; // frame original dimensions
+ double delay; // delay (already converted to ms)
+ Dispose dispose; // disposal method
+ int transparent_color_index; // needed for dispose()
+ RGBA_Color transparent_color; // needed for dispose()
+ };
+
+ FrameInfo(Fl_Anim_GIF_Image *anim) :
+ anim(anim),
+ valid(false),
+ frames_size(0),
+ frames(0),
+ loop_count(1),
+ loop(0),
+ background_color_index(-1),
+ canvas_w(0),
+ canvas_h(0),
+ desaturate(false),
+ average_color(FL_BLACK),
+ average_weight(-1),
+ scaling((Fl_RGB_Scaling)0),
+ debug_(0),
+ optimize_mem(false),
+ offscreen(0) {}
+ ~FrameInfo();
+ void clear();
+ void copy(const FrameInfo& fi);
+ double convert_delay(int d) const;
+ int debug() const { return debug_; }
+ bool load(const char *name, const unsigned char *data, size_t length);
+ bool push_back_frame(const GifFrame &frame);
+ void resize(int W, int H);
+ void scale_frame(int frame);
+ void set_frame(int frame);
+private:
+ Fl_Anim_GIF_Image *anim; // a pointer to the Image (only needed for name())
+ bool valid; // flag if valid data
+ int frames_size; // number of frames stored in 'frames'
+ GifFrame *frames; // "vector" for frames
+ int loop_count; // loop count from file
+ int loop; // current loop count
+ int background_color_index; // needed for dispose()
+ RGBA_Color background_color; // needed for dispose()
+ GifFrame frame; // current processed frame
+ int canvas_w; // width of GIF from header
+ int canvas_h; // height of GIF from header
+ bool desaturate; // flag if frames should be desaturated
+ Fl_Color average_color; // color for color_average()
+ float average_weight; // weight for color_average (negative: none)
+ Fl_RGB_Scaling scaling; // saved scaling method for scale_frame()
+ int debug_; // Flag for debug outputs
+ bool optimize_mem; // Flag to store frames in original dimensions
+ uchar *offscreen; // internal "offscreen" buffer
+private:
+private:
+ void dispose(int frame_);
+ void on_frame_data(Fl_GIF_Image::GIF_FRAME &gf);
+ void on_extension_data(Fl_GIF_Image::GIF_FRAME &gf);
+ void set_to_background(int frame_);
+};
+
+
+#define LOG(x) if (debug()) printf x
+#define DEBUG(x) if (debug() >= 2) printf x
+#ifndef LOG
+ #define LOG(x)
+#endif
+#ifndef DEBUG
+ #define DEBUG(x)
+#endif
+
+
+//
+// helper class FrameInfo implementation
+//
+
+Fl_Anim_GIF_Image::FrameInfo::~FrameInfo() {
+ clear();
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::clear() {
+ // release all allocated memory
+ while (frames_size-- > 0) {
+ if (frames[frames_size].scalable)
+ frames[frames_size].scalable->release();
+ delete frames[frames_size].rgb;
+ }
+ delete[] offscreen;
+ offscreen = 0;
+ free(frames);
+ frames = 0;
+ frames_size = 0;
+}
+
+
+double Fl_Anim_GIF_Image::FrameInfo::convert_delay(int d) const {
+ if (d <= 0)
+ d = loop_count != 1 ? 10 : 0;
+ return (double)d / 100;
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::copy(const FrameInfo& fi) {
+ // copy from source
+ for (int i = 0; i < fi.frames_size; i++) {
+ if (!push_back_frame(fi.frames[i])) {
+ break;
+ }
+ double scale_factor_x = (double)canvas_w / (double)fi.canvas_w;
+ double scale_factor_y = (double)canvas_h / (double)fi.canvas_h;
+ if (fi.optimize_mem) {
+ frames[i].x = lround(fi.frames[i].x * scale_factor_x);
+ frames[i].y = lround(fi.frames[i].y * scale_factor_y);
+ int new_w = (int)lround(fi.frames[i].w * scale_factor_x);
+ int new_h = (int)lround(fi.frames[i].h * scale_factor_y);
+ frames[i].w = new_w;
+ frames[i].h = new_h;
+ }
+ // just copy data 1:1 now - scaling will be done adhoc when frame is displayed
+ frames[i].rgb = (Fl_RGB_Image *)fi.frames[i].rgb->copy();
+ frames[i].scalable = 0;
+ }
+ optimize_mem = fi.optimize_mem;
+ scaling = Fl_Image::RGB_scaling(); // save current scaling mode
+ loop_count = fi.loop_count; // .. and the loop_count!
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::dispose(int frame) {
+ if (frame < 0) {
+ return;
+ }
+ // dispose frame with index 'frame_' to offscreen buffer
+ switch (frames[frame].dispose) {
+ case DISPOSE_PREVIOUS: {
+ // dispose to previous restores to first not DISPOSE_TO_PREVIOUS frame
+ int prev(frame);
+ while (prev > 0 && frames[prev].dispose == DISPOSE_PREVIOUS)
+ prev--;
+ if (prev == 0 && frames[prev].dispose == DISPOSE_PREVIOUS) {
+ set_to_background(frame);
+ return;
+ }
+ DEBUG((" dispose frame %d to previous frame %d\n", frame + 1, prev + 1));
+ // copy the previous image data..
+ uchar *dst = offscreen;
+ int px = frames[prev].x;
+ int py = frames[prev].y;
+ int pw = frames[prev].w;
+ int ph = frames[prev].h;
+ const char *src = frames[prev].rgb->data()[0];
+ if (px == 0 && py == 0 && pw == canvas_w && ph == canvas_h)
+ memcpy((char *)dst, (char *)src, canvas_w * canvas_h * 4);
+ else {
+ if ( px + pw > canvas_w ) pw = canvas_w - px;
+ if ( py + ph > canvas_h ) ph = canvas_h - py;
+ for (int y = 0; y < ph; y++) {
+ memcpy(dst + ( y + py ) * canvas_w * 4 + px, src + y * frames[prev].w * 4, pw * 4);
+ }
+ }
+ break;
+ }
+ case DISPOSE_BACKGROUND:
+ DEBUG((" dispose frame %d to background\n", frame + 1));
+ set_to_background(frame);
+ break;
+
+ default: {
+ // nothing to do (keep everything as is)
+ break;
+ }
+ }
+}
+
+
+bool Fl_Anim_GIF_Image::FrameInfo::load(const char *name, const unsigned char *data, size_t length) {
+ // decode using FLTK
+ valid = false;
+ if (data) {
+ anim->Fl_GIF_Image::load(name, data, length, true); // calls on_frame_data() for each frame
+ } else {
+ anim->Fl_GIF_Image::load(name, true); // calls on_frame_data() for each frame
+ }
+
+ delete[] offscreen;
+ offscreen = 0;
+ return valid;
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::on_extension_data(Fl_GIF_Image::GIF_FRAME &gf) {
+ if (!gf.bptr)
+ return;
+ const uchar *ext = gf.bptr;
+ if (memcmp(ext, "NETSCAPE2.0", 11) == 0) {
+ const uchar *params = &ext[11];
+ loop_count = params[1] | (params[2] << 8);
+ DEBUG(("netscape loop count: %u\n", loop_count));
+ }
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::on_frame_data(Fl_GIF_Image::GIF_FRAME &gf) {
+ if (!gf.bptr)
+ return;
+ int delay = gf.delay;
+ if (delay <= 0)
+ delay = -(delay + 1);
+ LOG(("on_frame_data: frame #%d/%d, %dx%d at %d/%d, delay: %d, bkgd=%d/%d, trans=%d, dispose=%d\n",
+ gf.ifrm + 1, -1, gf.w, gf.h, gf.x, gf.y,
+ gf.delay, gf.bkgd, gf.clrs, gf.trans, gf.dispose));
+
+ if (!gf.ifrm) {
+ // first frame, get width/height
+ valid = true; // may be reset later from loading callback
+ canvas_w = gf.width;
+ canvas_h = gf.height;
+ offscreen = new uchar[canvas_w * canvas_h * 4];
+ memset(offscreen, 0, canvas_w * canvas_h * 4);
+ }
+
+ if (!gf.ifrm) {
+ // store background_color AFTER color table is set
+ background_color_index = gf.clrs && gf.bkgd < gf.clrs ? gf.bkgd : -1;
+
+ if (background_color_index >= 0) {
+ background_color = RGBA_Color(gf.cpal[background_color_index].r,
+ gf.cpal[background_color_index].g,
+ gf.cpal[background_color_index].b);
+ }
+ }
+
+ // process frame
+ frame.x = gf.x;
+ frame.y = gf.y;
+ frame.w = gf.w;
+ frame.h = gf.h;
+ frame.delay = convert_delay(delay);
+ frame.transparent_color_index = gf.trans && gf.trans < gf.clrs ? gf.trans : -1;
+ frame.dispose = (Dispose)gf.dispose;
+ if (frame.transparent_color_index >= 0) {
+ frame.transparent_color = RGBA_Color(gf.cpal[frame.transparent_color_index].r,
+ gf.cpal[frame.transparent_color_index].g,
+ gf.cpal[frame.transparent_color_index].b);
+ }
+ DEBUG(("#%d %d/%d %dx%d delay: %d, dispose: %d transparent_color: %d\n",
+ (int)frames_size + 1,
+ frame.x, frame.y, frame.w, frame.h,
+ gf.delay, gf.dispose, gf.trans));
+
+ // we know now everything we need about the frame..
+ dispose(frames_size - 1);
+
+ // copy image data to offscreen
+ const uchar *bits = gf.bptr;
+ const uchar *endp = offscreen + canvas_w * canvas_h * 4;
+ for (int y = frame.y; y < frame.y + frame.h; y++) {
+ for (int x = frame.x; x < frame.x + frame.w; x++) {
+ uchar c = *bits++;
+ if (c == gf.trans)
+ continue;
+ uchar *buf = offscreen;
+ buf += (y * canvas_w * 4 + (x * 4));
+ if (buf >= endp)
+ continue;
+ *buf++ = gf.cpal[c].r;
+ *buf++ = gf.cpal[c].g;
+ *buf++ = gf.cpal[c].b;
+ *buf = T_NONE;
+ }
+ }
+
+ // create RGB image from offscreen
+ if (optimize_mem) {
+ uchar *buf = new uchar[frame.w * frame.h * 4];
+ uchar *dest = buf;
+ for (int y = frame.y; y < frame.y + frame.h; y++) {
+ for (int x = frame.x; x < frame.x + frame.w; x++) {
+ if (offscreen + y * canvas_w * 4 + x * 4 < endp)
+ memcpy(dest, &offscreen[y * canvas_w * 4 + x * 4], 4);
+ dest += 4;
+ }
+ }
+ frame.rgb = new Fl_RGB_Image(buf, frame.w, frame.h, 4);
+ }
+ else {
+ uchar *buf = new uchar[canvas_w * canvas_h * 4];
+ memcpy(buf, offscreen, canvas_w * canvas_h * 4);
+ frame.rgb = new Fl_RGB_Image(buf, canvas_w, canvas_h, 4);
+ }
+ frame.rgb->alloc_array = 1;
+
+ if (!push_back_frame(frame)) {
+ valid = false;
+ }
+}
+
+
+bool Fl_Anim_GIF_Image::FrameInfo::push_back_frame(const GifFrame &frame) {
+ void *tmp = realloc(frames, sizeof(GifFrame) * (frames_size + 1));
+ if (!tmp) {
+ return false;
+ }
+ frames = (GifFrame *)tmp;
+ memcpy(&frames[frames_size], &frame, sizeof(GifFrame));
+ frames_size++;
+ return true;
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::resize(int W, int H) {
+ double scale_factor_x = (double)W / (double)canvas_w;
+ double scale_factor_y = (double)H / (double)canvas_h;
+ for (int i=0; i < frames_size; i++) {
+ if (optimize_mem) {
+ frames[i].x = lround(frames[i].x * scale_factor_x);
+ frames[i].y = lround(frames[i].y * scale_factor_y);
+ int new_w = (int)lround(frames[i].w * scale_factor_x);
+ int new_h = (int)lround(frames[i].h * scale_factor_y);
+ frames[i].w = new_w;
+ frames[i].h = new_h;
+ }
+ }
+ canvas_w = W;
+ canvas_h = H;
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::scale_frame(int frame) {
+ // Do the actual scaling after a resize if neccessary
+ int new_w = optimize_mem ? frames[frame].w : canvas_w;
+ int new_h = optimize_mem ? frames[frame].h : canvas_h;
+ if (frames[frame].scalable &&
+ frames[frame].scalable->w() == new_w &&
+ frames[frame].scalable->h() == new_h)
+ return;
+
+ Fl_RGB_Scaling old_scaling = Fl_Image::RGB_scaling(); // save current scaling method
+ Fl_Image::RGB_scaling(scaling);
+ if (!frames[frame].scalable) {
+ frames[frame].scalable = Fl_Shared_Image::get(frames[frame].rgb, 0);
+ }
+ frames[frame].scalable->scale(new_w, new_h, 0, 1);
+ Fl_Image::RGB_scaling(old_scaling); // restore scaling method
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::set_to_background(int frame) {
+ // reset offscreen to background color
+ int bg = background_color_index;
+ int tp = frame >= 0 ? frames[frame].transparent_color_index : bg;
+ DEBUG((" set_to_background [%d] tp = %d, bg = %d\n", frame, tp, bg));
+ RGBA_Color color = background_color;
+ if (tp >= 0)
+ color = frames[frame].transparent_color;
+ if (tp >= 0 && bg >= 0)
+ bg = tp;
+ color.alpha = tp == bg ? T_FULL : tp < 0 ? T_FULL : T_NONE;
+ DEBUG((" set to color %d/%d/%d alpha=%d\n", color.r, color.g, color.b, color.alpha));
+ for (uchar *p = offscreen + canvas_w * canvas_h * 4 - 4; p >= offscreen; p -= 4)
+ memcpy(p, &color, 4);
+}
+
+
+void Fl_Anim_GIF_Image::FrameInfo::set_frame(int frame) {
+ // scaling pending?
+ scale_frame(frame);
+
+ // color average pending?
+ if (average_weight >= 0 && average_weight < 1 &&
+ ((average_color != frames[frame].average_color) ||
+ (average_weight != frames[frame].average_weight))) {
+ frames[frame].rgb->color_average(average_color, average_weight);
+ frames[frame].average_color = average_color;
+ frames[frame].average_weight = average_weight;
+ }
+
+ // desaturate pending?
+ if (desaturate && !frames[frame].desaturated) {
+ frames[frame].rgb->desaturate();
+ frames[frame].desaturated = true;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////
+//
+// Fl_Anim_GIF_Image
+//
+// An extension to Fl_GIF_Image.
+//
+///////////////////////////////////////////////////////////////////////
+
+
+//
+// Fl_Anim_GIF_Image global variables
+//
+
+/*static*/
+double Fl_Anim_GIF_Image::min_delay = 0.;
+/*static*/
+bool Fl_Anim_GIF_Image::loop = true;
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <FL/Fl_RGB_Image.H>
+#include <FL/Fl_Group.H>
+#include <FL/Fl.H>
+
+//
+// class Fl_Anim_GIF_Image implementation
+//
+
+/** Load an animated GIF image from a file.
+
+ This constructor creates an animated image form a GIF-formatted file.
+ Optionally it applies the \ref canvas() method after successful load.
+ If \ref DONT_START is not specified in the \p flags parameter it calls
+ \ref start() after successful load.
+
+ \param[in] filename path and name of GIF file in the file system
+ \param[in] canvas a widget that will show and animate the GIF, or \c NULL
+ \param[in] flags see \ref Flags for details, or 0
+
+ \note The GIF image must be decoupled from the canvas by calling
+ `myGif->canvas(NULL);` before deleting the canvas.
+ */
+Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char *filename,
+ Fl_Widget *canvas /* = 0*/,
+ unsigned short flags /* = 0 */) :
+ Fl_GIF_Image(),
+ name_(0),
+ flags_(flags),
+ canvas_(canvas),
+ uncache_(false),
+ valid_(false),
+ frame_(-1),
+ speed_(1.),
+ fi_(new FrameInfo(this))
+{
+ fi_->debug_ = ((flags_ & LOG_FLAG) != 0) + 2 * ((flags_ & DEBUG_FLAG) != 0);
+ fi_->optimize_mem = (flags_ & OPTIMIZE_MEMORY);
+ valid_ = load(filename, NULL, 0);
+ if (canvas_w() && canvas_h()) {
+ if (!w() && !h()) {
+ w(canvas_w());
+ h(canvas_h());
+ }
+ }
+ this->canvas(canvas, flags);
+ if (!(flags & DONT_START))
+ start();
+ else
+ frame_ = 0;
+}
+
+
+/** Load an animated GIF image from memory.
+
+ This constructor creates an animated image form a GIF-formatted block in
+ memory. Optionally it applies the \ref canvas() method after successful load.
+ If \ref DONT_START is not specified in the \p flags parameter it calls
+ \ref start() after successful load.
+
+ \p imagename can be \c NULL. If a name is given, the image is added to the
+ list of shared images and will be available by that name.
+
+ \param[in] imagename a name given to this image or \c NULL
+ \param[in] data pointer to the start of the GIF image in memory
+ \param[in] length length of the GIF image in memory
+ \param[in] canvas a widget that will show and animate the GIF, or \c NULL
+ \param[in] flags see \ref Flags for details, or 0
+
+ \note The GIF image must be decoupled from the canvas by calling
+ `myGif->canvas(NULL);` before deleting the canvas.
+ */
+Fl_Anim_GIF_Image::Fl_Anim_GIF_Image(const char* imagename, const unsigned char *data,
+ const size_t length, Fl_Widget *canvas /* = 0 */,
+ unsigned short flags /* = 0 */) :
+ Fl_GIF_Image(),
+ name_(0),
+ flags_(flags),
+ canvas_(canvas),
+ uncache_(false),
+ valid_(false),
+ frame_(-1),
+ speed_(1.),
+ fi_(new FrameInfo(this))
+{
+ fi_->debug_ = ((flags_ & LOG_FLAG) != 0) + 2 * ((flags_ & DEBUG_FLAG) != 0);
+ fi_->optimize_mem = (flags_ & OPTIMIZE_MEMORY);
+ valid_ = load(imagename, data, length);
+ if (canvas_w() && canvas_h()) {
+ if (!w() && !h()) {
+ w(canvas_w());
+ h(canvas_h());
+ }
+ }
+ this->canvas(canvas, flags);
+ if (!(flags & DONT_START))
+ start();
+ else
+ frame_ = 0;
+}
+
+
+/** Create an empty animated GIF image shell. */
+Fl_Anim_GIF_Image::Fl_Anim_GIF_Image() :
+ Fl_GIF_Image(),
+ name_(0),
+ flags_(0),
+ canvas_(0),
+ uncache_(false),
+ valid_(false),
+ frame_(-1),
+ speed_(1.),
+ fi_(new FrameInfo(this)) {
+}
+
+
+/** Release the image and all cached data.
+
+ Also removes the animation timer.
+ */
+Fl_Anim_GIF_Image::~Fl_Anim_GIF_Image() /* override */ {
+ Fl::remove_timeout(cb_animate, this);
+ delete fi_;
+ free(name_);
+}
+
+
+/** Link the image back to a widget for automated animation.
+
+ This method sets current widget, that is used to display the frame images.
+ The \p flags parameter specifies whether the canvas widget is resized to the
+ animation dimensions and/or its \ref image() method will be used to set the
+ current frame image during animation.
+
+ \param[in] canvas a pointer to the widget that will show the animation
+ \param[in] flags see \ref Flags
+
+ \note The GIF image must be decoupled from the canvas by calling
+ `myGif->canvas(NULL);` before deleting the canvas.
+ */
+void Fl_Anim_GIF_Image::canvas(Fl_Widget *canvas, unsigned short flags/* = 0*/) {
+ if (canvas_)
+ canvas_->image(0);
+ canvas_ = canvas;
+ if (canvas_ && !(flags & DONT_SET_AS_IMAGE))
+ canvas_->image(this); // set animation as image() of canvas
+ if (canvas_ && !(flags & DONT_RESIZE_CANVAS))
+ canvas_->size(w(), h());
+ if (flags_ != flags) {
+ flags_ = flags;
+ fi_->debug_ = ((flags & LOG_FLAG) != 0) + 2 * ((flags & DEBUG_FLAG) != 0);
+ }
+ // Note: 'Start' flag is *NOT* used here,
+ // but an already running animation is restarted.
+ frame_ = -1;
+ if (Fl::has_timeout(cb_animate, this)) {
+ Fl::remove_timeout(cb_animate, this);
+ next_frame();
+ }
+ else if ( fi_->frames_size ) {
+ set_frame(0);
+ }
+}
+
+
+/** Gets the current widget, that is used to display the frame images.
+ \return a pointer to a widget
+ */
+Fl_Widget *Fl_Anim_GIF_Image::canvas() const {
+ return canvas_;
+}
+
+
+/** Return the width of the animation canvas.
+ \return the width in pixel units
+ */
+int Fl_Anim_GIF_Image::canvas_w() const {
+ return fi_->canvas_w;
+}
+
+
+/** Return the height of the animation canvas.
+ \return the width in pixel units
+ */
+int Fl_Anim_GIF_Image::canvas_h() const {
+ return fi_->canvas_h;
+}
+
+
+/*static*/
+void Fl_Anim_GIF_Image::cb_animate(void *d) {
+ Fl_Anim_GIF_Image *b = (Fl_Anim_GIF_Image *)d;
+ b->next_frame();
+}
+
+
+void Fl_Anim_GIF_Image::clear_frames() {
+ fi_->clear();
+ valid_ = false;
+}
+
+
+/** Applies a color average to all frames.
+
+ The color_average() method averages the colors in the image with
+ the provided FLTK color value.
+
+ \param[in] c blend color
+ \param[in] i a value between 0.0 and 1.0 where 0 results in the blend color,
+ and 1 returns the original image
+ */
+void Fl_Anim_GIF_Image::color_average(Fl_Color c, float i) /* override */ {
+ if (i < 0) {
+ // immediate mode
+ i = -i;
+ for (int f=0; f < frames(); f++) {
+ fi_->frames[f].rgb->color_average(c, i);
+ }
+ return;
+ }
+ fi_->average_color = c;
+ fi_->average_weight = i;
+ // Do not call set_frame()! If this is called with an indexed color before
+ // the display connection is open, it will possible average *this* frame
+ // with a different RGB value than the following frames.
+ // See Fl_Image::inactive().
+ // set_frame();
+}
+
+
+/** Copy and resize the animation frames.
+
+ The virtual copy() method makes a copy of the animated image and resizes all
+ of its frame images to W x H using the current resize method.
+
+ \param[in] W, H new size in FLTK pixel units
+ \return the resized copy of the animation
+ */
+Fl_Image *Fl_Anim_GIF_Image::copy(int W, int H) const /* override */ {
+ Fl_Anim_GIF_Image *copied = new Fl_Anim_GIF_Image();
+ // copy/resize the base image (Fl_Pixmap)
+ // Note: this is not really necessary, if the draw()
+ // method never calls the base class.
+ if (fi_->frames_size) {
+ Fl_Pixmap *gif = (Fl_Pixmap *)Fl_GIF_Image::copy(W, H);
+ copied->Fl_GIF_Image::data(gif->data(), gif->count());
+ copied->alloc_data = gif->alloc_data;
+ gif->alloc_data = 0;
+ delete gif;
+ }
+
+ if (name_) copied->name_ = strdup(name_);
+ copied->flags_ = flags_;
+ copied->frame_ = frame_;
+ copied->speed_ = speed_;
+
+ copied->w(W);
+ copied->h(H);
+ copied->fi_->canvas_w = W;
+ copied->fi_->canvas_h = H;
+ copied->fi_->copy(*fi_); // copy the meta data
+
+ copied->uncache_ = uncache_; // copy 'inherits' frame uncache status
+ copied->valid_ = valid_ && copied->fi_->frames_size == fi_->frames_size;
+ copied->scale_frame(); // scale current frame now
+ if (copied->valid_ && frame_ >= 0 && !Fl::has_timeout(cb_animate, copied))
+ copied->start(); // start if original also was started
+ return copied;
+}
+
+
+int Fl_Anim_GIF_Image::debug() const {
+ return fi_->debug();
+}
+
+
+/** Return the delay of frame `[0-frames() -1]` in seconds.
+
+ \param[in] frame index into frame list
+ \return delay to next frame in seconds
+ */
+double Fl_Anim_GIF_Image::delay(int frame) const {
+ if (frame >= 0 && frame < frames())
+ return fi_->frames[frame].delay;
+ return 0.;
+}
+
+
+/** Set the delay of frame `[0-frames() -1]` in seconds.
+
+ \param[in] frame index into frame list
+ \param[in] delay to next frame in seconds
+ */
+void Fl_Anim_GIF_Image::delay(int frame, double delay) {
+ if (frame >= 0 && frame < frames())
+ fi_->frames[frame].delay = delay;
+}
+
+
+/** Desaturate to all frames of the animation.
+ */
+void Fl_Anim_GIF_Image::desaturate() /* override */ {
+ fi_->desaturate = true;
+ set_frame();
+}
+
+
+/** Draw the current frame of the animation.
+
+ \param[in] x, y, w, h target rectangle
+ \param[in] cx, cy source offset
+ */
+void Fl_Anim_GIF_Image::draw(int x, int y, int w, int h,
+ int cx/* = 0*/, int cy/* = 0*/) /* override */ {
+ if (this->image()) {
+ if (fi_->optimize_mem) {
+ int f0 = frame_;
+ while (f0 > 0 && !(fi_->frames[f0].x == 0 && fi_->frames[f0].y == 0 &&
+ fi_->frames[f0].w == this->w() && fi_->frames[f0].h == this->h()))
+ --f0;
+ for (int f = f0; f <= frame_; f++) {
+ if (f < frame_ && fi_->frames[f].dispose == FrameInfo::DISPOSE_PREVIOUS) continue;
+ if (f < frame_ && fi_->frames[f].dispose == FrameInfo::DISPOSE_BACKGROUND) continue;
+ Fl_RGB_Image *rgb = fi_->frames[f].rgb;
+ if (rgb) {
+ float s = Fl_Graphics_Driver::default_driver().scale();
+ rgb->scale(s*fi_->frames[f].w, s*fi_->frames[f].h, 0, 1);
+ rgb->draw(x + s*fi_->frames[f].x, y + s*fi_->frames[f].y, w, h, cx, cy);
+ }
+ }
+ }
+ else {
+ this->image()->scale(Fl_GIF_Image::w(), Fl_GIF_Image::h(), 0, 1);
+ this->image()->draw(x, y, w, h, cx, cy);
+ }
+ } else {
+ // Note: should the base class be called here?
+ // If it is, then the copy() method must also
+ // copy the base image!
+ Fl_GIF_Image::draw(x, y, w, h, cx, cy);
+ }
+}
+
+
+/** Return the current frame.
+
+ \return the current frame index in the range for 0 to `frames()-1`.
+ \return -1 if the image has no frames.
+ */
+int Fl_Anim_GIF_Image::frame() const {
+ return frame_;
+}
+
+
+/** Set the current frame.
+
+ \param[in] frame index into list of frames
+ */
+void Fl_Anim_GIF_Image::frame(int frame) {
+ if (Fl::has_timeout(cb_animate, this)) {
+ Fl::warning("Fl_Anim_GIF_Image::frame(%d): not idle!\n", frame);
+ return;
+ }
+ if (frame >= 0 && frame < frames()) {
+ set_frame(frame);
+ }
+ else {
+ Fl::warning("Fl_Anim_GIF_Image::frame(%d): out of range!\n", frame);
+ }
+}
+
+
+/** Get the number of frames in a GIF file or in a GIF compressed data block.
+
+ The static frame_count() method is just a convenience method for
+ getting the number of images (frames) stored in a GIF file.
+
+ As this count is not readily available in the GIF header, the
+ whole GIF file has be parsed (which is done here by using a
+ temporary Fl_Anim_GIF_Image object for simplicity).
+ So this call may be slow with large files.
+
+ If \p imgdata is \c NULL, the image will be read from the file. Otherwise, it will
+ be read from memory.
+
+ \param[in] name path and name of GIF file in the file system, ignored
+ when reading from memeory
+ \param[in] imgdata pointer to the start of the GIF image in memory, or
+ \c NULL to read from a file
+ \param[in] imglength length of the GIF image in memory, or \c 0
+
+ \return the number of frames in the animation
+ */
+int Fl_Anim_GIF_Image::frame_count(const char *name, const unsigned char *imgdata /* = NULL */, size_t imglength /* = 0 */) {
+ Fl_Anim_GIF_Image temp;
+ temp.load(name, imgdata, imglength);
+ int frames = temp.valid() ? temp.frames() : 0;
+ return frames;
+}
+
+
+/** Return the frame position of a frame.
+
+ Usefull only if loaded with 'optimize_mem' and
+ the animation also has size optimized frames.
+
+ \param[in] frame index into frame list
+ \return x position in FLTK pixle units
+ */
+int Fl_Anim_GIF_Image::frame_x(int frame) const {
+ if (frame >= 0 && frame < frames())
+ return fi_->frames[frame].x;
+ return -1;
+}
+
+
+/** Return the frame position of a frame.
+
+ Usefull only if loaded with 'optimize_mem' and
+ the animation also has size optimized frames.
+
+ \param[in] frame index into frame list
+ \return y position in FLTK pixle units
+ */
+int Fl_Anim_GIF_Image::frame_y(int frame) const {
+ if (frame >= 0 && frame < frames())
+ return fi_->frames[frame].y;
+ return -1;
+}
+
+
+/** Return the frame dimensions of a frame.
+
+ Usefull only if loaded with 'optimize_mem' and
+ the animation also has size optimized frames.
+
+ \param[in] frame index into frame list
+ \return width in FLTK pixle units
+ */
+int Fl_Anim_GIF_Image::frame_w(int frame) const {
+ if (frame >= 0 && frame < frames())
+ return fi_->frames[frame].w;
+ return -1;
+}
+
+
+/** Return the frame dimensions of a frame.
+
+ Usefull only if loaded with 'optimize_mem' and
+ the animation also has size optimized frames.
+
+ \param[in] frame index into frame list
+ \return height in FLTK pixle units
+ */
+int Fl_Anim_GIF_Image::frame_h(int frame) const {
+ if (frame >= 0 && frame < frames())
+ return fi_->frames[frame].h;
+ return -1;
+}
+
+
+/** Use frame_uncache() to set or forbid frame image uncaching.
+
+ If frame uncaching is set, frame images are not offscreen cached
+ for re-use and will be re-created every time they are displayed.
+ This saves a lot of memory on the expense of cpu usage and
+ should be carefully considered. Per default frame caching will
+ be done.
+
+ \param[in] uncache true to disable caching
+ */
+void Fl_Anim_GIF_Image::frame_uncache(bool uncache) {
+ uncache_ = uncache;
+}
+
+
+/** Return the active frame_uncache() setting.
+ \return true if caching is disabled
+ */
+bool Fl_Anim_GIF_Image::frame_uncache() const {
+ return uncache_;
+}
+
+
+/** Get the number of frames in the animation.
+ \return the number of frames
+ */
+int Fl_Anim_GIF_Image::frames() const {
+ return fi_->frames_size;
+}
+
+
+/** Return the current frame image.
+ \return a pointer to the image or NULL if this is not an animation.
+ */
+Fl_Image *Fl_Anim_GIF_Image::image() const {
+ return frame_ >= 0 && frame_ < frames() ? fi_->frames[frame_].rgb : 0;
+}
+
+
+/** Return the image of the given frame index.
+
+ \param[in] frame_ index into list of frames
+ \return image data or NULL if the frame number is not valid.
+ */
+Fl_Image *Fl_Anim_GIF_Image::image(int frame_) const {
+ if (frame_ >= 0 && frame_ < frames())
+ return fi_->frames[frame_].rgb;
+ return 0;
+}
+
+
+/** Check if this is a valid animation with more than one frame.
+
+ The \c is_animated() method is just a convenience method for testing the valid
+ flag and the frame count beeing greater 1.
+
+ \return true if the animation is valid and has multiple frames.
+ */
+bool Fl_Anim_GIF_Image::is_animated() const {
+ return valid_ && fi_->frames_size > 1;
+}
+
+
+/*static*/
+bool Fl_GIF_Image::is_animated(const char *name) {
+ return Fl_Anim_GIF_Image::frame_count(name) > 1;
+}
+
+
+/** Load an animation from a file or from a memory block.
+
+ The load() method is either used from the constructor to load the image from
+ the given file, or to re-load an existing animation from another file.
+
+ \param[in] name path and name of GIF file in the file system, or the image
+ name when reading from memory
+ \param[in] imgdata pointer to the start of the GIF image in memory, or
+ \c NULL to read from a file
+ \param[in] imglength length of the GIF image in memory, or \c 0
+ \return true if the animation loaded correctly
+ */
+bool Fl_Anim_GIF_Image::load(const char *name, const unsigned char *imgdata /* =NULL */, size_t imglength /* =0 */) {
+ DEBUG(("\nFl_Anim_GIF_Image::load '%s'\n", name));
+ clear_frames();
+ free(name_);
+ name_ = name ? strdup(name) : 0;
+
+ // as load() can be called multiple times
+ // we have to replicate the actions of the pixmap destructor here
+ uncache();
+ if (alloc_data) {
+ for (int i = 0; i < count(); i ++) delete[] (char *)data()[i];
+ delete[] (char **)data();
+ }
+ alloc_data = 0;
+ w(0);
+ h(0);
+
+ if (name_) {
+ fi_->load(name, imgdata, imglength);
+ }
+
+ frame_ = fi_->frames_size - 1;
+ valid_ = fi_->valid;
+
+ if (!valid_) {
+ Fl::error("Fl_Anim_GIF_Image: %s has invalid format.\n", name_);
+ ld(ERR_FORMAT);
+ }
+ return valid_;
+} // load
+
+
+/** Return the name of the played file as specified in the constructor.
+
+ If read from a memory block, this returns the name of the animation.
+
+ \return pointer to a C string
+ */
+const char *Fl_Anim_GIF_Image::name() const {
+ return name_;
+}
+
+
+bool Fl_Anim_GIF_Image::next_frame() {
+ int frame(frame_);
+ frame++;
+ if (frame >= fi_->frames_size) {
+ fi_->loop++;
+ if (Fl_Anim_GIF_Image::loop && fi_->loop_count > 0 && fi_->loop > fi_->loop_count) {
+ DEBUG(("loop count %d reached - stopped!\n", fi_->loop_count));
+ stop();
+ }
+ else
+ frame = 0;
+ }
+ if (frame >= fi_->frames_size)
+ return false;
+ set_frame(frame);
+ double delay = fi_->frames[frame].delay;
+ if (min_delay && delay < min_delay) {
+ DEBUG(("#%d: correct delay %f => %f\n", frame, delay, min_delay));
+ delay = min_delay;
+ }
+ if (is_animated() && delay > 0 && speed_ > 0) { // normal GIF has no delay
+ delay /= speed_;
+ Fl::add_timeout(delay, cb_animate, this);
+ }
+ return true;
+}
+
+
+/*virtual*/
+void Fl_Anim_GIF_Image::on_frame_data(Fl_GIF_Image::GIF_FRAME &gf) {
+ fi_->on_frame_data(gf);
+}
+
+
+/*virtual*/
+void Fl_Anim_GIF_Image::on_extension_data(Fl_GIF_Image::GIF_FRAME &gf) {
+ fi_->on_extension_data(gf);
+}
+
+
+/** Resizes the image to the specified size, replacing the current image.
+
+ If \ref DONT_RESIZE_CANVAS is not set, the canvas widget will also be resized.
+
+ \param[in] w, h new size of teh naimtion frames
+ */
+Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(int w, int h) {
+ int W(w);
+ int H(h);
+ if (canvas_ && !W && !H) {
+ W = canvas_->w();
+ H = canvas_->h();
+ }
+ if (!W || !H || ((W == this->w() && H == this->h()))) {
+ return *this;
+ }
+ fi_->resize(W, H);
+ scale_frame(); // scale current frame now
+ this->w(fi_->canvas_w);
+ this->h(fi_->canvas_h);
+ if (canvas_ && !(flags_ & DONT_RESIZE_CANVAS)) {
+ canvas_->size(this->w(), this->h());
+ }
+ return *this;
+}
+
+
+/** Resizes the image to the specified size, replacing the current image.
+
+ If \ref DONT_RESIZE_CANVAS is not set, the canvas widget will also be resized.
+
+ \param[in] scale rescale factor in relation to current size
+ */
+Fl_Anim_GIF_Image& Fl_Anim_GIF_Image::resize(double scale) {
+ return resize((int)lround((double)w() * scale), (int)lround((double)h() * scale));
+}
+
+
+void Fl_Anim_GIF_Image::scale_frame() {
+ int i(frame_);
+ if (i < 0)
+ return;
+ fi_->scale_frame(i);
+}
+
+
+void Fl_Anim_GIF_Image::set_frame() {
+ int i(frame_);
+ if (i < 0)
+ return;
+ fi_->set_frame(i);
+}
+
+
+void Fl_Anim_GIF_Image::set_frame(int frame) {
+// int last_frame = frame_;
+ frame_ = frame;
+ // NOTE: uncaching decreases performance, but saves a lot of memory
+ if (uncache_ && this->image())
+ this->image()->uncache();
+
+ fi_->set_frame(frame_);
+
+ Fl_Widget* cv = canvas();
+ if (cv) {
+ Fl_Group* parent = cv->parent();
+ bool no_bg = (cv->box() == FL_NO_BOX);
+ bool outside = (!(cv->align() & FL_ALIGN_INSIDE) && !((cv->align() & FL_ALIGN_POSITION_MASK)==FL_ALIGN_CENTER));
+// bool dispose = (fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_BACKGROUND)
+// || (fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_PREVIOUS);
+ if (parent && (no_bg || outside))
+ parent->redraw();
+ else
+ cv->redraw();
+
+// Note: the code below did not animate labels with a pixmap outside of the canvas
+// canvas()->parent() &&
+// (frame_ == 0 || (last_frame >= 0 && (fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_BACKGROUND ||
+// fi_->frames[last_frame].dispose == FrameInfo::DISPOSE_PREVIOUS))) &&
+// (canvas()->box() == FL_NO_BOX || (canvas()->align() && !(canvas()->align() & FL_ALIGN_INSIDE))) ?
+// canvas()->parent()->redraw() : canvas()->redraw();
+
+ }
+}
+
+
+/** Get the animation speed factor.
+ \return the current speed factor
+ */
+double Fl_Anim_GIF_Image::speed() const {
+ return speed_;
+}
+
+
+/** Set the animation speed factor.
+
+ The speed() method changes the playing speed to \p speed x original speed.
+ E.g. to play at half speed call it with 0.5, for double speed with 2.
+
+ \param[in] speed floating point speed factor
+ */
+void Fl_Anim_GIF_Image::speed(double speed) {
+ speed_ = speed;
+}
+
+
+/** The start() method (re-)starts the playing of the frames.
+ \return true if the animation has frames
+ */
+bool Fl_Anim_GIF_Image::start() {
+ Fl::remove_timeout(cb_animate, this);
+ if (fi_->frames_size) {
+ next_frame();
+ }
+ return fi_->frames_size != 0;
+}
+
+
+/** The stop() method stops the playing of the frames.
+ \return true if the animation has frames
+ */
+bool Fl_Anim_GIF_Image::stop() {
+ Fl::remove_timeout(cb_animate, this);
+ return fi_->frames_size != 0;
+}
+
+
+/** Show the next frame if the animation is stopped.
+ \return true if the animation has frames
+ */
+bool Fl_Anim_GIF_Image::next() {
+ if (fi_->frames_size && !(Fl::has_timeout(cb_animate, this))) {
+ int f = frame() + 1;
+ if (f >= frames()) f = 0;
+ frame(f);
+ }
+ return fi_->frames_size != 0;
+}
+
+
+/** Uncache all cached image data now.
+ Re-implemented from Fl_Pixmap.
+ */
+void Fl_Anim_GIF_Image::uncache() /* override */ {
+ Fl_GIF_Image::uncache();
+ for (int i=0; i < fi_->frames_size; i++) {
+ if (fi_->frames[i].rgb) fi_->frames[i].rgb->uncache();
+ }
+}
+
+
+/** Check if animation is valid.
+ \return true if the class has successfully loaded and the image has at least
+ one frame.
+ */
+bool Fl_Anim_GIF_Image::valid() const {
+ return valid_;
+}
diff --git a/src/Fl_GIF_Image.cxx b/src/Fl_GIF_Image.cxx
index a8b6250da..93fe22f36 100644
--- a/src/Fl_GIF_Image.cxx
+++ b/src/Fl_GIF_Image.cxx
@@ -1,7 +1,7 @@
//
// Fl_GIF_Image routines.
//
-// Copyright 1997-2021 by Bill Spitzak and others.
+// Copyright 1997-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@@ -74,6 +74,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.
It returns true (1) on error, false (0) otherwise.
@@ -137,8 +147,8 @@ Fl_GIF_Image::Fl_GIF_Image(const char *filename) :
Construct an image from a block of memory inside the application. Fluid offers
"binary data" chunks as a great way to add image data into the C++ source code.
- \p imagename can be NULL. If a name is given, the image is added to the list of
- shared images and will be available by that name.
+ \p imagename can be \c NULL. If a name is given, the image is added to the
+ list of shared images and will be available by that name.
If a GIF image is animated, Fl_GIF_Image will only read and display the
first frame of the animation.
@@ -205,193 +215,49 @@ Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data) :
}
}
-/*
- This method reads GIF image data and creates an RGB or RGBA image. The GIF
- format supports only 1 bit for alpha. The final image data is stored in
- a modified XPM format (Fl_GIF_Image is a subclass of Fl_Pixmap).
- To avoid code duplication, we use an Fl_Image_Reader that reads data from
- either a file or from memory.
-*/
-void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
+Fl_GIF_Image::Fl_GIF_Image(const char *filename, bool anim) :
+ Fl_Pixmap((char *const*)0)
{
- char **new_data; // Data array
- uchar *Image = 0L; // internal temporary image data array
- w(0); h(0);
-
- // printf("\nFl_GIF_Image::load_gif_ : %s\n", rdr.name());
-
- {char b[6] = { 0 };
- for (int i=0; i<6; ++i) b[i] = rdr.read_byte();
- if (b[0]!='G' || b[1]!='I' || b[2] != 'F') {
- Fl::error("Fl_GIF_Image: %s is not a GIF file.\n", rdr.name());
- ld(ERR_FORMAT);
- return;
- }
- if (b[3]!='8' || b[4]>'9' || b[5]!= 'a')
- Fl::warning("%s is version %c%c%c.",rdr.name(),b[3],b[4],b[5]);
- }
-
- int Width = rdr.read_word();
- int Height = rdr.read_word();
-
- uchar ch = rdr.read_byte();
- CHECK_ERROR
- char HasColormap = ((ch & 0x80) != 0);
- int BitsPerPixel = (ch & 7) + 1;
- int ColorMapSize;
- if (HasColormap) {
- ColorMapSize = 2 << (ch & 7);
+ Fl_Image_Reader rdr;
+ if (rdr.open(filename) == -1) {
+ Fl::error("Fl_GIF_Image: Unable to open %s!", filename);
+ ld(ERR_FILE_ACCESS);
} else {
- ColorMapSize = 0;
- }
- // int OriginalResolution = ((ch>>4)&7)+1;
- // int SortedTable = (ch&8)!=0;
- ch = rdr.read_byte(); // Background Color index
- ch = rdr.read_byte(); // Aspect ratio is N/64
- CHECK_ERROR
-
- // Read in global colormap:
- uchar transparent_pixel = 0;
- char has_transparent = 0;
- uchar Red[256], Green[256], Blue[256]; /* color map */
- if (HasColormap) {
- for (int i=0; i < ColorMapSize; i++) {
- Red[i] = rdr.read_byte();
- Green[i] = rdr.read_byte();
- Blue[i] = rdr.read_byte();
- }
- }
- CHECK_ERROR
-
- int CodeSize; /* Code size, init from GIF header, increases... */
- char Interlace;
-
- // Main parser loop: parse "blocks" until an image is found or error
-
- for (;;) {
-
- int i = rdr.read_byte();
- CHECK_ERROR
- int blocklen;
-
- if (i == 0x21) { // a "gif extension"
- ch = rdr.read_byte(); // extension type
- blocklen = rdr.read_byte();
- CHECK_ERROR
-
- if (ch == 0xF9 && blocklen == 4) { // Graphic Control Extension
- // printf("Graphic Control Extension at offset %ld\n", rdr.tell()-2);
- char bits = rdr.read_byte(); // Packed Fields
- rdr.read_word(); // Delay Time
- transparent_pixel = rdr.read_byte(); // Transparent Color Index
- blocklen = rdr.read_byte(); // Block Terminator (must be zero)
- CHECK_ERROR
- if (bits & 1) has_transparent = 1;
- }
- else if (ch == 0xFF) { // Application Extension
- // printf("Application Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
- ; // skip data
- }
- else if (ch == 0xFE) { // Comment Extension
- // printf("Comment Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
- ; // skip data
- }
- else if (ch == 0x01) { // Plain Text Extension
- // printf("Plain Text Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
- ; // skip data
- }
- else {
- Fl::warning("%s: unknown GIF extension 0x%02x at offset %ld, length = %d",
- rdr.name(), ch, rdr.tell()-3, blocklen);
- ; // skip data
- }
- } else if (i == 0x2c) { // an image: Image Descriptor follows
- // printf("Image Descriptor at offset %ld\n", rdr.tell());
- rdr.read_word(); // Image Left Position
- rdr.read_word(); // Image Top Position
- Width = rdr.read_word(); // Image Width
- Height = rdr.read_word(); // Image Height
- ch = rdr.read_byte(); // Packed Fields
- CHECK_ERROR
- Interlace = ((ch & 0x40) != 0);
- if (ch & 0x80) { // image has local color table
- // printf("Local Color Table at offset %ld\n", rdr.tell());
- BitsPerPixel = (ch & 7) + 1;
- ColorMapSize = 2 << (ch & 7);
- for (i=0; i < ColorMapSize; i++) {
- Red[i] = rdr.read_byte();
- Green[i] = rdr.read_byte();
- Blue[i] = rdr.read_byte();
- }
- }
- CHECK_ERROR
- break; // okay, this is the image we want
- } else if (i == 0x3b) { // Trailer (end of GIF data)
- // printf("Trailer found at offset %ld\n", rdr.tell());
- Fl::error("%s: no image data found.", rdr.name());
- ld(ERR_NO_IMAGE); // this GIF file is "empty" (no image)
- return; // terminate
- } else {
- Fl::error("%s: unknown GIF code 0x%02x at offset %ld", rdr.name(), i, rdr.tell()-1);
- ld(ERR_FORMAT); // broken file
- return; // terminate
- }
- CHECK_ERROR
-
- // skip all data (sub)blocks:
- while (blocklen > 0) {
- rdr.skip(blocklen);
- blocklen = rdr.read_byte();
- }
- // printf("End of data (sub)blocks at offset %ld\n", rdr.tell());
- }
-
- // read image data
-
- // printf("Image Data at offset %ld\n", rdr.tell());
-
- CodeSize = rdr.read_byte() + 1; // LZW Minimum Code Size
- CHECK_ERROR
-
- if (BitsPerPixel >= CodeSize) { // Workaround for broken GIF files...
- BitsPerPixel = CodeSize - 1;
- ColorMapSize = 1 << BitsPerPixel;
+ load_gif_(rdr, anim);
}
+}
- // Fix images w/o color table. The standard allows this and lets the
- // decoder choose a default color table. The standard recommends the
- // first two color table entries should be black and white.
-
- if (ColorMapSize == 0) { // no global and no local color table
- Fl::warning("%s does not have a color table, using default.\n", rdr.name());
- BitsPerPixel = CodeSize - 1;
- ColorMapSize = 1 << BitsPerPixel;
- Red[0] = Green[0] = Blue[0] = 0; // black
- Red[1] = Green[1] = Blue[1] = 255; // white
- for (int i = 2; i < ColorMapSize; i++) {
- Red[i] = Green[i] = Blue[i] = (uchar)(255 * i / (ColorMapSize - 1));
- }
+Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data, const size_t length, bool anim) :
+ Fl_Pixmap((char *const*)0)
+{
+ Fl_Image_Reader rdr;
+ if (rdr.open(imagename, data, length) == -1) {
+ ld(ERR_FILE_ACCESS);
+ } else {
+ load_gif_(rdr, anim);
}
+}
- // Fix transparent pixel index outside ColorMap (Issue #271)
- if (has_transparent && transparent_pixel >= ColorMapSize) {
- for (int k = ColorMapSize; k <= transparent_pixel; k++)
- Red[k] = Green[k] = Blue[k] = 0xff; // white (color is irrelevant)
- ColorMapSize = transparent_pixel + 1;
- }
+/**
+ The default constructor creates an empty GIF image.
-#if (0) // TEST/DEBUG: fill color table to maximum size
- for (int i = ColorMapSize; i < 256; i++) {
- Red[i] = Green[i] = Blue[i] = 0; // black
- }
-#endif
+*/
+Fl_GIF_Image::Fl_GIF_Image() :
+ Fl_Pixmap((char *const*)0)
+{
+}
- CHECK_ERROR
- // now read the LZW compressed image data
+/*
+ Internally used method to read from the LZW compressed data
+ stream 'rdr' and decode it to 'Image' buffer.
- Image = new uchar[Width*Height];
+ NOTE: This methode has been extracted from load_gif_()
+ in order to make the code more read/hand-able.
+*/
+void Fl_GIF_Image::lzw_decode(Fl_Image_Reader &rdr, uchar *Image,
+ int Width, int Height, int CodeSize, int ColorMapSize, int Interlace) {
int YC = 0, Pass = 0; /* Used to de-interlace the picture */
uchar *p = Image;
uchar *eol = p+Width;
@@ -419,10 +285,10 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
for (;;) {
/* Fetch the next code from the raster data stream. The codes can be
- * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to
- * maintain our location as a pointer and a bit offset.
- * In addition, GIF adds totally useless and annoying block counts
- * that must be correctly skipped over. */
+ * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to
+ * maintain our location as a pointer and a bit offset.
+ * In addition, GIF adds totally useless and annoying block counts
+ * that must be correctly skipped over. */
int CurCode = thisbyte;
if (frombit+CodeSize > 7) {
if (blocklen <= 0) {
@@ -455,8 +321,11 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
continue;
}
- if (CurCode == EOFCode)
+ if (CurCode == EOFCode) {
+ rdr.skip(blocklen);
+ blocklen = rdr.read_byte(); // Block-Terminator must follow!
break;
+ }
uchar OutCode[4097]; // temporary array for reversing codes
uchar *tp = OutCode;
@@ -515,47 +384,52 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
}
OldCode = CurCode;
}
+}
+
- // We are done reading the image, now convert to xpm
+/*
+ Internally used function to convert raw 'Image' data
+ to XPM format in an allocated buffer 'new_data'.
- w(Width);
- h(Height);
- d(1);
+ NOTE: This function has been extracted from load_gif_()
+ in order to make the code more read/hand-able.
+*/
+static char ** convert_to_xpm(uchar *Image, int Width, int Height, ColorMap &CMap, int ColorMapSize, int transparent_pixel) {
// allocate line pointer arrays:
- new_data = new char*[Height+2];
+ char **new_data = new char*[Height+2];
// transparent pixel must be zero, swap if it isn't:
- if (has_transparent && transparent_pixel != 0) {
+ if (transparent_pixel > 0) {
// swap transparent pixel with zero
- p = Image+Width*Height;
+ uchar *p = Image+Width*Height;
while (p-- > Image) {
if (*p==transparent_pixel) *p = 0;
else if (!*p) *p = transparent_pixel;
}
uchar t;
- t = Red[0];
- Red[0] = Red[transparent_pixel];
- Red[transparent_pixel] = t;
+ t = CMap.Red[0];
+ CMap.Red[0] = CMap.Red[transparent_pixel];
+ CMap.Red[transparent_pixel] = t;
- t = Green[0];
- Green[0] = Green[transparent_pixel];
- Green[transparent_pixel] = t;
+ t = CMap.Green[0];
+ CMap.Green[0] = CMap.Green[transparent_pixel];
+ CMap.Green[transparent_pixel] = t;
- t = Blue[0];
- Blue[0] = Blue[transparent_pixel];
- Blue[transparent_pixel] = t;
+ t = CMap.Blue[0];
+ CMap.Blue[0] = CMap.Blue[transparent_pixel];
+ CMap.Blue[transparent_pixel] = t;
}
// find out what colors are actually used:
uchar used[256]; uchar remap[256];
int i;
for (i = 0; i < ColorMapSize; i++) used[i] = 0;
- p = Image+Width*Height;
+ uchar *p = Image+Width*Height;
while (p-- > Image) used[*p] = 1;
// remap them to start with printing characters:
- int base = has_transparent && used[0] ? ' ' : ' '+1;
+ int base = transparent_pixel >= 0 && used[0] ? ' ' : ' '+1;
int numcolors = 0;
for (i = 0; i < ColorMapSize; i++) if (used[i]) {
remap[i] = (uchar)(base++);
@@ -563,6 +437,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
}
// write the first line of xpm data (use suffix as temp array):
+ uchar Suffix[4096];
int length = snprintf((char*)(Suffix), sizeof(Suffix),
"%d %d %d %d",Width,Height,-numcolors,1);
new_data[0] = new char[length+1];
@@ -572,9 +447,9 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
new_data[1] = (char*)(p = new uchar[4*numcolors]);
for (i = 0; i < ColorMapSize; i++) if (used[i]) {
*p++ = remap[i];
- *p++ = Red[i];
- *p++ = Green[i];
- *p++ = Blue[i];
+ *p++ = CMap.Red[i];
+ *p++ = CMap.Green[i];
+ *p++ = CMap.Blue[i];
}
// remap the image data:
@@ -588,9 +463,344 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
new_data[i + 2][Width] = 0;
}
- data((const char **)new_data, Height + 2);
- alloc_data = 1;
+ return new_data;
+}
+
+
+/*
+ This method reads GIF image data and creates an RGB or RGBA image. The GIF
+ format supports only 1 bit for alpha. The final image data is stored in
+ a modified XPM format (Fl_GIF_Image is a subclass of Fl_Pixmap).
+ To avoid code duplication, we use an Fl_Image_Reader that reads data from
+ either a file or from memory.
+
+ wcout 2022/01/20 - Added animated GIF support:
+ If the load_gif_() method is called with 'anim=true', then not only the first
+ image is decoded (as with Fl_GIF_Image), but all contained images are read.
+ The new Fl_Anim_GIF_Image class is derived from Fl_GIF_Image and utilises this
+ feature in order to avoid code duplication of the GIF decoding routines.
+ The first image is in this case (additionally) stored in the usual way as described
+ above (making the Fl_Anim_GIF_Image a normal Fl_GIF_Image too).
+ All subsequent images are only decoded (and not converted to XPM) and passed
+ to Fl_Anim_GIF_Image, which stores them on its own (in RGBA format).
+*/
+void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr, bool anim/*=false*/)
+{
+ uchar *Image = 0L; // internal temporary image data array
+ int frame = 0;
+
+ // Needed for proper decoding of *all* images in file (Fl_Anim_GIF_Image)
+ uchar background_color_index = 0;
+ GIF_FRAME::CPAL GlobalColorTable[256];
+ bool HasGlobalColorTable = false;
+ GIF_FRAME::CPAL LocalColorTable[256];
+ bool HasLocalColorTable = false;
+
+ w(0); h(0);
+
+ // printf("\nFl_GIF_Image::load_gif_ : %s\n", rdr.name());
+
+ {char b[6] = { 0 };
+ for (int i=0; i<6; ++i) b[i] = rdr.read_byte();
+ if (b[0]!='G' || b[1]!='I' || b[2] != 'F') {
+ Fl::error("Fl_GIF_Image: %s is not a GIF file.\n", rdr.name());
+ ld(ERR_FORMAT);
+ return;
+ }
+ if (b[3]!='8' || b[4]>'9' || b[5]!= 'a')
+ Fl::warning("%s is version %c%c%c.",rdr.name(),b[3],b[4],b[5]);
+ }
+
+ int ScreenWidth = rdr.read_word();
+ int ScreenHeight = rdr.read_word();
+ int Width = ScreenWidth;
+ int Height = ScreenHeight;
+
+ uchar ch = rdr.read_byte();
+ CHECK_ERROR
+ char HasColormap = ((ch & 0x80) != 0);
+ HasGlobalColorTable = HasColormap;
+ memset(GlobalColorTable, 0, sizeof(GlobalColorTable));
+ int BitsPerPixel = (ch & 7) + 1;
+ int ColorMapSize = HasColormap ? 2 << (ch & 7) : 0;
+ // int OriginalResolution = ((ch>>4)&7)+1;
+ // int SortedTable = (ch&8)!=0;
+ background_color_index = rdr.read_byte(); // Background Color index
+ ch = rdr.read_byte(); // Aspect ratio is N/64
+ CHECK_ERROR
+
+ // Read in global colormap:
+ uchar transparent_pixel = 0;
+ char has_transparent = 0;
+ char user_input = 0;
+ int delay = 0;
+ int dispose = 0;
+ int XPos = 0;
+ int YPos = 0;
+ struct ColorMap CMap; /* color map */
+ if (HasColormap) {
+ for (int i=0; i < ColorMapSize; i++) {
+ CMap.Red[i] = rdr.read_byte();
+ CMap.Green[i] = rdr.read_byte();
+ CMap.Blue[i] = rdr.read_byte();
+ // store away for reading of further images in file (Fl_Anim_GIF_Image)
+ // because the values are changed during processing.
+ GlobalColorTable[i].r = CMap.Red[i];
+ GlobalColorTable[i].g = CMap.Green[i];
+ GlobalColorTable[i].b = CMap.Blue[i];
+ }
+ }
+ CHECK_ERROR
+
+ char Interlace = 0;
+
+ // Main parser loop: parse "blocks" until an image is found or error
+
+ for (;;) {
+
+ int i = rdr.read_byte();
+ CHECK_ERROR
+ int blocklen = 0;
+
+ if (i == 0x21) { // a "gif extension"
+ ch = rdr.read_byte(); // extension type
+ blocklen = rdr.read_byte();
+ CHECK_ERROR
+
+ if (ch == 0xF9 && blocklen == 4) { // Graphic Control Extension
+ // printf("Graphic Control Extension at offset %ld\n", rdr.tell()-2);
+ uchar bits = rdr.read_byte(); // Packed Fields xxxDDDUT
+ dispose = (bits >> 2) & 7;
+ delay = rdr.read_word(); // Delay Time
+ transparent_pixel = rdr.read_byte(); // Transparent Color Index
+ blocklen = rdr.read_byte(); // Block Terminator (must be zero)
+ CHECK_ERROR
+ has_transparent = (bits & 1) ? 1 : 0;
+ user_input = (bits & 2) ? 1 : 0;
+ }
+ else if (ch == 0xFF) { // Application Extension
+ // printf("Application Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
+ uchar buf[512];
+ memset(buf, 0, sizeof(buf));
+ for (i=0; i<blocklen; i++) buf[i] = rdr.read_byte();
+ blocklen = rdr.read_byte(); // read next Data Sub-block too for NETSCAPE ext.
+ CHECK_ERROR
+ if (blocklen) {
+ while (blocklen--) buf[i++] = rdr.read_byte();
+ blocklen = rdr.read_byte();
+ }
+ CHECK_ERROR
+
+ // Notify derived class on loaded extension data
+
+ GIF_FRAME f(frame, buf);
+ on_extension_data(f);
+ }
+ 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());
+ XPos = rdr.read_word(); // Image Left Position
+ YPos = 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);
+ HasLocalColorTable = ch & 0x80;
+ 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);
+ memset(LocalColorTable, 0, sizeof(LocalColorTable));
+ for (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.
+ LocalColorTable[i].r = CMap.Red[i];
+ LocalColorTable[i].g = CMap.Green[i];
+ LocalColorTable[i].b = CMap.Blue[i];
+ }
+ }
+ CHECK_ERROR
+
+ // read image data
+
+ // printf("Image Data at offset %ld\n", rdr.tell());
+
+ int CodeSize = rdr.read_byte(); // LZW initial Code Size (increases...)
+ CHECK_ERROR
+ if (CodeSize < 2 || CodeSize > 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<ColorMapSize; i++) {
+ printf("%d: #%02X%02X%02X\n", i, gf.cpal[i].r, gf.cpal[i].g, gf.cpal[i].b);
+ }
+#endif
+ on_frame_data(gf);
+
+ // We are done reading the image, now convert to xpm (first image only)
+ if (!frame) {
+ 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<ymax; y++) {
+ uchar *src = Image + (y-YPos) * Width + (xstart-XPos);
+ uchar *dst = moved_image + y*ScreenWidth + xstart;
+ memcpy(dst, src, xmax-xstart);
+ }
+ char **new_data = convert_to_xpm(moved_image, ScreenWidth, ScreenHeight, CMap, ColorMapSize, has_transparent ? transparent_pixel : -1);
+ data((const char **)new_data, Height + 2);
+ alloc_data = 1;
+ delete[] moved_image;
+ } else {
+ // Fl_GIF_Image does not apply offsets and just show the first frame at 0, 0
+ w(Width);
+ h(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;
+ }
+ }
+
+ delete[] Image;
+ Image = NULL;
+
+ if (!anim)
+ break; // okay, it is only the first image we want
+ frame++; // continue to read more images (of animated GIF)
+ // end of Image Descriptor block processing
+ } else if (i == 0x3b) { // Trailer (end of GIF data)
+ // printf("Trailer found at offset %ld\n", rdr.tell());
+ if (!frame) {
+ 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());
+ }
- 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 cbba4076d..f491ce2d8 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -230,6 +230,7 @@ IMGCPPFILES = \
Fl_BMP_Image.cxx \
Fl_File_Icon2.cxx \
Fl_GIF_Image.cxx \
+ Fl_Anim_GIF_Image.cxx \
Fl_Help_Dialog.cxx \
Fl_ICO_Image.cxx \
Fl_JPEG_Image.cxx \
diff --git a/src/fl_images_core.cxx b/src/fl_images_core.cxx
index 5c2a7e98b..431e7c98c 100644
--- a/src/fl_images_core.cxx
+++ b/src/fl_images_core.cxx
@@ -27,6 +27,7 @@
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl_BMP_Image.H>
#include <FL/Fl_GIF_Image.H>
+#include <FL/Fl_Anim_GIF_Image.H>
#include <FL/Fl_JPEG_Image.H>
#include <FL/Fl_PNG_Image.H>
#include <FL/Fl_PNM_Image.H>
@@ -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 5db28d5a8..108b02437 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