/* * Copyright 2003 John M Bell * Copyright 2004 Richard Wilson * Copyright 2008 Sean Fox * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** \file * Content for image/gif (implementation) * * All GIFs are dynamically decompressed using the routines that gifread.c * provides. Whilst this allows support for progressive decoding, it is * not implemented here as NetSurf currently does not provide such support. * * [rjw] - Sun 4th April 2004 */ #include #include #include #include #include #include "utils/config.h" #include "content/content_protected.h" #include "content/hlcache.h" #include "desktop/options.h" #include "desktop/plotters.h" #include "image/bitmap.h" #include "image/gif.h" #include "utils/log.h" #include "utils/messages.h" #include "utils/schedule.h" #include "utils/talloc.h" #include "utils/utils.h" typedef struct nsgif_content { struct content base; struct gif_animation *gif; /**< GIF animation data */ int current_frame; /**< current frame to display [0...(max-1)] */ struct bitmap *bitmap; /**< Created NetSurf bitmap */ } nsgif_content; /** * Callback for libnsgif; forwards the call to bitmap_create() * * \param width width of image in pixels * \param height width of image in pixels * \return an opaque struct bitmap, or NULL on memory exhaustion */ static void *nsgif_bitmap_create(int width, int height) { return bitmap_create(width, height, BITMAP_NEW); } /* The Bitmap callbacks function table; * necessary for interaction with nsgiflib. */ static gif_bitmap_callback_vt gif_bitmap_callbacks = { .bitmap_create = nsgif_bitmap_create, .bitmap_destroy = bitmap_destroy, .bitmap_get_buffer = bitmap_get_buffer, .bitmap_set_opaque = bitmap_set_opaque, .bitmap_test_opaque = bitmap_test_opaque, .bitmap_modified = bitmap_modified }; static nserror nsgif_create_gif_data(nsgif_content *c) { union content_msg_data msg_data; /* Initialise our data structure */ c->gif = calloc(sizeof(gif_animation), 1); if (c->gif == NULL) { msg_data.error = messages_get("NoMemory"); content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); return NSERROR_NOMEM; } gif_create(c->gif, &gif_bitmap_callbacks); return NSERROR_OK; } static nserror nsgif_create(const content_handler *handler, lwc_string *imime_type, const struct http_parameter *params, llcache_handle *llcache, const char *fallback_charset, bool quirks, struct content **c) { nsgif_content *result; nserror error; result = talloc_zero(0, nsgif_content); if (result == NULL) return NSERROR_NOMEM; error = content__init(&result->base, handler, imime_type, params, llcache, fallback_charset, quirks); if (error != NSERROR_OK) { talloc_free(result); return error; } error = nsgif_create_gif_data(result); if (error != NSERROR_OK) { talloc_free(result); return error; } *c = (struct content *) result; return NSERROR_OK; } /** * Performs any necessary animation. * * \param p The content to animate */ static void nsgif_animate(void *p) { nsgif_content *gif = p; union content_msg_data data; int delay; int f; /* Advance by a frame, updating the loop count accordingly */ gif->current_frame++; if (gif->current_frame == (int)gif->gif->frame_count_partial) { gif->current_frame = 0; /* A loop count of 0 has a special meaning of infinite */ if (gif->gif->loop_count != 0) { gif->gif->loop_count--; if (gif->gif->loop_count == 0) { gif->current_frame = gif->gif->frame_count_partial - 1; gif->gif->loop_count = -1; } } } /* Continue animating if we should */ if (gif->gif->loop_count >= 0) { delay = gif->gif->frames[gif->current_frame].frame_delay; if (delay < option_minimum_gif_delay) delay = option_minimum_gif_delay; schedule(delay, nsgif_animate, gif); } if ((!option_animate_images) || (!gif->gif->frames[gif->current_frame].display)) return; /* area within gif to redraw */ f = gif->current_frame; data.redraw.x = gif->gif->frames[f].redraw_x; data.redraw.y = gif->gif->frames[f].redraw_y; data.redraw.width = gif->gif->frames[f].redraw_width; data.redraw.height = gif->gif->frames[f].redraw_height; /* redraw background (true) or plot on top (false) */ if (gif->current_frame > 0) { data.redraw.full_redraw = gif->gif->frames[f - 1].redraw_required; /* previous frame needed clearing: expand the redraw area to * cover it */ if (data.redraw.full_redraw) { if (data.redraw.x > (int)(gif->gif->frames[f - 1].redraw_x)) { data.redraw.width += data.redraw.x - gif->gif->frames[f - 1].redraw_x; data.redraw.x = gif->gif->frames[f - 1].redraw_x; } if (data.redraw.y > (int)(gif->gif->frames[f - 1].redraw_y)) { data.redraw.height += (data.redraw.y - gif->gif->frames[f - 1].redraw_y); data.redraw.y = gif->gif->frames[f - 1].redraw_y; } if ((int)(gif->gif->frames[f - 1].redraw_x + gif->gif->frames[f - 1].redraw_width) > (data.redraw.x + data.redraw.width)) data.redraw.width = gif->gif->frames[f - 1].redraw_x - data.redraw.x + gif->gif->frames[f - 1].redraw_width; if ((int)(gif->gif->frames[f - 1].redraw_y + gif->gif->frames[f - 1].redraw_height) > (data.redraw.y + data.redraw.height)) data.redraw.height = gif->gif->frames[f - 1].redraw_y - data.redraw.y + gif->gif->frames[f - 1].redraw_height; } } else { /* do advanced check */ if ((data.redraw.x == 0) && (data.redraw.y == 0) && (data.redraw.width == (int)(gif->gif->width)) && (data.redraw.height == (int)(gif->gif->height))) { data.redraw.full_redraw = !gif->gif->frames[f].opaque; } else { data.redraw.full_redraw = true; data.redraw.x = 0; data.redraw.y = 0; data.redraw.width = gif->gif->width; data.redraw.height = gif->gif->height; } } /* other data */ data.redraw.object = (struct content *) gif; data.redraw.object_x = 0; data.redraw.object_y = 0; data.redraw.object_width = gif->base.width; data.redraw.object_height = gif->base.height; content_broadcast(&gif->base, CONTENT_MSG_REDRAW, data); } static void nsgif_invalidate(void *bitmap, void *private_word) { struct gif_animation *gif = (struct gif_animation *)private_word; gif->decoded_frame = -1; } static bool nsgif_convert(struct content *c) { nsgif_content *gif = (nsgif_content *) c; int res; union content_msg_data msg_data; const char *data; unsigned long size; char title[100]; /* Get the animation */ data = content__get_source_data(c, &size); /* Initialise the GIF */ do { res = gif_initialise(gif->gif, size, (unsigned char *) data); if (res != GIF_OK && res != GIF_WORKING && res != GIF_INSUFFICIENT_FRAME_DATA) { switch (res) { case GIF_FRAME_DATA_ERROR: case GIF_INSUFFICIENT_DATA: case GIF_DATA_ERROR: msg_data.error = messages_get("BadGIF"); break; case GIF_INSUFFICIENT_MEMORY: msg_data.error = messages_get("NoMemory"); break; } content_broadcast(c, CONTENT_MSG_ERROR, msg_data); return false; } } while (res != GIF_OK && res != GIF_INSUFFICIENT_FRAME_DATA); /* Abort on bad GIFs */ if ((gif->gif->frame_count_partial == 0) || (gif->gif->width == 0) || (gif->gif->height == 0)) { msg_data.error = messages_get("BadGIF"); content_broadcast(c, CONTENT_MSG_ERROR, msg_data); return false; } /* Store our content width and description */ c->width = gif->gif->width; c->height = gif->gif->height; snprintf(title, sizeof(title), messages_get("GIFTitle"), c->width, c->height, size); content__set_title(c, title); c->size += (gif->gif->width * gif->gif->height * 4) + 16 + 44; /* Schedule the animation if we have one */ gif->current_frame = 0; if (gif->gif->frame_count_partial > 1) schedule(gif->gif->frames[0].frame_delay, nsgif_animate, c); else bitmap_set_suspendable(gif->gif->frame_image, gif->gif, nsgif_invalidate); /* Exit as a success */ gif->bitmap = gif->gif->frame_image; content_set_ready(c); content_set_done(c); /* Done: update status bar */ content_set_status(c, ""); return true; } /** * Updates the GIF bitmap to display the current frame * * \param c the content to update */ static gif_result nsgif_get_frame(struct content *c) { nsgif_content *gif = (nsgif_content *) c; int previous_frame, current_frame, frame; gif_result res = GIF_OK; current_frame = gif->current_frame; if (!option_animate_images) current_frame = 0; if (current_frame < gif->gif->decoded_frame) previous_frame = 0; else previous_frame = gif->gif->decoded_frame + 1; for (frame = previous_frame; frame <= current_frame; frame++) res = gif_decode_frame(gif->gif, frame); return res; } static bool nsgif_redraw(struct content *c, struct content_redraw_data *data, const struct rect *clip, const struct redraw_context *ctx) { nsgif_content *gif = (nsgif_content *) c; bitmap_flags_t flags = BITMAPF_NONE; if (gif->current_frame != gif->gif->decoded_frame) if (nsgif_get_frame(c) != GIF_OK) return false; gif->bitmap = gif->gif->frame_image; if ((data->width == -1) && (data->height == -1)) return true; if (data->repeat_x) flags |= BITMAPF_REPEAT_X; if (data->repeat_y) flags |= BITMAPF_REPEAT_Y; return ctx->plot->bitmap(data->x, data->y, data->width, data->height, gif->bitmap, data->background_colour, flags); } static void nsgif_destroy(struct content *c) { nsgif_content *gif = (nsgif_content *) c; /* Free all the associated memory buffers */ schedule_remove(nsgif_animate, c); gif_finalise(gif->gif); free(gif->gif); } static nserror nsgif_clone(const struct content *old, struct content **newc) { nsgif_content *gif; nserror error; gif = talloc_zero(0, nsgif_content); if (gif == NULL) return NSERROR_NOMEM; error = content__clone(old, &gif->base); if (error != NSERROR_OK) { content_destroy(&gif->base); return error; } /* Simply replay creation and conversion of content */ error = nsgif_create_gif_data(gif); if (error != NSERROR_OK) { content_destroy(&gif->base); return error; } if (old->status == CONTENT_STATUS_READY || old->status == CONTENT_STATUS_DONE) { if (nsgif_convert(&gif->base) == false) { content_destroy(&gif->base); return NSERROR_CLONE_FAILED; } } *newc = (struct content *) gif; return NSERROR_OK; } static void *nsgif_get_internal(const struct content *c, void *context) { nsgif_content *gif = (nsgif_content *) c; return gif->bitmap; } static content_type nsgif_content_type(lwc_string *mime_type) { return CONTENT_IMAGE; } static const content_handler nsgif_content_handler = { .create = nsgif_create, .data_complete = nsgif_convert, .destroy = nsgif_destroy, .redraw = nsgif_redraw, .clone = nsgif_clone, .get_internal = nsgif_get_internal, .type = nsgif_content_type, .no_share = false, }; static const char *nsgif_types[] = { "image/gif" }; CONTENT_FACTORY_REGISTER_TYPES(nsgif, nsgif_types, nsgif_content_handler);