From 3224d7121aff91ab8d888831dc493ef621e3dd39 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Mon, 23 May 2016 23:32:16 +0100 Subject: move image content handlers to accomodate core build changes --- content/handlers/image/Makefile | 15 + content/handlers/image/bitmap.h | 177 ++++++++ content/handlers/image/bmp.c | 282 +++++++++++++ content/handlers/image/bmp.h | 33 ++ content/handlers/image/gif.c | 455 ++++++++++++++++++++ content/handlers/image/gif.h | 29 ++ content/handlers/image/ico.c | 300 +++++++++++++ content/handlers/image/ico.h | 28 ++ content/handlers/image/image.c | 159 +++++++ content/handlers/image/image.h | 43 ++ content/handlers/image/image_cache.c | 797 +++++++++++++++++++++++++++++++++++ content/handlers/image/image_cache.h | 189 +++++++++ content/handlers/image/jpeg.c | 393 +++++++++++++++++ content/handlers/image/jpeg.h | 28 ++ content/handlers/image/nssprite.c | 259 ++++++++++++ content/handlers/image/nssprite.h | 28 ++ content/handlers/image/png.c | 614 +++++++++++++++++++++++++++ content/handlers/image/png.h | 25 ++ content/handlers/image/rsvg.c | 326 ++++++++++++++ content/handlers/image/rsvg.h | 28 ++ content/handlers/image/svg.c | 353 ++++++++++++++++ content/handlers/image/svg.h | 28 ++ content/handlers/image/video.c | 202 +++++++++ content/handlers/image/video.h | 26 ++ 24 files changed, 4817 insertions(+) create mode 100644 content/handlers/image/Makefile create mode 100644 content/handlers/image/bitmap.h create mode 100644 content/handlers/image/bmp.c create mode 100644 content/handlers/image/bmp.h create mode 100644 content/handlers/image/gif.c create mode 100644 content/handlers/image/gif.h create mode 100644 content/handlers/image/ico.c create mode 100644 content/handlers/image/ico.h create mode 100644 content/handlers/image/image.c create mode 100644 content/handlers/image/image.h create mode 100644 content/handlers/image/image_cache.c create mode 100644 content/handlers/image/image_cache.h create mode 100644 content/handlers/image/jpeg.c create mode 100644 content/handlers/image/jpeg.h create mode 100644 content/handlers/image/nssprite.c create mode 100644 content/handlers/image/nssprite.h create mode 100644 content/handlers/image/png.c create mode 100644 content/handlers/image/png.h create mode 100644 content/handlers/image/rsvg.c create mode 100644 content/handlers/image/rsvg.h create mode 100644 content/handlers/image/svg.c create mode 100644 content/handlers/image/svg.h create mode 100644 content/handlers/image/video.c create mode 100644 content/handlers/image/video.h (limited to 'content/handlers/image') diff --git a/content/handlers/image/Makefile b/content/handlers/image/Makefile new file mode 100644 index 000000000..5851a1c43 --- /dev/null +++ b/content/handlers/image/Makefile @@ -0,0 +1,15 @@ +# Image content handlers sources + +# S_IMAGE are sources related to image management +S_IMAGE_YES := image.c image_cache.c +S_IMAGE_NO := +S_IMAGE_$(NETSURF_USE_BMP) += bmp.c ico.c +S_IMAGE_$(NETSURF_USE_GIF) += gif.c +S_IMAGE_$(NETSURF_USE_JPEG) += jpeg.c +S_IMAGE_$(NETSURF_USE_ROSPRITE) += nssprite.c +S_IMAGE_$(NETSURF_USE_PNG) += png.c +S_IMAGE_$(NETSURF_USE_NSSVG) += svg.c +S_IMAGE_$(NETSURF_USE_RSVG) += rsvg.c +S_IMAGE_$(NETSURF_USE_VIDEO) += video.c + +S_IMAGE := $(addprefix image/,$(S_IMAGE_YES)) diff --git a/content/handlers/image/bitmap.h b/content/handlers/image/bitmap.h new file mode 100644 index 000000000..ef6c1b49b --- /dev/null +++ b/content/handlers/image/bitmap.h @@ -0,0 +1,177 @@ +/* + * Copyright 2004 James Bursa + * + * 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 + * Generic bitmap handling interface. + * + * This interface wraps the native platform-specific image format, so that + * portable image convertors can be written. + * + * Bitmaps are required to be 32bpp with components in the order RR GG BB AA. + * + * For example, an opaque 1x1 pixel image would yield the following bitmap + * data: + * + * > Red : 0xff 0x00 0x00 0x00 + * > Green: 0x00 0xff 0x00 0x00 + * > Blue : 0x00 0x00 0xff 0x00 + * + * Any attempt to read pixels by casting bitmap data to uint32_t or similar + * will need to cater for the order of bytes in a word being different on + * big and little endian systems. To avoid confusion, it is recommended + * that pixel data is loaded as follows: + * + * uint32_t read_pixel(const uint8_t *bmp) + * { + * // red green blue alpha + * return bmp[0] | (bmp[1] << 8) | (bmp[2] << 16) | (bmp[3] << 24); + * } + * + * and *not* as follows: + * + * uint32_t read_pixel(const uint8_t *bmp) + * { + * return *((uint32_t *) bmp); + * } + */ + +#ifndef _NETSURF_IMAGE_BITMAP_H_ +#define _NETSURF_IMAGE_BITMAP_H_ + +#define BITMAP_NEW 0 +#define BITMAP_OPAQUE (1 << 0) /**< image is opaque */ +#define BITMAP_MODIFIED (1 << 1) /**< buffer has been modified */ +#define BITMAP_CLEAR_MEMORY (1 << 2) /**< memory should be wiped */ + +struct content; +struct bitmap; +struct hlcache_handle; + +/** + * Bitmap operations. + */ +struct gui_bitmap_table { + /* Mandantory entries */ + + /** + * Create a new bitmap. + * + * \param width width of image in pixels + * \param height width of image in pixels + * \param state The state to create the bitmap in. + * \return A bitmap structure or NULL on error. + */ + void *(*create)(int width, int height, unsigned int state); + + /** + * Destroy a bitmap. + * + * \param bitmap The bitmap to destroy. + */ + void (*destroy)(void *bitmap); + + /** + * Set the opacity of a bitmap. + * + * \param bitmap The bitmap to set opacity on. + * \param opaque The bitmap opacity to set. + */ + void (*set_opaque)(void *bitmap, bool opaque); + + /** + * Get the opacity of a bitmap. + * + * \param bitmap The bitmap to examine. + * \return The bitmap opacity. + */ + bool (*get_opaque)(void *bitmap); + + /** + * Test if a bitmap is opaque. + * + * \param bitmap The bitmap to examine. + * \return The bitmap opacity. + */ + bool (*test_opaque)(void *bitmap); + + /** + * Get the image buffer from a bitmap + * + * \param bitmap The bitmap to get the buffer from. + * \return The image buffer or NULL if there is none. + */ + unsigned char *(*get_buffer)(void *bitmap); + + /** + * Get the number of bytes per row of the image + * + * \param bitmap The bitmap + * \return The number of bytes for a row of the bitmap. + */ + size_t (*get_rowstride)(void *bitmap); + + /** + * Get the bitmap width + * + * \param bitmap The bitmap + * \return The bitmap width in pixels. + */ + int (*get_width)(void *bitmap); + + /** + * Get the bitmap height + * + * \param bitmap The bitmap + * \return The bitmap height in pixels. + */ + int (*get_height)(void *bitmap); + + /** + * The the *bytes* per pixel. + * + * \param bitmap The bitmap + */ + size_t (*get_bpp)(void *bitmap); + + /** + * Savde a bitmap to disc. + * + * \param bitmap The bitmap to save + * \param path The path to save the bitmap to. + * \param flags Flags affectin the save. + */ + bool (*save)(void *bitmap, const char *path, unsigned flags); + + /** + * Marks a bitmap as modified. + * + * \param bitmap The bitmap set as modified. + */ + void (*modified)(void *bitmap); + + /** + * Render content into a bitmap. + * + * \param bitmap The bitmap to render into. + * \param content The content to render. + */ + nserror (*render)(struct bitmap *bitmap, struct hlcache_handle *content); +}; + +#endif diff --git a/content/handlers/image/bmp.c b/content/handlers/image/bmp.c new file mode 100644 index 000000000..f622f6cb0 --- /dev/null +++ b/content/handlers/image/bmp.c @@ -0,0 +1,282 @@ +/* + * Copyright 2006 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 + * implementation of content handler for BMP images. + */ + +#include +#include +#include + +#include "utils/utils.h" +#include "utils/messages.h" +#include "content/content_protected.h" +#include "desktop/gui_internal.h" +#include "desktop/plotters.h" + +#include "bitmap.h" +#include "bmp.h" + +/** bmp context. */ +typedef struct nsbmp_content { + struct content base; + + bmp_image *bmp; /** BMP image data */ + + struct bitmap *bitmap; /**< Created NetSurf bitmap */ +} nsbmp_content; + +/** + * Callback for libnsbmp; forwards the call to bitmap_create() + * + * \param width width of image in pixels + * \param height width of image in pixels + * \param bmp_state A flag word indicating the initial state + * \return An opaque struct bitmap, or NULL on memory exhaustion + */ +static void *nsbmp_bitmap_create(int width, int height, unsigned int bmp_state) +{ + unsigned int bitmap_state = BITMAP_NEW; + + /* set bitmap state based on bmp state */ + bitmap_state |= (bmp_state & BMP_OPAQUE) ? BITMAP_OPAQUE : 0; + bitmap_state |= (bmp_state & BMP_CLEAR_MEMORY) ? + BITMAP_CLEAR_MEMORY : 0; + + /* return the created bitmap */ + return guit->bitmap->create(width, height, bitmap_state); +} + +static nserror nsbmp_create_bmp_data(nsbmp_content *bmp) +{ + union content_msg_data msg_data; + bmp_bitmap_callback_vt bmp_bitmap_callbacks = { + .bitmap_create = nsbmp_bitmap_create, + .bitmap_destroy = guit->bitmap->destroy, + .bitmap_get_buffer = guit->bitmap->get_buffer, + .bitmap_get_bpp = guit->bitmap->get_bpp + }; + + bmp->bmp = calloc(sizeof(struct bmp_image), 1); + if (bmp->bmp == NULL) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(&bmp->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; + } + + bmp_create(bmp->bmp, &bmp_bitmap_callbacks); + + return NSERROR_OK; +} + +static nserror nsbmp_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) +{ + nsbmp_content *bmp; + nserror error; + + bmp = calloc(1, sizeof(nsbmp_content)); + if (bmp == NULL) + return NSERROR_NOMEM; + + error = content__init(&bmp->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(bmp); + return error; + } + + error = nsbmp_create_bmp_data(bmp); + if (error != NSERROR_OK) { + free(bmp); + return error; + } + + *c = (struct content *) bmp; + + return NSERROR_OK; +} + +static bool nsbmp_convert(struct content *c) +{ + nsbmp_content *bmp = (nsbmp_content *) c; + bmp_result res; + union content_msg_data msg_data; + uint32_t swidth; + const char *data; + unsigned long size; + char *title; + + /* set the bmp data */ + data = content__get_source_data(c, &size); + + /* analyse the BMP */ + res = bmp_analyse(bmp->bmp, size, (unsigned char *) data); + switch (res) { + case BMP_OK: + break; + case BMP_INSUFFICIENT_MEMORY: + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + case BMP_INSUFFICIENT_DATA: + case BMP_DATA_ERROR: + msg_data.error = messages_get("BadBMP"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + /* Store our content width and description */ + c->width = bmp->bmp->width; + c->height = bmp->bmp->height; + swidth = bmp->bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bmp->bitmap) * + bmp->bmp->width; + c->size += (swidth * bmp->bmp->height) + 16 + 44; + + /* set title text */ + title = messages_get_buff("BMPTitle", + nsurl_access_leaf(llcache_handle_get_url(c->llcache)), + c->width, c->height); + if (title != NULL) { + content__set_title(c, title); + free(title); + } + + /* exit as a success */ + bmp->bitmap = bmp->bmp->bitmap; + guit->bitmap->modified(bmp->bitmap); + + content_set_ready(c); + content_set_done(c); + + /* Done: update status bar */ + content_set_status(c, ""); + return true; +} + +static bool nsbmp_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + nsbmp_content *bmp = (nsbmp_content *) c; + bitmap_flags_t flags = BITMAPF_NONE; + + if (bmp->bmp->decoded == false) + if (bmp_decode(bmp->bmp) != BMP_OK) + return false; + + bmp->bitmap = bmp->bmp->bitmap; + + 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, + bmp->bitmap, data->background_colour, flags); +} + + +static void nsbmp_destroy(struct content *c) +{ + nsbmp_content *bmp = (nsbmp_content *) c; + + bmp_finalise(bmp->bmp); + free(bmp->bmp); +} + + +static nserror nsbmp_clone(const struct content *old, struct content **newc) +{ + nsbmp_content *new_bmp; + nserror error; + + new_bmp = calloc(1, sizeof(nsbmp_content)); + if (new_bmp == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, &new_bmp->base); + if (error != NSERROR_OK) { + content_destroy(&new_bmp->base); + return error; + } + + /* We "clone" the old content by replaying creation and conversion */ + error = nsbmp_create_bmp_data(new_bmp); + if (error != NSERROR_OK) { + content_destroy(&new_bmp->base); + return error; + } + + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (nsbmp_convert(&new_bmp->base) == false) { + content_destroy(&new_bmp->base); + return NSERROR_CLONE_FAILED; + } + } + + *newc = (struct content *) new_bmp; + + return NSERROR_OK; +} + +static void *nsbmp_get_internal(const struct content *c, void *context) +{ + nsbmp_content *bmp = (nsbmp_content *)c; + + return bmp->bitmap; +} + +static content_type nsbmp_content_type(void) +{ + return CONTENT_IMAGE; +} + + +static const content_handler nsbmp_content_handler = { + .create = nsbmp_create, + .data_complete = nsbmp_convert, + .destroy = nsbmp_destroy, + .redraw = nsbmp_redraw, + .clone = nsbmp_clone, + .get_internal = nsbmp_get_internal, + .type = nsbmp_content_type, + .no_share = false, +}; + +static const char *nsbmp_types[] = { + "application/bmp", + "application/preview", + "application/x-bmp", + "application/x-win-bitmap", + "image/bmp", + "image/ms-bmp", + "image/x-bitmap", + "image/x-bmp", + "image/x-ms-bmp", + "image/x-win-bitmap", + "image/x-windows-bmp", + "image/x-xbitmap" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsbmp, nsbmp_types, nsbmp_content_handler); diff --git a/content/handlers/image/bmp.h b/content/handlers/image/bmp.h new file mode 100644 index 000000000..f3b398584 --- /dev/null +++ b/content/handlers/image/bmp.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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/bmp (interface). + */ + +#ifndef _NETSURF_IMAGE_BMP_H_ +#define _NETSURF_IMAGE_BMP_H_ + +#include + +extern bmp_bitmap_callback_vt bmp_bitmap_callbacks; /** Only to be used by ICO code. */ + +nserror nsbmp_init(void); + +#endif diff --git a/content/handlers/image/gif.c b/content/handlers/image/gif.c new file mode 100644 index 000000000..d8d968e7d --- /dev/null +++ b/content/handlers/image/gif.c @@ -0,0 +1,455 @@ +/* + * 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/utils.h" +#include "utils/messages.h" +#include "utils/nsoption.h" +#include "content/content_protected.h" +#include "desktop/gui_misc.h" +#include "desktop/gui_internal.h" + +#include "image.h" +#include "bitmap.h" +#include "gif.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)] */ +} 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 guit->bitmap->create(width, height, BITMAP_NEW); +} + + +static nserror nsgif_create_gif_data(nsgif_content *c) +{ + union content_msg_data msg_data; + gif_bitmap_callback_vt gif_bitmap_callbacks = { + .bitmap_create = nsgif_bitmap_create, + .bitmap_destroy = guit->bitmap->destroy, + .bitmap_get_buffer = guit->bitmap->get_buffer, + .bitmap_set_opaque = guit->bitmap->set_opaque, + .bitmap_test_opaque = guit->bitmap->test_opaque, + .bitmap_modified = guit->bitmap->modified + }; + + /* 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 = calloc(1, sizeof(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) { + free(result); + return error; + } + + error = nsgif_create_gif_data(result); + if (error != NSERROR_OK) { + 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 < nsoption_int(minimum_gif_delay)) + delay = nsoption_int(minimum_gif_delay); + guit->misc->schedule(delay * 10, nsgif_animate, gif); + } + + if ((!nsoption_bool(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 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; + + /* 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, height and calculate size */ + c->width = gif->gif->width; + c->height = gif->gif->height; + c->size += (gif->gif->width * gif->gif->height * 4) + 16 + 44; + + /* set title text */ + title = messages_get_buff("GIFTitle", + nsurl_access_leaf(llcache_handle_get_url(c->llcache)), + c->width, c->height); + if (title != NULL) { + content__set_title(c, title); + free(title); + } + + /* Schedule the animation if we have one */ + gif->current_frame = 0; + if (gif->gif->frame_count_partial > 1) + guit->misc->schedule(gif->gif->frames[0].frame_delay * 10, + nsgif_animate, + c); + + /* Exit as a success */ + 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 gif The gif context to update. + * \return GIF_OK on success else apropriate error code. + */ +static gif_result nsgif_get_frame(nsgif_content *gif) +{ + int previous_frame, current_frame, frame; + gif_result res = GIF_OK; + + current_frame = gif->current_frame; + if (!nsoption_bool(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; + + if (gif->current_frame != gif->gif->decoded_frame) { + if (nsgif_get_frame(gif) != GIF_OK) { + return false; + } + } + + return image_bitmap_plot(gif->gif->frame_image, data, clip, ctx); +} + + +static void nsgif_destroy(struct content *c) +{ + nsgif_content *gif = (nsgif_content *) c; + + /* Free all the associated memory buffers */ + guit->misc->schedule(-1, 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 = calloc(1, sizeof(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_add_user(struct content *c) +{ + nsgif_content *gif = (nsgif_content *) c; + + /* Ensure this content has already been converted. + * If it hasn't, the animation will start at the conversion phase instead. */ + if (gif->gif == NULL) return; + + if (content_count_users(c) == 1) { + /* First user, and content already converted, so start the animation. */ + if (gif->gif->frame_count_partial > 1) { + guit->misc->schedule(gif->gif->frames[0].frame_delay * 10, + nsgif_animate, c); + } + } +} + +static void nsgif_remove_user(struct content *c) +{ + if (content_count_users(c) == 1) { + /* Last user is about to be removed from this content, so stop the animation. */ + guit->misc->schedule(-1, nsgif_animate, c); + } +} + +static void *nsgif_get_internal(const struct content *c, void *context) +{ + nsgif_content *gif = (nsgif_content *) c; + + if (gif->current_frame != gif->gif->decoded_frame) { + if (nsgif_get_frame(gif) != GIF_OK) + return NULL; + } + + return gif->gif->frame_image; +} + +static content_type nsgif_content_type(void) +{ + 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, + .add_user = nsgif_add_user, + .remove_user = nsgif_remove_user, + .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); diff --git a/content/handlers/image/gif.h b/content/handlers/image/gif.h new file mode 100644 index 000000000..a75821a96 --- /dev/null +++ b/content/handlers/image/gif.h @@ -0,0 +1,29 @@ +/* + * 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 (interface). + */ + +#ifndef _NETSURF_IMAGE_GIF_H_ +#define _NETSURF_IMAGE_GIF_H_ + +nserror nsgif_init(void); + +#endif diff --git a/content/handlers/image/ico.c b/content/handlers/image/ico.c new file mode 100644 index 000000000..d364fdff5 --- /dev/null +++ b/content/handlers/image/ico.c @@ -0,0 +1,300 @@ +/* + * Copyright 2006 Richard Wilson + * + * 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/ico (implementation) + */ + +#include +#include +#include + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "content/content_protected.h" +#include "desktop/gui_internal.h" + +#include "image.h" +#include "bitmap.h" +#include "ico.h" + +typedef struct nsico_content { + struct content base; + + struct ico_collection *ico; /** ICO collection data */ + +} nsico_content; + +/** + * Callback for libnsbmp; forwards the call to bitmap_create() + * + * \param width width of image in pixels + * \param height width of image in pixels + * \param bmp_state A flag word indicating the initial state + * \return an opaque struct bitmap, or NULL on memory exhaustion + */ +static void *nsico_bitmap_create(int width, int height, unsigned int bmp_state) +{ + unsigned int bitmap_state = BITMAP_NEW; + + /* set bitmap state based on bmp state */ + bitmap_state |= (bmp_state & BMP_OPAQUE) ? BITMAP_OPAQUE : 0; + bitmap_state |= (bmp_state & BMP_CLEAR_MEMORY) ? + BITMAP_CLEAR_MEMORY : 0; + + /* return the created bitmap */ + return guit->bitmap->create(width, height, bitmap_state); +} + +static nserror nsico_create_ico_data(nsico_content *c) +{ + union content_msg_data msg_data; + bmp_bitmap_callback_vt bmp_bitmap_callbacks = { + .bitmap_create = nsico_bitmap_create, + .bitmap_destroy = guit->bitmap->destroy, + .bitmap_get_buffer = guit->bitmap->get_buffer, + .bitmap_get_bpp = guit->bitmap->get_bpp + }; + + c->ico = calloc(sizeof(ico_collection), 1); + if (c->ico == NULL) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; + } + ico_collection_create(c->ico, &bmp_bitmap_callbacks); + return NSERROR_OK; +} + + +static nserror nsico_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) +{ + nsico_content *result; + nserror error; + + result = calloc(1, sizeof(nsico_content)); + if (result == NULL) + return NSERROR_NOMEM; + + error = content__init(&result->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(result); + return error; + } + + error = nsico_create_ico_data(result); + if (error != NSERROR_OK) { + free(result); + return error; + } + + *c = (struct content *) result; + + return NSERROR_OK; +} + + + +static bool nsico_convert(struct content *c) +{ + nsico_content *ico = (nsico_content *) c; + struct bmp_image *bmp; + bmp_result res; + union content_msg_data msg_data; + const char *data; + unsigned long size; + char *title; + + /* set the ico data */ + data = content__get_source_data(c, &size); + + /* analyse the ico */ + res = ico_analyse(ico->ico, size, (unsigned char *) data); + + switch (res) { + case BMP_OK: + break; + case BMP_INSUFFICIENT_MEMORY: + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + case BMP_INSUFFICIENT_DATA: + case BMP_DATA_ERROR: + msg_data.error = messages_get("BadICO"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + /* Store our content width, height and calculate size */ + c->width = ico->ico->width; + c->height = ico->ico->height; + c->size += (ico->ico->width * ico->ico->height * 4) + 16 + 44; + + /* set title text */ + title = messages_get_buff("ICOTitle", + nsurl_access_leaf(llcache_handle_get_url(c->llcache)), + c->width, c->height); + if (title != NULL) { + content__set_title(c, title); + free(title); + } + + /* select largest icon to ensure one can be selected */ + bmp = ico_find(ico->ico, 255, 255); + if (bmp == NULL) { + /* return error */ + LOG("Failed to select icon"); + return false; + } + + content_set_ready(c); + content_set_done(c); + + /* Done: update status bar */ + content_set_status(c, ""); + return true; +} + + +static bool nsico_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + nsico_content *ico = (nsico_content *)c; + struct bmp_image *bmp; + + /* select most appropriate sized icon for size */ + bmp = ico_find(ico->ico, data->width, data->height); + if (bmp == NULL) { + /* return error */ + LOG("Failed to select icon"); + return false; + } + + /* ensure its decided */ + if (bmp->decoded == false) { + if (bmp_decode(bmp) != BMP_OK) { + return false; + } else { + LOG("Decoding bitmap"); + guit->bitmap->modified(bmp->bitmap); + } + + } + + return image_bitmap_plot(bmp->bitmap, data, clip, ctx); +} + + +static void nsico_destroy(struct content *c) +{ + nsico_content *ico = (nsico_content *) c; + + ico_finalise(ico->ico); + free(ico->ico); +} + +static nserror nsico_clone(const struct content *old, struct content **newc) +{ + nsico_content *ico; + nserror error; + + ico = calloc(1, sizeof(nsico_content)); + if (ico == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, &ico->base); + if (error != NSERROR_OK) { + content_destroy(&ico->base); + return error; + } + + /* Simply replay creation and conversion */ + error = nsico_create_ico_data(ico); + if (error != NSERROR_OK) { + content_destroy(&ico->base); + return error; + } + + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (nsico_convert(&ico->base) == false) { + content_destroy(&ico->base); + return NSERROR_CLONE_FAILED; + } + } + + *newc = (struct content *) ico; + + return NSERROR_OK; +} + +static void *nsico_get_internal(const struct content *c, void *context) +{ + nsico_content *ico = (nsico_content *) c; + /* TODO: Pick best size for purpose. + * Currently assumes it's for a URL bar. */ + struct bmp_image *bmp; + + bmp = ico_find(ico->ico, 16, 16); + if (bmp == NULL) { + /* return error */ + LOG("Failed to select icon"); + return NULL; + } + + if (bmp->decoded == false) { + if (bmp_decode(bmp) != BMP_OK) { + return NULL; + } else { + guit->bitmap->modified(bmp->bitmap); + } + } + + return bmp->bitmap; +} + +static content_type nsico_content_type(void) +{ + return CONTENT_IMAGE; +} + +static const content_handler nsico_content_handler = { + .create = nsico_create, + .data_complete = nsico_convert, + .destroy = nsico_destroy, + .redraw = nsico_redraw, + .clone = nsico_clone, + .get_internal = nsico_get_internal, + .type = nsico_content_type, + .no_share = false, +}; + +static const char *nsico_types[] = { + "application/ico", + "application/x-ico", + "image/ico", + "image/vnd.microsoft.icon", + "image/x-icon" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsico, nsico_types, nsico_content_handler); diff --git a/content/handlers/image/ico.h b/content/handlers/image/ico.h new file mode 100644 index 000000000..21e9bdb6e --- /dev/null +++ b/content/handlers/image/ico.h @@ -0,0 +1,28 @@ +/* + * Copyright 2006 Richard Wilson + * + * 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/ico (interface). + */ + +#ifndef _NETSURF_IMAGE_ICO_H_ +#define _NETSURF_IMAGE_ICO_H_ + +nserror nsico_init(void); + +#endif diff --git a/content/handlers/image/image.c b/content/handlers/image/image.c new file mode 100644 index 000000000..d1743697d --- /dev/null +++ b/content/handlers/image/image.c @@ -0,0 +1,159 @@ +/* + * Copyright 2011 John-Mark Bell + * + * 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 . + */ + +#include +#include + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "content/content.h" +#include "desktop/plotters.h" +#include "desktop/gui_internal.h" + +#include "bitmap.h" +#include "bmp.h" +#include "gif.h" +#include "ico.h" +#include "jpeg.h" +#include "nssprite.h" +#include "png.h" +#include "rsvg.h" +#include "svg.h" +#include "image.h" + +/** + * Initialise image content handlers + * + * \return NSERROR_OK on success, appropriate error otherwise. + */ +nserror image_init(void) +{ + nserror error = NSERROR_OK; + +#ifdef WITH_BMP + error = nsbmp_init(); + if (error != NSERROR_OK) + return error; +#endif + +#ifdef WITH_GIF + error = nsgif_init(); + if (error != NSERROR_OK) + return error; +#endif + +#ifdef WITH_BMP + error = nsico_init(); + if (error != NSERROR_OK) + return error; +#endif + +#ifdef WITH_JPEG + error = nsjpeg_init(); + if (error != NSERROR_OK) + return error; +#endif + +#ifdef WITH_PNG + error = nspng_init(); + if (error != NSERROR_OK) + return error; +#endif + +#ifdef WITH_NSSPRITE + error = nssprite_init(); + if (error != NSERROR_OK) + return error; +#endif + + /* Prefer rsvg over libsvgtiny for svgs */ +#ifdef WITH_NS_SVG + error = svg_init(); + if (error != NSERROR_OK) + return error; +#endif +#ifdef WITH_RSVG + error = nsrsvg_init(); + if (error != NSERROR_OK) + return error; +#endif + + return error; +} + + +bool image_bitmap_plot(struct bitmap *bitmap, + struct content_redraw_data *data, + const struct rect *clip, + const struct redraw_context *ctx) +{ + bitmap_flags_t flags = BITMAPF_NONE; + + int width; + int height; + unsigned char *pixel; + plot_style_t fill_style; + struct rect area; + + width = guit->bitmap->get_width(bitmap); + if (width == 1) { + height = guit->bitmap->get_height(bitmap); + if (height == 1) { + /* optimise 1x1 bitmap plot */ + pixel = guit->bitmap->get_buffer(bitmap); + fill_style.fill_colour = pixel_to_colour(pixel); + + if (guit->bitmap->get_opaque(bitmap) || + ((fill_style.fill_colour & 0xff000000) == 0xff000000)) { + + area = *clip; + + if (data->repeat_x != true) { + area.x0 = data->x; + area.x1 = data->x + data->width; + } + + if (data->repeat_y != true) { + area.y0 = data->y; + area.y1 = data->y + data->height; + } + + fill_style.stroke_type = PLOT_OP_TYPE_NONE; + fill_style.fill_type = PLOT_OP_TYPE_SOLID; + + return ctx->plot->rectangle(area.x0, area.y0, + area.x1, area.y1, + &fill_style); + + } else if ((fill_style.fill_colour & 0xff000000) == 0) { + /* transparent pixel used as spacer, skip it */ + return true; + } + } + } + + /* do the plot */ + 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, + bitmap, data->background_colour, flags); +} diff --git a/content/handlers/image/image.h b/content/handlers/image/image.h new file mode 100644 index 000000000..eb9482583 --- /dev/null +++ b/content/handlers/image/image.h @@ -0,0 +1,43 @@ +/* + * Copyright 2011 John-Mark Bell + * + * 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 + * Initialisation/finalisation of image handlers. + */ + +#ifndef NETSURF_IMAGE_IMAGE_H_ +#define NETSURF_IMAGE_IMAGE_H_ + +#include "utils/errors.h" + +/** Initialise the content handlers for image types. + */ +nserror image_init(void); + +/** Common image content handler bitmap plot call. + * + * This plots the specified bitmap controlled by the redraw context + * and specific content redraw data. It is a helper specifically + * provided for image content handlers redraw callback. + */ +bool image_bitmap_plot(struct bitmap *bitmap, + struct content_redraw_data *data, + const struct rect *clip, + const struct redraw_context *ctx); + +#endif diff --git a/content/handlers/image/image_cache.c b/content/handlers/image/image_cache.c new file mode 100644 index 000000000..49370ad6e --- /dev/null +++ b/content/handlers/image/image_cache.c @@ -0,0 +1,797 @@ +/* + * Copyright 2011 Vincent Sanders + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "utils/utils.h" +#include "utils/log.h" +#include "content/content_protected.h" +#include "desktop/gui_misc.h" +#include "desktop/gui_internal.h" + +#include "bitmap.h" +#include "image_cache.h" +#include "image.h" + +/** Age of an entry within the cache + * + * type deffed away so it can be readily changed later perhaps to a + * wallclock time structure. + */ +typedef unsigned int cache_age; + +/** Image cache entry + */ +struct image_cache_entry_s { + struct image_cache_entry_s *next; /* next cache entry in list */ + struct image_cache_entry_s *prev; /* previous cache entry in list */ + + struct content *content; /** content is used as a key */ + struct bitmap *bitmap; /** associated bitmap entry */ + /** Conversion routine */ + image_cache_convert_fn *convert; + + /* Statistics for replacement algorithm */ + + unsigned int redraw_count; /**< number of times object has been drawn */ + cache_age redraw_age; /**< Age of last redraw */ + size_t bitmap_size; /**< size if storage occupied by bitmap */ + cache_age bitmap_age; /**< Age of last conversion to a bitmap by cache*/ + + int conversion_count; /**< Number of times image has been converted */ +}; + +/** Current state of the cache. + * + * Global state of the cache. entries "age" is determined based on a + * monotonically incrementing operation count. This avoids issues with + * using wall clock time while allowing the LRU algorithm to work + * sensibly. + */ +struct image_cache_s { + /** Cache parameters */ + struct image_cache_parameters params; + + /** The "age" of the current operation */ + cache_age current_age; + + /* The objects the cache holds */ + struct image_cache_entry_s *entries; + + + /* Statistics for management algorithm */ + + /** total size of bitmaps currently allocated */ + size_t total_bitmap_size; + + /** Total count of bitmaps currently allocated */ + int bitmap_count; + + /** Maximum size of bitmaps allocated at any one time */ + size_t max_bitmap_size; + /** The number of objects when maximum bitmap usage occoured */ + int max_bitmap_size_count; + + /** Maximum count of bitmaps allocated at any one time */ + int max_bitmap_count; + /** The size of the bitmaps when the max count occoured */ + size_t max_bitmap_count_size; + + /** Bitmap was not available at plot time required conversion */ + int miss_count; + uint64_t miss_size; + /** Bitmap was available at plot time required no conversion */ + int hit_count; + uint64_t hit_size; + /** Bitmap was not available at plot time and required + * conversion which failed. + */ + int fail_count; + uint64_t fail_size; + + /* Cache entry freed without ever being redrawn */ + int total_unrendered; + /** Bitmap was available but never required - wasted conversions */ + int specultive_miss_count; + + /** Total number of additional (after the first) conversions */ + int total_extra_conversions; + /** counts total number of images with more than one conversion */ + int total_extra_conversions_count; + + /** Bitmap with most conversions was converted this many times */ + int peak_conversions; + /** Size of bitmap with most conversions */ + unsigned int peak_conversions_size; +}; + +/** image cache state */ +static struct image_cache_s *image_cache = NULL; + + +/** Find the nth cache entry + */ +static struct image_cache_entry_s *image_cache__findn(int entryn) +{ + struct image_cache_entry_s *found; + + found = image_cache->entries; + while ((found != NULL) && (entryn > 0)) { + entryn--; + found = found->next; + } + return found; +} + +/** Find the cache entry for a content + */ +static struct image_cache_entry_s *image_cache__find(const struct content *c) +{ + struct image_cache_entry_s *found; + + found = image_cache->entries; + while ((found != NULL) && (found->content != c)) { + found = found->next; + } + return found; +} + +static void image_cache_stats_bitmap_add(struct image_cache_entry_s *centry) +{ + centry->bitmap_age = image_cache->current_age; + centry->conversion_count++; + + image_cache->total_bitmap_size += centry->bitmap_size; + image_cache->bitmap_count++; + + if (image_cache->total_bitmap_size > image_cache->max_bitmap_size) { + image_cache->max_bitmap_size = image_cache->total_bitmap_size; + image_cache->max_bitmap_size_count = image_cache->bitmap_count; + + } + + if (image_cache->bitmap_count > image_cache->max_bitmap_count) { + image_cache->max_bitmap_count = image_cache->bitmap_count; + image_cache->max_bitmap_count_size = image_cache->total_bitmap_size; + } + + if (centry->conversion_count == 2) { + image_cache->total_extra_conversions_count++; + } + + if (centry->conversion_count > 1) { + image_cache->total_extra_conversions++; + } + + if ((centry->conversion_count > image_cache->peak_conversions) || + (centry->conversion_count == image_cache->peak_conversions && + centry->bitmap_size > image_cache->peak_conversions_size)) { + image_cache->peak_conversions = centry->conversion_count; + image_cache->peak_conversions_size = centry->bitmap_size; + } +} + +static void image_cache__link(struct image_cache_entry_s *centry) +{ + centry->next = image_cache->entries; + centry->prev = NULL; + if (centry->next != NULL) { + centry->next->prev = centry; + } + image_cache->entries = centry; +} + +static void image_cache__unlink(struct image_cache_entry_s *centry) +{ + /* unlink entry */ + if (centry->prev == NULL) { + /* first in list */ + if (centry->next != NULL) { + centry->next->prev = centry->prev; + image_cache->entries = centry->next; + } else { + /* empty list */ + image_cache->entries = NULL; + } + } else { + centry->prev->next = centry->next; + + if (centry->next != NULL) { + centry->next->prev = centry->prev; + } + } +} + +static void image_cache__free_bitmap(struct image_cache_entry_s *centry) +{ + if (centry->bitmap != NULL) { +#ifdef IMAGE_CACHE_VERBOSE + LOG("Freeing bitmap %p size %d age %d redraw count %d", centry->bitmap, centry->bitmap_size, image_cache->current_age - centry->bitmap_age, centry->redraw_count); +#endif + guit->bitmap->destroy(centry->bitmap); + centry->bitmap = NULL; + image_cache->total_bitmap_size -= centry->bitmap_size; + image_cache->bitmap_count--; + if (centry->redraw_count == 0) { + image_cache->specultive_miss_count++; + } + } + +} + +/* free cache entry */ +static void image_cache__free_entry(struct image_cache_entry_s *centry) +{ +#ifdef IMAGE_CACHE_VERBOSE + LOG("freeing %p ", centry); +#endif + + if (centry->redraw_count == 0) { + image_cache->total_unrendered++; + } + + image_cache__free_bitmap(centry); + + image_cache__unlink(centry); + + free(centry); +} + +/** Cache cleaner */ +static void image_cache__clean(struct image_cache_s *icache) +{ + struct image_cache_entry_s *centry = icache->entries; + + while (centry != NULL) { + if ((icache->current_age - centry->redraw_age) > + icache->params.bg_clean_time) { + /* only consider older entries, avoids active entries */ + if ((icache->total_bitmap_size > + (icache->params.limit - icache->params.hysteresis)) && + (rand() > (RAND_MAX / 2))) { + image_cache__free_bitmap(centry); + } + } + centry=centry->next; + } +} + +/** Cache background scheduled callback. */ +static void image_cache__background_update(void *p) +{ + struct image_cache_s *icache = p; + + /* increment current cache age */ + icache->current_age += icache->params.bg_clean_time; + +#ifdef IMAGE_CACHE_VERBOSE + LOG("Cache age %ds", icache->current_age / 1000); +#endif + + image_cache__clean(icache); + + guit->misc->schedule(icache->params.bg_clean_time, + image_cache__background_update, + icache); +} + +/* exported interface documented in image_cache.h */ +struct bitmap *image_cache_get_bitmap(const struct content *c) +{ + struct image_cache_entry_s *centry; + + centry = image_cache__find(c); + if (centry == NULL) { + return NULL; + } + + if (centry->bitmap == NULL) { + if (centry->convert != NULL) { + centry->bitmap = centry->convert(centry->content); + } + + if (centry->bitmap != NULL) { + image_cache_stats_bitmap_add(centry); + image_cache->miss_count++; + image_cache->miss_size += centry->bitmap_size; + } else { + image_cache->fail_count++; + image_cache->fail_size += centry->bitmap_size; + } + } else { + image_cache->hit_count++; + image_cache->hit_size += centry->bitmap_size; + } + + return centry->bitmap; +} + +/* exported interface documented in image_cache.h */ +bool image_cache_speculate(struct content *c) +{ + bool decision = false; + + /* If the cache is below its target usage and the bitmap is + * small enough speculate. + */ + if ((image_cache->total_bitmap_size < image_cache->params.limit) && + (c->size <= image_cache->params.speculative_small)) { +#ifdef IMAGE_CACHE_VERBOSE + LOG("content size (%d) is smaller than minimum (%d)", c->size, SPECULATE_SMALL); +#endif + decision = true; + } + +#ifdef IMAGE_CACHE_VERBOSE + LOG("returning %d", decision); +#endif + return decision; +} + +/* exported interface documented in image_cache.h */ +struct bitmap *image_cache_find_bitmap(struct content *c) +{ + struct image_cache_entry_s *centry; + + centry = image_cache__find(c); + if (centry == NULL) { + return NULL; + } + + return centry->bitmap; +} + +/* exported interface documented in image_cache.h */ +nserror +image_cache_init(const struct image_cache_parameters *image_cache_parameters) +{ + image_cache = calloc(1, sizeof(struct image_cache_s)); + if (image_cache == NULL) { + return NSERROR_NOMEM; + } + + image_cache->params = *image_cache_parameters; + + guit->misc->schedule(image_cache->params.bg_clean_time, + image_cache__background_update, + image_cache); + + LOG("Image cache initilised with a limit of %" PRIsizet " hysteresis of %"PRIsizet, + image_cache->params.limit, image_cache->params.hysteresis); + + return NSERROR_OK; +} + +/* exported interface documented in image_cache.h */ +nserror image_cache_fini(void) +{ + unsigned int op_count; + + guit->misc->schedule(-1, image_cache__background_update, image_cache); + + LOG("Size at finish %" PRIsizet " (in %d)", + image_cache->total_bitmap_size, image_cache->bitmap_count); + + while (image_cache->entries != NULL) { + image_cache__free_entry(image_cache->entries); + } + + op_count = image_cache->hit_count + + image_cache->miss_count + + image_cache->fail_count; + + LOG("Age %ds", image_cache->current_age / 1000); + LOG("Peak size %" PRIsizet " (in %d)", + image_cache->max_bitmap_size, image_cache->max_bitmap_size_count); + LOG("Peak image count %d (size %" PRIsizet ")", + image_cache->max_bitmap_count, image_cache->max_bitmap_count_size); + + if (op_count > 0) { + uint64_t op_size; + + op_size = image_cache->hit_size + + image_cache->miss_size + + image_cache->fail_size; + + LOG("Cache total/hit/miss/fail (counts) %d/%d/%d/%d (100%%/%d%%/%d%%/%d%%)", + op_count, + image_cache->hit_count, + image_cache->miss_count, + image_cache->fail_count, + (image_cache->hit_count * 100) / op_count, + (image_cache->miss_count * 100) / op_count, + (image_cache->fail_count * 100) / op_count); + LOG("Cache total/hit/miss/fail (size) %"PRIu64"/%"PRIu64"/%"PRIu64"/%"PRIu64" (100%%/%"PRId64"%%/%"PRId64"%%/%"PRId64"%%)", + op_size, + image_cache->hit_size, + image_cache->miss_size, + image_cache->fail_size, + (image_cache->hit_size * 100) / op_size, + (image_cache->miss_size * 100) / op_size, + (image_cache->fail_size * 100) / op_size); + } + + LOG("Total images never rendered: %d (includes %d that were converted)", + image_cache->total_unrendered, + image_cache->specultive_miss_count); + + LOG("Total number of excessive conversions: %d (from %d images converted more than once)", + image_cache->total_extra_conversions, + image_cache->total_extra_conversions_count); + + LOG("Bitmap of size %d had most (%d) conversions", + image_cache->peak_conversions_size, + image_cache->peak_conversions); + + free(image_cache); + + return NSERROR_OK; +} + +/* exported interface documented in image_cache.h */ +nserror image_cache_add(struct content *content, + struct bitmap *bitmap, + image_cache_convert_fn *convert) +{ + struct image_cache_entry_s *centry; + + /* bump the cache age by a ms to ensure multiple items are not + * added at exactly the same time + */ + image_cache->current_age++; + + centry = image_cache__find(content); + if (centry == NULL) { + /* new cache entry, content not previously added */ + centry = calloc(1, sizeof(struct image_cache_entry_s)); + if (centry == NULL) { + return NSERROR_NOMEM; + } + image_cache__link(centry); + centry->content = content; + + centry->bitmap_size = content->width * content->height * 4; + } + + LOG("centry %p, content %p, bitmap %p", centry, content, bitmap); + + centry->convert = convert; + + /* set bitmap entry if one is passed, free extant one if present */ + if (bitmap != NULL) { + if (centry->bitmap != NULL) { + guit->bitmap->destroy(centry->bitmap); + } else { + image_cache_stats_bitmap_add(centry); + } + centry->bitmap = bitmap; + } else { + /* no bitmap, check to see if we should speculatively convert */ + if ((centry->convert != NULL) && + (image_cache_speculate(content) == true)) { + centry->bitmap = centry->convert(centry->content); + + if (centry->bitmap != NULL) { + image_cache_stats_bitmap_add(centry); + } else { + image_cache->fail_count++; + } + } + } + + + + return NSERROR_OK; +} + +/* exported interface documented in image_cache.h */ +nserror image_cache_remove(struct content *content) +{ + struct image_cache_entry_s *centry; + + /* get the cache entry */ + centry = image_cache__find(content); + if (centry == NULL) { + LOG("Could not find cache entry for content (%p)", content); + return NSERROR_NOT_FOUND; + } + + image_cache__free_entry(centry); + + return NSERROR_OK; +} + +/* exported interface documented in image_cache.h */ +int image_cache_snsummaryf(char *string, size_t size, const char *fmt) +{ + size_t slen = 0; /* current output string length */ + int fmtc = 0; /* current index into format string */ + bool pct; + unsigned int op_count; + uint64_t op_size; + + op_count = image_cache->hit_count + + image_cache->miss_count + + image_cache->fail_count; + + op_size = image_cache->hit_size + + image_cache->miss_size + + image_cache->fail_size; + + while((slen < size) && (fmt[fmtc] != 0)) { + if (fmt[fmtc] == '%') { + fmtc++; + + /* check for percentage modifier */ + if (fmt[fmtc] == 'p') { + fmtc++; + pct = true; + } else { + pct = false; + } + +#define FMTCHR(chr,fmt,var) case chr : \ +slen += snprintf(string + slen, size - slen, "%"fmt, image_cache->var); break + +#define FMTPCHR(chr,fmt,var,div) \ +case chr : \ + if (pct) { \ + if (div > 0) { \ + slen += snprintf(string + slen, size - slen, "%"PRId64, (uint64_t)((image_cache->var * 100) / div)); \ + } else { \ + slen += snprintf(string + slen, size - slen, "100"); \ + } \ + } else { \ + slen += snprintf(string + slen, size - slen, "%"fmt, image_cache->var); \ + } break + + + switch (fmt[fmtc]) { + case '%': + string[slen] = '%'; + slen++; + break; + + FMTCHR('a', PRIssizet, params.limit); + FMTCHR('b', PRIssizet, params.hysteresis); + FMTCHR('c', PRIssizet, total_bitmap_size); + FMTCHR('d', "d", bitmap_count); + FMTCHR('e', "d", current_age / 1000); + FMTCHR('f', PRIssizet, max_bitmap_size); + FMTCHR('g', "d", max_bitmap_size_count); + FMTCHR('h', "d", max_bitmap_count); + FMTCHR('i', PRIssizet, max_bitmap_count_size); + + + case 'j': + slen += snprintf(string + slen, size - slen, + "%d", pct?100:op_count); + break; + + FMTPCHR('k', "d", hit_count, op_count); + FMTPCHR('l', "d", miss_count, op_count); + FMTPCHR('m', "d", fail_count, op_count); + + case 'n': + slen += snprintf(string + slen, size - slen, + "%"PRId64, pct?100:op_size); + break; + + FMTPCHR('o', PRId64, hit_size, op_size); + FMTPCHR('q', PRId64, miss_size, op_size); + FMTPCHR('r', PRId64, fail_size, op_size); + + FMTCHR('s', "d", total_unrendered); + FMTCHR('t', "d", specultive_miss_count); + FMTCHR('u', "d", total_extra_conversions); + FMTCHR('v', "d", total_extra_conversions_count); + FMTCHR('w', "d", peak_conversions_size); + FMTCHR('x', "d", peak_conversions); + + + } +#undef FMTCHR +#undef FMTPCHR + + fmtc++; + } else { + string[slen] = fmt[fmtc]; + slen++; + fmtc++; + } + } + + /* Ensure that we NUL-terminate the output */ + string[min(slen, size - 1)] = '\0'; + + return slen; +} + +/* exported interface documented in image_cache.h */ +int image_cache_snentryf(char *string, size_t size, unsigned int entryn, + const char *fmt) +{ + struct image_cache_entry_s *centry; + size_t slen = 0; /* current output string length */ + int fmtc = 0; /* current index into format string */ + lwc_string *origin; /* current entry's origin */ + + centry = image_cache__findn(entryn); + if (centry == NULL) + return -1; + + while((slen < size) && (fmt[fmtc] != 0)) { + if (fmt[fmtc] == '%') { + fmtc++; + switch (fmt[fmtc]) { + case 'e': + slen += snprintf(string + slen, size - slen, + "%d", entryn); + break; + + case 'r': + slen += snprintf(string + slen, size - slen, + "%u", centry->redraw_count); + break; + + case 'a': + slen += snprintf(string + slen, size - slen, + "%.2f", (float)((image_cache->current_age - centry->redraw_age)) / 1000); + break; + + + case 'c': + slen += snprintf(string + slen, size - slen, + "%d", centry->conversion_count); + break; + + case 'g': + slen += snprintf(string + slen, size - slen, + "%.2f", (float)((image_cache->current_age - centry->bitmap_age)) / 1000); + break; + + case 'k': + slen += snprintf(string + slen, size - slen, + "%p", centry->content); + break; + + case 'U': + slen += snprintf(string + slen, size - slen, + "%s", nsurl_access(llcache_handle_get_url(centry->content->llcache))); + break; + + case 'o': + if (nsurl_has_component(llcache_handle_get_url( + centry->content->llcache), + NSURL_HOST)) { + origin = nsurl_get_component( + llcache_handle_get_url( + centry->content-> + llcache), + NSURL_HOST); + + slen += snprintf(string + slen, + size - slen, "%s", + lwc_string_data( + origin)); + + lwc_string_unref(origin); + } else { + slen += snprintf(string + slen, + size - slen, "%s", + "localhost"); + } + break; + + case 's': + if (centry->bitmap != NULL) { + slen += snprintf(string + slen, + size - slen, + "%" PRIssizet, + centry->bitmap_size); + } else { + slen += snprintf(string + slen, + size - slen, + "0"); + } + break; + } + fmtc++; + } else { + string[slen] = fmt[fmtc]; + slen++; + fmtc++; + } + } + + /* Ensure that we NUL-terminate the output */ + string[min(slen, size - 1)] = '\0'; + + return slen; +} + + +/* exported interface documented in image_cache.h */ +bool image_cache_redraw(struct content *c, + struct content_redraw_data *data, + const struct rect *clip, + const struct redraw_context *ctx) +{ + struct image_cache_entry_s *centry; + + /* get the cache entry */ + centry = image_cache__find(c); + if (centry == NULL) { + LOG("Could not find cache entry for content (%p)", c); + return false; + } + + if (centry->bitmap == NULL) { + if (centry->convert != NULL) { + centry->bitmap = centry->convert(centry->content); + } + + if (centry->bitmap != NULL) { + image_cache_stats_bitmap_add(centry); + image_cache->miss_count++; + image_cache->miss_size += centry->bitmap_size; + } else { + image_cache->fail_count++; + image_cache->fail_size += centry->bitmap_size; + return false; + } + } else { + image_cache->hit_count++; + image_cache->hit_size += centry->bitmap_size; + } + + + /* update statistics */ + centry->redraw_count++; + centry->redraw_age = image_cache->current_age; + + return image_bitmap_plot(centry->bitmap, data, clip, ctx); +} + +void image_cache_destroy(struct content *content) +{ + struct image_cache_entry_s *centry; + + /* get the cache entry */ + centry = image_cache__find(content); + if (centry == NULL) { + LOG("Could not find cache entry for content (%p)", content); + } else { + image_cache__free_entry(centry); + } +} + +void *image_cache_get_internal(const struct content *c, void *context) +{ + return image_cache_get_bitmap(c); +} + +content_type image_cache_content_type(void) +{ + return CONTENT_IMAGE; +} diff --git a/content/handlers/image/image_cache.h b/content/handlers/image/image_cache.h new file mode 100644 index 000000000..2f1a5caee --- /dev/null +++ b/content/handlers/image/image_cache.h @@ -0,0 +1,189 @@ +/* + * Copyright 2011 John-Mark Bell + * + * 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 + * The image content handler intermediate image cache. + * + * This cache allows netsurf to use a generic intermediate bitmap + * format without keeping the + * intermediate representation in memory. + * + * The bitmap structure is opaque to the rest of netsurf and is + * controlled by the platform-specific code (see image/bitmap.h for + * detials). All image content handlers convert into this format and + * pass it to the plot functions for display, + * + * This cache maintains a link between the underlying original content + * and the intermediate representation. It is intended to be flexable + * and either manage the bitmap plotting completely or give the image + * content handler complete control. + */ + +#ifndef NETSURF_IMAGE_IMAGE_CACHE_H_ +#define NETSURF_IMAGE_IMAGE_CACHE_H_ + +#include "utils/errors.h" + +struct content_redraw_data; +struct redraw_context; + +typedef struct bitmap * (image_cache_convert_fn) (struct content *content); + +struct image_cache_parameters { + /** How frequently the background cache clean process is run (ms) */ + unsigned int bg_clean_time; + + /** The target upper bound for the image cache size */ + size_t limit; + + /** The hysteresis allowed round the target size */ + size_t hysteresis; + + /** The speculative conversion "small" size */ + size_t speculative_small; +}; + +/** Initialise the image cache + * + * @param image_cache_parameters The control parameters for the image cache + */ +nserror image_cache_init(const struct image_cache_parameters *image_cache_parameters); +nserror image_cache_fini(void); + +/** adds an image content to be cached. + * + * @param content The content handle used as a key + * @param bitmap A bitmap representing the already converted content or NULL. + * @param convert A function pointer to convert the content into a bitmap or NULL. + * @return A netsurf error code. + */ +nserror image_cache_add(struct content *content, + struct bitmap *bitmap, + image_cache_convert_fn *convert); + +nserror image_cache_remove(struct content *content); + + +/** Obtain a bitmap from a content converting from source if neccessary. */ +struct bitmap *image_cache_get_bitmap(const struct content *c); + +/** Obtain a bitmap from a content with no conversion */ +struct bitmap *image_cache_find_bitmap(struct content *c); + +/** Decide if a content should be speculatively converted. + * + * This allows for image content handlers to ask the cache if a bitmap + * should be generated before it is added to the cache. This is the + * same decision logic used to decide to perform an immediate + * conversion when a content is initially added to the cache. + * + * @param c The content to be considered. + * @return true if a speculative conversion is desired false otherwise. + */ +bool image_cache_speculate(struct content *c); + +/** + * Fill a buffer with information about a cache entry using a format. + * + * The format string is copied into the output buffer with the + * following replaced: + * %e - The entry number + * %k - The content key + * %r - The number of redraws of this bitmap + * %c - The number of times this bitmap has been converted + * %s - The size of the current bitmap allocation + * + * \param string The buffer in which to place the results. + * \param size The size of the string buffer. + * \param entryn The opaque entry number. + * \param fmt The format string. + * \return The number of bytes written to \a string or -1 on error + */ +int image_cache_snentryf(char *string, size_t size, unsigned int entryn, + const char *fmt); + +/** + * Fill a buffer with information about the image cache using a format. + * + * The format string is copied into the output buffer with the + * following replaced: + * + * a Configured cache limit size + * b Configured cache hysteresis size + * c Current caches total consumed size + * d Number of images currently in the cache + * e The age of the cache + * f The largest amount of space the cache has occupied since initialisation + * g The number of objetcs when the cache was at its largest + * h The largest number of images in the cache since initialisation + * i The size of the cache when the largest number of objects occoured + * j The total number of read operations performed on the cache + * k The total number of read operations satisfied from the cache without + * conversion. + * l The total number of read operations satisfied from the cache which + * required a conversion. + * m The total number of read operations which could not be sucessfully + * returned. ie. not available in cache and conversion failed. + * n The total size of read operations performed on the cache + * o The total size of read operations satisfied from the cache without + * conversion. + * q The total size of read operations satisfied from the cache which + * required a conversion. + * r The total size of read operations which could not be sucessfully + * returned. ie. not available in cache and conversion failed. + * s The number of images which were placed in the cache but never read. + * t The number of images that were converted on insertion into the cache which were subsequently never used. + * u The number of times an image was converted after the first + * v The number of images that had extra conversions performed. + * w Size of the image that was converted (read missed cache) highest number + * of times. + * x The number of times the image that was converted (read missed cache) + * highest number of times. + * + * format modifiers: + * A p before the value modifies the replacement to be a percentage. + * + * + * \param string The buffer in which to place the results. + * \param size The size of the string buffer. + * \param fmt The format string. + * \return The number of bytes written to \a string or -1 on error + */ + +int image_cache_snsummaryf(char *string, size_t size, const char *fmt); + +/********* Image content handler generic cache callbacks ************/ + +/** Generic content redraw callback + * + * May be used by image content handlers as their redraw + * callback. Performs all neccissary cache lookups and conversions and + * calls the bitmap plot function in the redraw context. + */ +bool image_cache_redraw(struct content *c, + struct content_redraw_data *data, + const struct rect *clip, + const struct redraw_context *ctx); + +void image_cache_destroy(struct content *c); + +void *image_cache_get_internal(const struct content *c, void *context); + +content_type image_cache_content_type(void); + +#endif diff --git a/content/handlers/image/jpeg.c b/content/handlers/image/jpeg.c new file mode 100644 index 000000000..b5eade306 --- /dev/null +++ b/content/handlers/image/jpeg.c @@ -0,0 +1,393 @@ +/* + * Copyright 2004 James Bursa + * Copyright 2004 John M Bell + * + * 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/jpeg (implementation). + * + * This implementation uses the IJG JPEG library. + */ + +#include +#include +#include + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "content/content_protected.h" +#include "desktop/gui_internal.h" + +#include "image_cache.h" +#include "bitmap.h" + +#define JPEG_INTERNAL_OPTIONS +#include "jpeglib.h" +#include "jpeg.h" + +/** absolute minimum size of a jpeg below which it is not even worth + * trying to read header data + */ +#define MIN_JPEG_SIZE 20 + +#ifdef riscos +/* We prefer the library to be configured with these options to save + * copying data during decoding. */ +#if RGB_RED != 0 || RGB_GREEN != 1 || RGB_BLUE != 2 || RGB_PIXELSIZE != 4 +#warning JPEG library not optimally configured. Decoding will be slower. +#endif +/* but we don't care if we're not on RISC OS */ +#endif + +static char nsjpeg_error_buffer[JMSG_LENGTH_MAX]; + +static unsigned char nsjpeg_eoi[] = { 0xff, JPEG_EOI }; + +/** + * Content create entry point. + */ +static nserror nsjpeg_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) +{ + struct content *jpeg; + nserror error; + + jpeg = calloc(1, sizeof(struct content)); + if (jpeg == NULL) + return NSERROR_NOMEM; + + error = content__init(jpeg, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(jpeg); + return error; + } + + *c = jpeg; + + return NSERROR_OK; +} + +/** + * JPEG data source manager: initialize source. + */ +static void nsjpeg_init_source(j_decompress_ptr cinfo) +{ +} + + +/** + * JPEG data source manager: fill the input buffer. + * + * This can only occur if the JPEG data was truncated or corrupted. Insert a + * fake EOI marker to allow the decompressor to output as much as possible. + */ +static boolean nsjpeg_fill_input_buffer(j_decompress_ptr cinfo) +{ + cinfo->src->next_input_byte = nsjpeg_eoi; + cinfo->src->bytes_in_buffer = 2; + return TRUE; +} + + +/** + * JPEG data source manager: skip num_bytes worth of data. + */ + +static void nsjpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + if ((long) cinfo->src->bytes_in_buffer < num_bytes) { + cinfo->src->next_input_byte = 0; + cinfo->src->bytes_in_buffer = 0; + } else { + cinfo->src->next_input_byte += num_bytes; + cinfo->src->bytes_in_buffer -= num_bytes; + } +} + + +/** + * JPEG data source manager: terminate source. + */ +static void nsjpeg_term_source(j_decompress_ptr cinfo) +{ +} + + +/** + * Error output handler for JPEG library. + * + * This logs to NetSurf log instead of stderr. + * Warnings only - fatal errors are trapped by nsjpeg_error_exit + * and do not call the output handler. + */ +static void nsjpeg_error_log(j_common_ptr cinfo) +{ + cinfo->err->format_message(cinfo, nsjpeg_error_buffer); + LOG("%s", nsjpeg_error_buffer); +} + + +/** + * Fatal error handler for JPEG library. + * + * This prevents jpeglib calling exit() on a fatal error. + */ +static void nsjpeg_error_exit(j_common_ptr cinfo) +{ + jmp_buf *setjmp_buffer = (jmp_buf *) cinfo->client_data; + + cinfo->err->format_message(cinfo, nsjpeg_error_buffer); + LOG("%s", nsjpeg_error_buffer); + + longjmp(*setjmp_buffer, 1); +} + +static struct bitmap * +jpeg_cache_convert(struct content *c) +{ + uint8_t *source_data; /* Jpeg source data */ + unsigned long source_size; /* length of Jpeg source data */ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + jmp_buf setjmp_buffer; + unsigned int height; + unsigned int width; + struct bitmap * volatile bitmap = NULL; + uint8_t * volatile pixels = NULL; + size_t rowstride; + struct jpeg_source_mgr source_mgr = { + 0, + 0, + nsjpeg_init_source, + nsjpeg_fill_input_buffer, + nsjpeg_skip_input_data, + jpeg_resync_to_restart, + nsjpeg_term_source }; + + /* obtain jpeg source data and perfom minimal sanity checks */ + source_data = (uint8_t *)content__get_source_data(c, &source_size); + + if ((source_data == NULL) || + (source_size < MIN_JPEG_SIZE)) { + return NULL; + } + + /* setup a JPEG library error handler */ + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = nsjpeg_error_exit; + jerr.output_message = nsjpeg_error_log; + + /* handler for fatal errors during decompression */ + if (setjmp(setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return bitmap; + } + + jpeg_create_decompress(&cinfo); + cinfo.client_data = &setjmp_buffer; + + /* setup data source */ + source_mgr.next_input_byte = source_data; + source_mgr.bytes_in_buffer = source_size; + cinfo.src = &source_mgr; + + /* read JPEG header information */ + jpeg_read_header(&cinfo, TRUE); + + /* set output processing parameters */ + cinfo.out_color_space = JCS_RGB; + cinfo.dct_method = JDCT_ISLOW; + + /* commence the decompression, output parameters now valid */ + jpeg_start_decompress(&cinfo); + + width = cinfo.output_width; + height = cinfo.output_height; + + /* create opaque bitmap (jpegs cannot be transparent) */ + bitmap = guit->bitmap->create(width, height, BITMAP_NEW | BITMAP_OPAQUE); + if (bitmap == NULL) { + /* empty bitmap could not be created */ + jpeg_destroy_decompress(&cinfo); + return NULL; + } + + pixels = guit->bitmap->get_buffer(bitmap); + if (pixels == NULL) { + /* bitmap with no buffer available */ + guit->bitmap->destroy(bitmap); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + + /* Convert scanlines from jpeg into bitmap */ + rowstride = guit->bitmap->get_rowstride(bitmap); + do { + JSAMPROW scanlines[1]; + + scanlines[0] = (JSAMPROW) (pixels + + rowstride * cinfo.output_scanline); + jpeg_read_scanlines(&cinfo, scanlines, 1); + +#if RGB_RED != 0 || RGB_GREEN != 1 || RGB_BLUE != 2 || RGB_PIXELSIZE != 4 +{ + /* Missmatch between configured libjpeg pixel format and + * NetSurf pixel format. Convert to RGBA */ + int i; + for (i = width - 1; 0 <= i; i--) { + int r = scanlines[0][i * RGB_PIXELSIZE + RGB_RED]; + int g = scanlines[0][i * RGB_PIXELSIZE + RGB_GREEN]; + int b = scanlines[0][i * RGB_PIXELSIZE + RGB_BLUE]; + scanlines[0][i * 4 + 0] = r; + scanlines[0][i * 4 + 1] = g; + scanlines[0][i * 4 + 2] = b; + scanlines[0][i * 4 + 3] = 0xff; + } +} +#endif + } while (cinfo.output_scanline != cinfo.output_height); + guit->bitmap->modified(bitmap); + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return bitmap; +} + +/** + * Convert a CONTENT_JPEG for display. + */ +static bool nsjpeg_convert(struct content *c) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + jmp_buf setjmp_buffer; + struct jpeg_source_mgr source_mgr = { 0, 0, + nsjpeg_init_source, nsjpeg_fill_input_buffer, + nsjpeg_skip_input_data, jpeg_resync_to_restart, + nsjpeg_term_source }; + union content_msg_data msg_data; + const char *data; + unsigned long size; + char *title; + + /* check image header is valid and get width/height */ + data = content__get_source_data(c, &size); + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = nsjpeg_error_exit; + jerr.output_message = nsjpeg_error_log; + + if (setjmp(setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + + msg_data.error = nsjpeg_error_buffer; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + jpeg_create_decompress(&cinfo); + cinfo.client_data = &setjmp_buffer; + source_mgr.next_input_byte = (unsigned char *) data; + source_mgr.bytes_in_buffer = size; + cinfo.src = &source_mgr; + jpeg_read_header(&cinfo, TRUE); + cinfo.out_color_space = JCS_RGB; + cinfo.dct_method = JDCT_ISLOW; + + jpeg_calc_output_dimensions(&cinfo); + + c->width = cinfo.output_width; + c->height = cinfo.output_height; + c->size = c->width * c->height * 4; + + jpeg_destroy_decompress(&cinfo); + + image_cache_add(c, NULL, jpeg_cache_convert); + + /* set title text */ + title = messages_get_buff("JPEGTitle", + nsurl_access_leaf(llcache_handle_get_url(c->llcache)), + c->width, c->height); + if (title != NULL) { + content__set_title(c, title); + free(title); + } + + content_set_ready(c); + content_set_done(c); + content_set_status(c, ""); /* Done: update status bar */ + + return true; +} + + + +/** + * Clone content. + */ +static nserror nsjpeg_clone(const struct content *old, struct content **newc) +{ + struct content *jpeg_c; + nserror error; + + jpeg_c = calloc(1, sizeof(struct content)); + if (jpeg_c == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, jpeg_c); + if (error != NSERROR_OK) { + content_destroy(jpeg_c); + return error; + } + + /* re-convert if the content is ready */ + if ((old->status == CONTENT_STATUS_READY) || + (old->status == CONTENT_STATUS_DONE)) { + if (nsjpeg_convert(jpeg_c) == false) { + content_destroy(jpeg_c); + return NSERROR_CLONE_FAILED; + } + } + + *newc = jpeg_c; + + return NSERROR_OK; +} + +static const content_handler nsjpeg_content_handler = { + .create = nsjpeg_create, + .data_complete = nsjpeg_convert, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, + .clone = nsjpeg_clone, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, + .no_share = false, +}; + +static const char *nsjpeg_types[] = { + "image/jpeg", + "image/jpg", + "image/pjpeg" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsjpeg, nsjpeg_types, nsjpeg_content_handler); diff --git a/content/handlers/image/jpeg.h b/content/handlers/image/jpeg.h new file mode 100644 index 000000000..8c054732b --- /dev/null +++ b/content/handlers/image/jpeg.h @@ -0,0 +1,28 @@ +/* + * Copyright 2004 James Bursa + * + * 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/jpeg (interface). + */ + +#ifndef _NETSURF_IMAGE_JPEG_H_ +#define _NETSURF_IMAGE_JPEG_H_ + +nserror nsjpeg_init(void); + +#endif diff --git a/content/handlers/image/nssprite.c b/content/handlers/image/nssprite.c new file mode 100644 index 000000000..fd651e0b0 --- /dev/null +++ b/content/handlers/image/nssprite.c @@ -0,0 +1,259 @@ + /* + * Copyright 2008 James Shaw + * + * 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/x-riscos-sprite (librosprite implementation). + * + */ + +#include +#include +#include + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "content/content_protected.h" +#include "desktop/gui_internal.h" +#include "desktop/plotters.h" + +#include "bitmap.h" +#include "nssprite.h" + +typedef struct nssprite_content { + struct content base; + struct bitmap *bitmap; /**< Created NetSurf bitmap */ + + struct rosprite_area* sprite_area; +} nssprite_content; + + +#define ERRCHK(x) do { \ + rosprite_error err = x; \ + if (err == ROSPRITE_EOF) { \ + LOG("Got ROSPRITE_EOF when loading sprite file"); \ + return false; \ + } else if (err == ROSPRITE_BADMODE) { \ + LOG("Got ROSPRITE_BADMODE when loading sprite file"); \ + return false; \ + } else if (err == ROSPRITE_OK) { \ + } else { \ + return false; \ + } \ +} while(0) + + + + +static nserror nssprite_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) +{ + nssprite_content *sprite; + nserror error; + + sprite = calloc(1, sizeof(nssprite_content)); + if (sprite == NULL) + return NSERROR_NOMEM; + + error = content__init(&sprite->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(sprite); + return error; + } + + *c = (struct content *) sprite; + + return NSERROR_OK; +} + +/** + * Convert a CONTENT_SPRITE for display. + * + * No conversion is necessary. We merely read the sprite dimensions. + */ + +static bool nssprite_convert(struct content *c) +{ + nssprite_content *nssprite = (nssprite_content *) c; + union content_msg_data msg_data; + + struct rosprite_mem_context* ctx; + + const char *data; + unsigned long size; + char *title; + + data = content__get_source_data(c, &size); + + ERRCHK(rosprite_create_mem_context((uint8_t *) data, size, &ctx)); + + struct rosprite_area* sprite_area; + ERRCHK(rosprite_load(rosprite_mem_reader, ctx, &sprite_area)); + rosprite_destroy_mem_context(ctx); + nssprite->sprite_area = sprite_area; + + assert(sprite_area->sprite_count > 0); + + struct rosprite* sprite = sprite_area->sprites[0]; + + nssprite->bitmap = guit->bitmap->create(sprite->width, sprite->height, BITMAP_NEW); + if (!nssprite->bitmap) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + uint32_t* imagebuf = (uint32_t *)guit->bitmap->get_buffer(nssprite->bitmap); + if (!imagebuf) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + unsigned char *spritebuf = (unsigned char *)sprite->image; + + /* reverse byte order of each word */ + for (uint32_t y = 0; y < sprite->height; y++) { + for (uint32_t x = 0; x < sprite->width; x++) { + int offset = 4 * (y * sprite->width + x); + + *imagebuf = (spritebuf[offset] << 24) | + (spritebuf[offset + 1] << 16) | + (spritebuf[offset + 2] << 8) | + (spritebuf[offset + 3]); + + imagebuf++; + } + } + + c->width = sprite->width; + c->height = sprite->height; + + /* set title text */ + title = messages_get_buff("SpriteTitle", + nsurl_access_leaf(llcache_handle_get_url(c->llcache)), + c->width, c->height); + if (title != NULL) { + content__set_title(c, title); + free(title); + } + + guit->bitmap->modified(nssprite->bitmap); + + content_set_ready(c); + content_set_done(c); + content_set_status(c, ""); /* Done: update status bar */ + + return true; +} + + +/** + * Destroy a CONTENT_SPRITE and free all resources it owns. + */ + +static void nssprite_destroy(struct content *c) +{ + nssprite_content *nssprite = (nssprite_content *) c; + + if (nssprite->sprite_area != NULL) + rosprite_destroy_sprite_area(nssprite->sprite_area); + if (nssprite->bitmap != NULL) + guit->bitmap->destroy(nssprite->bitmap); +} + + +/** + * Redraw a CONTENT_SPRITE. + */ + +static bool nssprite_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + nssprite_content *nssprite = (nssprite_content *) c; + bitmap_flags_t flags = BITMAPF_NONE; + + 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, + nssprite->bitmap, data->background_colour, flags); +} + + +static nserror nssprite_clone(const struct content *old, struct content **newc) +{ + nssprite_content *sprite; + nserror error; + + sprite = calloc(1, sizeof(nssprite_content)); + if (sprite == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, &sprite->base); + if (error != NSERROR_OK) { + content_destroy(&sprite->base); + return error; + } + + /* Simply replay convert */ + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (nssprite_convert(&sprite->base) == false) { + content_destroy(&sprite->base); + return NSERROR_CLONE_FAILED; + } + } + + *newc = (struct content *) sprite; + + return NSERROR_OK; +} + +static void *nssprite_get_internal(const struct content *c, void *context) +{ + nssprite_content *nssprite = (nssprite_content *) c; + + return nssprite->bitmap; +} + +static content_type nssprite_content_type(void) +{ + return CONTENT_IMAGE; +} + + +static const content_handler nssprite_content_handler = { + .create = nssprite_create, + .data_complete = nssprite_convert, + .destroy = nssprite_destroy, + .redraw = nssprite_redraw, + .clone = nssprite_clone, + .get_internal = nssprite_get_internal, + .type = nssprite_content_type, + .no_share = false, +}; + +static const char *nssprite_types[] = { + "image/x-riscos-sprite" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nssprite, nssprite_types, nssprite_content_handler); diff --git a/content/handlers/image/nssprite.h b/content/handlers/image/nssprite.h new file mode 100644 index 000000000..38226f84e --- /dev/null +++ b/content/handlers/image/nssprite.h @@ -0,0 +1,28 @@ +/* + * Copyright 2008 James Shaw + * + * 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/x-riscos-sprite (librosprite interface). + */ + +#ifndef _NETSURF_NS_SPRITE_H_ +#define _NETSURF_NS_SPRITE_H_ + +nserror nssprite_init(void); + +#endif diff --git a/content/handlers/image/png.c b/content/handlers/image/png.c new file mode 100644 index 000000000..4f6f17d58 --- /dev/null +++ b/content/handlers/image/png.c @@ -0,0 +1,614 @@ +/* + * Copyright 2004 James Bursa + * Copyright 2004 Richard Wilson + * Copyright 2008 Daniel Silverstone + * + * 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 . + */ + +#include +#include +#include + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "content/content_protected.h" +#include "desktop/gui_internal.h" + +#include "image_cache.h" +#include "bitmap.h" +#include "png.h" + +/* accommodate for old versions of libpng (beware security holes!) */ + +#ifndef png_jmpbuf +#warning you have an antique libpng +#define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) +#endif + +#if PNG_LIBPNG_VER < 10209 +#define png_set_expand_gray_1_2_4_to_8(png) png_set_gray_1_2_4_to_8(png) +#endif + +typedef struct nspng_content { + struct content base; /**< base content type */ + + bool no_process_data; /**< Do not continue to process data as it arrives */ + png_structp png; + png_infop info; + int interlace; + struct bitmap *bitmap; /**< Created NetSurf bitmap */ + size_t rowstride, bpp; /**< Bitmap rowstride and bpp */ + size_t rowbytes; /**< Number of bytes per row */ +} nspng_content; + +static unsigned int interlace_start[8] = {0, 16, 0, 8, 0, 4, 0}; +static unsigned int interlace_step[8] = {28, 28, 12, 12, 4, 4, 0}; +static unsigned int interlace_row_start[8] = {0, 0, 4, 0, 2, 0, 1}; +static unsigned int interlace_row_step[8] = {8, 8, 8, 4, 4, 2, 2}; + +/** Callbak error numbers*/ +enum nspng_cberr { + CBERR_NONE = 0, /* no error */ + CBERR_LIBPNG, /* error from png library */ + CBERR_NOPRE, /* no pre-conversion performed */ +}; + +/** + * nspng_warning -- callback for libpng warnings + */ +static void nspng_warning(png_structp png_ptr, png_const_charp warning_message) +{ + LOG("%s", warning_message); +} + +/** + * nspng_error -- callback for libpng errors + */ +static void nspng_error(png_structp png_ptr, png_const_charp error_message) +{ + LOG("%s", error_message); + longjmp(png_jmpbuf(png_ptr), CBERR_LIBPNG); +} + +static void nspng_setup_transforms(png_structp png_ptr, png_infop info_ptr) +{ + int bit_depth, color_type, intent; + double gamma; + + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr, info_ptr); + + /* Set up our transformations */ + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } + + if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8)) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + } + + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + + if (!(color_type & PNG_COLOR_MASK_ALPHA)) { + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + } + + /* gamma correction - we use 2.2 as our screen gamma + * this appears to be correct (at least in respect to !Browse) + * see http://www.w3.org/Graphics/PNG/all_seven.html for a test case + */ + if (png_get_sRGB(png_ptr, info_ptr, &intent)) { + png_set_gamma(png_ptr, 2.2, 0.45455); + } else { + if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { + png_set_gamma(png_ptr, 2.2, gamma); + } else { + png_set_gamma(png_ptr, 2.2, 0.45455); + } + } + + png_read_update_info(png_ptr, info_ptr); +} + +/** + * info_callback -- PNG header has been completely received, prepare to process + * image data + */ +static void info_callback(png_structp png_s, png_infop info) +{ + int interlace; + png_uint_32 width, height; + nspng_content *png_c = png_get_progressive_ptr(png_s); + + width = png_get_image_width(png_s, info); + height = png_get_image_height(png_s, info); + interlace = png_get_interlace_type(png_s, info); + + png_c->base.width = width; + png_c->base.height = height; + png_c->base.size += width * height * 4; + + /* see if progressive-conversion should continue */ + if (image_cache_speculate((struct content *)png_c) == false) { + longjmp(png_jmpbuf(png_s), CBERR_NOPRE); + } + + /* Claim the required memory for the converted PNG */ + png_c->bitmap = guit->bitmap->create(width, height, BITMAP_NEW); + if (png_c->bitmap == NULL) { + /* Failed to create bitmap skip pre-conversion */ + longjmp(png_jmpbuf(png_s), CBERR_NOPRE); + } + + png_c->rowstride = guit->bitmap->get_rowstride(png_c->bitmap); + png_c->bpp = guit->bitmap->get_bpp(png_c->bitmap); + + nspng_setup_transforms(png_s, info); + + png_c->rowbytes = png_get_rowbytes(png_s, info); + png_c->interlace = (interlace == PNG_INTERLACE_ADAM7); + + LOG("size %li * %li, rowbytes %" PRIsizet, + (unsigned long)width, + (unsigned long)height, + png_c->rowbytes); +} + +static void row_callback(png_structp png_s, png_bytep new_row, + png_uint_32 row_num, int pass) +{ + nspng_content *png_c = png_get_progressive_ptr(png_s); + unsigned long rowbytes = png_c->rowbytes; + unsigned char *buffer, *row; + + /* Give up if there's no bitmap */ + if (png_c->bitmap == NULL) + return; + + /* Abort if we've not got any data */ + if (new_row == NULL) + return; + + /* Get bitmap buffer */ + buffer = guit->bitmap->get_buffer(png_c->bitmap); + if (buffer == NULL) { + /* No buffer, bail out */ + longjmp(png_jmpbuf(png_s), 1); + } + + /* Calculate address of row start */ + row = buffer + (png_c->rowstride * row_num); + + /* Handle interlaced sprites using the Adam7 algorithm */ + if (png_c->interlace) { + unsigned long dst_off; + unsigned long src_off = 0; + unsigned int start, step; + + start = interlace_start[pass]; + step = interlace_step[pass]; + row_num = interlace_row_start[pass] + + interlace_row_step[pass] * row_num; + + /* Copy the data to our current row taking interlacing + * into consideration */ + row = buffer + (png_c->rowstride * row_num); + + for (dst_off = start; dst_off < rowbytes; dst_off += step) { + row[dst_off++] = new_row[src_off++]; + row[dst_off++] = new_row[src_off++]; + row[dst_off++] = new_row[src_off++]; + row[dst_off++] = new_row[src_off++]; + } + } else { + /* Do a fast memcpy of the row data */ + memcpy(row, new_row, rowbytes); + } +} + + +static void end_callback(png_structp png_s, png_infop info) +{ +} + +static nserror nspng_create_png_data(nspng_content *png_c) +{ + union content_msg_data msg_data; + + png_c->bitmap = NULL; + + png_c->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (png_c->png == NULL) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(&png_c->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; + } + + png_set_error_fn(png_c->png, NULL, nspng_error, nspng_warning); + + png_c->info = png_create_info_struct(png_c->png); + if (png_c->info == NULL) { + png_destroy_read_struct(&png_c->png, &png_c->info, 0); + + msg_data.error = messages_get("NoMemory"); + content_broadcast(&png_c->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; + } + + if (setjmp(png_jmpbuf(png_c->png))) { + png_destroy_read_struct(&png_c->png, &png_c->info, 0); + LOG("Failed to set callbacks"); + png_c->png = NULL; + png_c->info = NULL; + + msg_data.error = messages_get("PNGError"); + content_broadcast(&png_c->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; + } + + png_set_progressive_read_fn(png_c->png, png_c, + info_callback, row_callback, end_callback); + + return NSERROR_OK; +} + +static nserror nspng_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) +{ + nspng_content *png_c; + nserror error; + + png_c = calloc(1, sizeof(nspng_content)); + if (png_c == NULL) + return NSERROR_NOMEM; + + error = content__init(&png_c->base, + handler, + imime_type, + params, + llcache, + fallback_charset, + quirks); + if (error != NSERROR_OK) { + free(png_c); + return error; + } + + error = nspng_create_png_data(png_c); + if (error != NSERROR_OK) { + free(png_c); + return error; + } + + *c = (struct content *)png_c; + + return NSERROR_OK; +} + + +static bool nspng_process_data(struct content *c, const char *data, + unsigned int size) +{ + nspng_content *png_c = (nspng_content *)c; + union content_msg_data msg_data; + volatile bool ret = true; + + if (png_c->no_process_data) { + return ret; + } + + switch (setjmp(png_jmpbuf(png_c->png))) { + case CBERR_NONE: /* direct return */ + png_process_data(png_c->png, png_c->info, (uint8_t *)data, size); + break; + + case CBERR_NOPRE: /* not going to progressive convert */ + png_c->no_process_data = true; + break; + + default: /* fatal error from library processing png */ + if (png_c->bitmap != NULL) { + /* A bitmap managed to get created so + * operation is past header and possibly some + * conversion happened before faliure. + * + * In this case keep the partial + * conversion. This is usually seen if a png + * has been truncated (often jsut lost its + * last byte and hence end of image marker) + */ + png_c->no_process_data = true; + } else { + /* not managed to progress past header, clean + * up png conversion and signal the content + * error + */ + LOG("Fatal PNG error during header, error content"); + + png_destroy_read_struct(&png_c->png, &png_c->info, 0); + png_c->png = NULL; + png_c->info = NULL; + + msg_data.error = messages_get("PNGError"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + + ret = false; + + } + break; + } + + return ret; +} + +struct png_cache_read_data_s { + const char *data; + unsigned long size; +}; + +/** PNG library read fucntion to read data from a memory array + */ +static void +png_cache_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) +{ + struct png_cache_read_data_s *png_cache_read_data; + png_cache_read_data = png_get_io_ptr(png_ptr); + + if (length > png_cache_read_data->size) { + length = png_cache_read_data->size; + } + + if (length == 0) { + png_error(png_ptr, "Read Error"); + } + + memcpy(data, png_cache_read_data->data, length); + + png_cache_read_data->data += length; + png_cache_read_data->size -= length; +} + +/** calculate an array of row pointers into a bitmap data area + */ +static png_bytep *calc_row_pointers(struct bitmap *bitmap) +{ + int height = guit->bitmap->get_height(bitmap); + unsigned char *buffer= guit->bitmap->get_buffer(bitmap); + size_t rowstride = guit->bitmap->get_rowstride(bitmap); + png_bytep *row_ptrs; + int hloop; + + /* The buffer allocation may occour when the buffer is aquired + * and therefore may fail. + */ + if (buffer == NULL) { + return NULL; + } + + row_ptrs = malloc(sizeof(png_bytep) * height); + + if (row_ptrs != NULL) { + for (hloop = 0; hloop < height; hloop++) { + row_ptrs[hloop] = buffer + (rowstride * hloop); + } + } + + return row_ptrs; +} + +/** PNG content to bitmap conversion. + * + * This routine generates a bitmap object from a PNG image content + */ +static struct bitmap * +png_cache_convert(struct content *c) +{ + png_structp png_ptr; + png_infop info_ptr; + png_infop end_info_ptr; + volatile struct bitmap * volatile bitmap = NULL; + struct png_cache_read_data_s png_cache_read_data; + png_uint_32 width, height; + volatile png_bytep * volatile row_pointers = NULL; + + png_cache_read_data.data = + content__get_source_data(c, &png_cache_read_data.size); + + if ((png_cache_read_data.data == NULL) || + (png_cache_read_data.size <= 8)) { + return NULL; + } + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, + nspng_error, nspng_warning); + if (png_ptr == NULL) { + return NULL; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return NULL; + } + + end_info_ptr = png_create_info_struct(png_ptr); + if (end_info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return NULL; + } + + /* setup error exit path */ + if (setjmp(png_jmpbuf(png_ptr))) { + /* cleanup and bail */ + goto png_cache_convert_error; + } + + /* read from a buffer instead of stdio */ + png_set_read_fn(png_ptr, &png_cache_read_data, png_cache_read_fn); + + /* ensure the png info structure is populated */ + png_read_info(png_ptr, info_ptr); + + /* setup output transforms */ + nspng_setup_transforms(png_ptr, info_ptr); + + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); + + /* Claim the required memory for the converted PNG */ + bitmap = guit->bitmap->create(width, height, BITMAP_NEW); + if (bitmap == NULL) { + /* cleanup and bail */ + goto png_cache_convert_error; + } + + row_pointers = calc_row_pointers((struct bitmap *) bitmap); + + if (row_pointers != NULL) { + png_read_image(png_ptr, (png_bytep *) row_pointers); + } else { + guit->bitmap->destroy((struct bitmap *)bitmap); + bitmap = NULL; + } + +png_cache_convert_error: + + /* cleanup png read */ + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr); + + if (row_pointers != NULL) { + free((png_bytep *) row_pointers); + } + + if (bitmap != NULL) { + guit->bitmap->modified((struct bitmap *)bitmap); + } + + return (struct bitmap *)bitmap; +} + +static bool nspng_convert(struct content *c) +{ + nspng_content *png_c = (nspng_content *) c; + char *title; + + assert(png_c->png != NULL); + assert(png_c->info != NULL); + + /* clean up png structures */ + png_destroy_read_struct(&png_c->png, &png_c->info, 0); + + /* set title text */ + title = messages_get_buff("PNGTitle", + nsurl_access_leaf(llcache_handle_get_url(c->llcache)), + c->width, c->height); + if (title != NULL) { + content__set_title(c, title); + free(title); + } + + if (png_c->bitmap != NULL) { + guit->bitmap->set_opaque(png_c->bitmap, guit->bitmap->test_opaque(png_c->bitmap)); + guit->bitmap->modified(png_c->bitmap); + } + + image_cache_add(c, png_c->bitmap, png_cache_convert); + + content_set_ready(c); + content_set_done(c); + content_set_status(c, ""); + + return true; +} + + +static nserror nspng_clone(const struct content *old_c, struct content **new_c) +{ + nspng_content *clone_png_c; + nserror error; + const char *data; + unsigned long size; + + clone_png_c = calloc(1, sizeof(nspng_content)); + if (clone_png_c == NULL) + return NSERROR_NOMEM; + + error = content__clone(old_c, &clone_png_c->base); + if (error != NSERROR_OK) { + content_destroy(&clone_png_c->base); + return error; + } + + /* Simply replay create/process/convert */ + error = nspng_create_png_data(clone_png_c); + if (error != NSERROR_OK) { + content_destroy(&clone_png_c->base); + return error; + } + + data = content__get_source_data(&clone_png_c->base, &size); + if (size > 0) { + if (nspng_process_data(&clone_png_c->base, data, size) == false) { + content_destroy(&clone_png_c->base); + return NSERROR_NOMEM; + } + } + + if ((old_c->status == CONTENT_STATUS_READY) || + (old_c->status == CONTENT_STATUS_DONE)) { + if (nspng_convert(&clone_png_c->base) == false) { + content_destroy(&clone_png_c->base); + return NSERROR_CLONE_FAILED; + } + } + + *new_c = (struct content *)clone_png_c; + + return NSERROR_OK; +} + +static const content_handler nspng_content_handler = { + .create = nspng_create, + .process_data = nspng_process_data, + .data_complete = nspng_convert, + .clone = nspng_clone, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, + .no_share = false, +}; + +static const char *nspng_types[] = { + "image/png", + "image/x-png" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nspng, nspng_types, nspng_content_handler); diff --git a/content/handlers/image/png.h b/content/handlers/image/png.h new file mode 100644 index 000000000..3a2e3741f --- /dev/null +++ b/content/handlers/image/png.h @@ -0,0 +1,25 @@ +/* + * Copyright 2003 James Bursa + * Copyright 2008 Daniel Silverstone + * + * 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 . + */ + +#ifndef _NETSURF_RISCOS_PNG_H_ +#define _NETSURF_RISCOS_PNG_H_ + +nserror nspng_init(void); + +#endif diff --git a/content/handlers/image/rsvg.c b/content/handlers/image/rsvg.c new file mode 100644 index 000000000..bd773682f --- /dev/null +++ b/content/handlers/image/rsvg.c @@ -0,0 +1,326 @@ +/* + * Copyright 2007 Rob Kendrick + * + * 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 handler for image/svg using librsvg (implementation). + * + * SVG files are rendered to a NetSurf bitmap by creating a Cairo rendering + * surface (content_rsvg_data.cs) over the bitmap's data, creating a Cairo + * drawing context using that surface, and then passing that drawing context + * to librsvg which then uses Cairo calls to plot the graphic to the bitmap. + * We store this in content->bitmap, and then use the usual bitmap plotter + * function to render it for redraw requests. + */ + +#include +#include +#include +#include +#include + +#include +#ifndef RSVG_CAIRO_H +#include +#endif + +#include "utils/log.h" +#include "utils/utils.h" +#include "utils/messages.h" +#include "content/content_protected.h" +#include "desktop/plotters.h" +#include "desktop/gui_internal.h" + +#include "bitmap.h" +#include "rsvg.h" + +typedef struct rsvg_content { + struct content base; + + RsvgHandle *rsvgh; /**< Context handle for RSVG renderer */ + cairo_surface_t *cs; /**< The surface built inside a nsbitmap */ + cairo_t *ct; /**< Cairo drawing context */ + struct bitmap *bitmap; /**< Created NetSurf bitmap */ +} rsvg_content; + +static nserror rsvg_create_svg_data(rsvg_content *c) +{ + union content_msg_data msg_data; + + c->rsvgh = NULL; + c->cs = NULL; + c->ct = NULL; + c->bitmap = NULL; + + if ((c->rsvgh = rsvg_handle_new()) == NULL) { + LOG("rsvg_handle_new() returned NULL."); + msg_data.error = messages_get("NoMemory"); + content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; + } + + return NSERROR_OK; +} + + +static nserror rsvg_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) +{ + rsvg_content *svg; + nserror error; + + svg = calloc(1, sizeof(rsvg_content)); + if (svg == NULL) + return NSERROR_NOMEM; + + error = content__init(&svg->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(svg); + return error; + } + + error = rsvg_create_svg_data(svg); + if (error != NSERROR_OK) { + free(svg); + return error; + } + + *c = (struct content *) svg; + + return NSERROR_OK; +} + + +static bool rsvg_process_data(struct content *c, const char *data, + unsigned int size) +{ + rsvg_content *d = (rsvg_content *) c; + union content_msg_data msg_data; + GError *err = NULL; + + if (rsvg_handle_write(d->rsvgh, (const guchar *)data, (gsize)size, + &err) == FALSE) { + LOG("rsvg_handle_write returned an error: %s", err->message); + msg_data.error = err->message; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + return true; +} + +/** Convert Cairo's ARGB output to NetSurf's favoured ABGR format. It converts + * the data in-place. + * + * \param pixels Pixel data, in the form of ARGB. This will + * be overwritten with new data in the form of ABGR. + * \param width Width of the bitmap + * \param height Height of the bitmap + * \param rowstride Number of bytes to skip after each row (this + * implementation requires this to be a multiple of 4.) + */ +static inline void rsvg_argb_to_abgr(uint8_t *pixels, + int width, int height, size_t rowstride) +{ + uint8_t *p = pixels; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + /* Swap R and B */ + const uint8_t r = p[x+3]; + + p[x+3] = p[x]; + + p[x] = r; + } + + p += rowstride; + } +} + +static bool rsvg_convert(struct content *c) +{ + rsvg_content *d = (rsvg_content *) c; + union content_msg_data msg_data; + RsvgDimensionData rsvgsize; + GError *err = NULL; + + if (rsvg_handle_close(d->rsvgh, &err) == FALSE) { + LOG("rsvg_handle_close returned an error: %s", err->message); + msg_data.error = err->message; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + assert(err == NULL); + + /* we should now be able to query librsvg for the natural size of the + * graphic, so we can create our bitmap. + */ + + rsvg_handle_get_dimensions(d->rsvgh, &rsvgsize); + c->width = rsvgsize.width; + c->height = rsvgsize.height; + + if ((d->bitmap = guit->bitmap->create(c->width, c->height, + BITMAP_NEW)) == NULL) { + LOG("Failed to create bitmap for rsvg render."); + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + if ((d->cs = cairo_image_surface_create_for_data( + (unsigned char *)guit->bitmap->get_buffer(d->bitmap), + CAIRO_FORMAT_ARGB32, + c->width, c->height, + guit->bitmap->get_rowstride(d->bitmap))) == NULL) { + LOG("Failed to create Cairo image surface for rsvg render."); + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + if ((d->ct = cairo_create(d->cs)) == NULL) { + LOG("Failed to create Cairo drawing context for rsvg render."); + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + rsvg_handle_render_cairo(d->rsvgh, d->ct); + rsvg_argb_to_abgr(guit->bitmap->get_buffer(d->bitmap), + c->width, c->height, + guit->bitmap->get_rowstride(d->bitmap)); + + guit->bitmap->modified(d->bitmap); + content_set_ready(c); + content_set_done(c); + /* Done: update status bar */ + content_set_status(c, ""); + + return true; +} + +static bool rsvg_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + rsvg_content *rsvgcontent = (rsvg_content *) c; + bitmap_flags_t flags = BITMAPF_NONE; + + assert(rsvgcontent->bitmap != NULL); + + 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, + rsvgcontent->bitmap, data->background_colour, flags); +} + +static void rsvg_destroy(struct content *c) +{ + rsvg_content *d = (rsvg_content *) c; + + if (d->bitmap != NULL) guit->bitmap->destroy(d->bitmap); + if (d->rsvgh != NULL) g_object_unref(d->rsvgh); + if (d->ct != NULL) cairo_destroy(d->ct); + if (d->cs != NULL) cairo_surface_destroy(d->cs); + + return; +} + +static nserror rsvg_clone(const struct content *old, struct content **newc) +{ + rsvg_content *svg; + nserror error; + const char *data; + unsigned long size; + + svg = calloc(1, sizeof(rsvg_content)); + if (svg == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, &svg->base); + if (error != NSERROR_OK) { + content_destroy(&svg->base); + return error; + } + + /* Simply replay create/process/convert */ + error = rsvg_create_svg_data(svg); + if (error != NSERROR_OK) { + content_destroy(&svg->base); + return error; + } + + data = content__get_source_data(&svg->base, &size); + if (size > 0) { + if (rsvg_process_data(&svg->base, data, size) == false) { + content_destroy(&svg->base); + return NSERROR_NOMEM; + } + } + + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (rsvg_convert(&svg->base) == false) { + content_destroy(&svg->base); + return NSERROR_CLONE_FAILED; + } + } + + *newc = (struct content *) svg; + + return NSERROR_OK; +} + +static void *rsvg_get_internal(const struct content *c, void *context) +{ + rsvg_content *d = (rsvg_content *) c; + + return d->bitmap; +} + +static content_type rsvg_content_type(void) +{ + return CONTENT_IMAGE; +} + +static const content_handler rsvg_content_handler = { + .create = rsvg_create, + .process_data = rsvg_process_data, + .data_complete = rsvg_convert, + .destroy = rsvg_destroy, + .redraw = rsvg_redraw, + .clone = rsvg_clone, + .get_internal = rsvg_get_internal, + .type = rsvg_content_type, + .no_share = false, +}; + +static const char *rsvg_types[] = { + "image/svg", + "image/svg+xml" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsrsvg, rsvg_types, rsvg_content_handler); + diff --git a/content/handlers/image/rsvg.h b/content/handlers/image/rsvg.h new file mode 100644 index 000000000..f38f8b0e8 --- /dev/null +++ b/content/handlers/image/rsvg.h @@ -0,0 +1,28 @@ +/* + * Copyright 2007 Rob Kendrick + * + * 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 handler for image/svg using librsvg (interface). + */ + +#ifndef _NETSURF_IMAGE_RSVG_H_ +#define _NETSURF_IMAGE_RSVG_H_ + +nserror nsrsvg_init(void); + +#endif diff --git a/content/handlers/image/svg.c b/content/handlers/image/svg.c new file mode 100644 index 000000000..c91b00650 --- /dev/null +++ b/content/handlers/image/svg.c @@ -0,0 +1,353 @@ +/* + * Copyright 2007-2008 James Bursa + * + * 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/svg (implementation). + */ + +#include +#include +#include + +#include + +#include "utils/messages.h" +#include "utils/utils.h" +#include "content/content_protected.h" +#include "css/css.h" +#include "desktop/plotters.h" + +#include "svg.h" + +typedef struct svg_content { + struct content base; + + struct svgtiny_diagram *diagram; + + int current_width; + int current_height; +} svg_content; + + + +static nserror svg_create_svg_data(svg_content *c) +{ + union content_msg_data msg_data; + + c->diagram = svgtiny_create(); + if (c->diagram == NULL) + goto no_memory; + + c->current_width = INT_MAX; + c->current_height = INT_MAX; + + return NSERROR_OK; + +no_memory: + msg_data.error = messages_get("NoMemory"); + content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); + return NSERROR_NOMEM; +} + + +/** + * Create a CONTENT_SVG. + */ + +static nserror svg_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) +{ + svg_content *svg; + nserror error; + + svg = calloc(1, sizeof(svg_content)); + if (svg == NULL) + return NSERROR_NOMEM; + + error = content__init(&svg->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(svg); + return error; + } + + error = svg_create_svg_data(svg); + if (error != NSERROR_OK) { + free(svg); + return error; + } + + *c = (struct content *) svg; + + return NSERROR_OK; +} + + + +/** + * Convert a CONTENT_SVG for display. + */ + +static bool svg_convert(struct content *c) +{ + /*c->title = malloc(100); + if (c->title) + snprintf(c->title, 100, messages_get("svgTitle"), + width, height, c->source_size);*/ + //c->size += ?; + content_set_ready(c); + content_set_done(c); + /* Done: update status bar */ + content_set_status(c, ""); + + return true; +} + +/** + * Reformat a CONTENT_SVG. + */ + +static void svg_reformat(struct content *c, int width, int height) +{ + svg_content *svg = (svg_content *) c; + const char *source_data; + unsigned long source_size; + + assert(svg->diagram); + + /* Avoid reformats to same width/height as we already reformatted to */ + if (width != svg->current_width || height != svg->current_height) { + source_data = content__get_source_data(c, &source_size); + + svgtiny_parse(svg->diagram, source_data, source_size, + nsurl_access(content_get_url(c)), + width, height); + + svg->current_width = width; + svg->current_height = height; + } + + c->width = svg->diagram->width; + c->height = svg->diagram->height; +} + + +/** + * Redraw a CONTENT_SVG. + */ + +static bool svg_redraw_internal(struct content *c, int x, int y, + int width, int height, const struct rect *clip, + const struct redraw_context *ctx, float scale, + colour background_colour) +{ + svg_content *svg = (svg_content *) c; + float transform[6]; + struct svgtiny_diagram *diagram = svg->diagram; + bool ok; + int px, py; + unsigned int i; + plot_font_style_t fstyle = *plot_style_font; + + assert(diagram); + + transform[0] = (float) width / (float) c->width; + transform[1] = 0; + transform[2] = 0; + transform[3] = (float) height / (float) c->height; + transform[4] = x; + transform[5] = y; + +#define BGR(c) ((c) == svgtiny_TRANSPARENT ? NS_TRANSPARENT : \ + ((svgtiny_RED((c))) | \ + (svgtiny_GREEN((c)) << 8) | \ + (svgtiny_BLUE((c)) << 16))) + + for (i = 0; i != diagram->shape_count; i++) { + if (diagram->shape[i].path) { + ok = ctx->plot->path(diagram->shape[i].path, + diagram->shape[i].path_length, + BGR(diagram->shape[i].fill), + diagram->shape[i].stroke_width, + BGR(diagram->shape[i].stroke), + transform); + if (!ok) + return false; + + } else if (diagram->shape[i].text) { + px = transform[0] * diagram->shape[i].text_x + + transform[2] * diagram->shape[i].text_y + + transform[4]; + py = transform[1] * diagram->shape[i].text_x + + transform[3] * diagram->shape[i].text_y + + transform[5]; + + fstyle.background = 0xffffff; + fstyle.foreground = 0x000000; + fstyle.size = (8 * FONT_SIZE_SCALE) * scale; + + ok = ctx->plot->text(px, py, + diagram->shape[i].text, + strlen(diagram->shape[i].text), + &fstyle); + if (!ok) + return false; + } + } + +#undef BGR + + return true; +} + + +/** + * Redraw a CONTENT_SVG. + */ + +static bool svg_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + int x = data->x; + int y = data->y; + + if ((data->width <= 0) && (data->height <= 0)) { + /* No point trying to plot SVG if it does not occupy a valid + * area */ + return true; + } + + if ((data->repeat_x == false) && (data->repeat_y == false)) { + /* Simple case: SVG is not tiled */ + return svg_redraw_internal(c, x, y, + data->width, data->height, + clip, ctx, data->scale, + data->background_colour); + } else { + /* Tiled redraw required. SVG repeats to extents of clip + * rectangle, in x, y or both directions */ + int x0, y0, x1, y1; + + /* Find the redraw boundaries to loop within */ + x0 = x; + if (data->repeat_x) { + for (; x0 > clip->x0; x0 -= data->width); + x1 = clip->x1; + } else { + x1 = x + 1; + } + y0 = y; + if (data->repeat_y) { + for (; y0 > clip->y0; y0 -= data->height); + y1 = clip->y1; + } else { + y1 = y + 1; + } + + /* Repeatedly plot the SVG across the area */ + for (y = y0; y < y1; y += data->height) { + for (x = x0; x < x1; x += data->width) { + if (!svg_redraw_internal(c, x, y, + data->width, data->height, + clip, ctx, data->scale, + data->background_colour)) { + return false; + } + } + } + } + + return true; +} + + +/** + * Destroy a CONTENT_SVG and free all resources it owns. + */ + +static void svg_destroy(struct content *c) +{ + svg_content *svg = (svg_content *) c; + + if (svg->diagram != NULL) + svgtiny_free(svg->diagram); +} + + +static nserror svg_clone(const struct content *old, struct content **newc) +{ + svg_content *svg; + nserror error; + + svg = calloc(1, sizeof(svg_content)); + if (svg == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, &svg->base); + if (error != NSERROR_OK) { + content_destroy(&svg->base); + return error; + } + + /* Simply replay create/convert */ + error = svg_create_svg_data(svg); + if (error != NSERROR_OK) { + content_destroy(&svg->base); + return error; + } + + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (svg_convert(&svg->base) == false) { + content_destroy(&svg->base); + return NSERROR_CLONE_FAILED; + } + } + + *newc = (struct content *) svg; + + return NSERROR_OK; +} + +static content_type svg_content_type(void) +{ + return CONTENT_IMAGE; +} + +static const content_handler svg_content_handler = { + .create = svg_create, + .data_complete = svg_convert, + .reformat = svg_reformat, + .destroy = svg_destroy, + .redraw = svg_redraw, + .clone = svg_clone, + .type = svg_content_type, + .no_share = true +}; + +static const char *svg_types[] = { + "image/svg", + "image/svg+xml" +}; + + +CONTENT_FACTORY_REGISTER_TYPES(svg, svg_types, svg_content_handler); + + diff --git a/content/handlers/image/svg.h b/content/handlers/image/svg.h new file mode 100644 index 000000000..96b9c3879 --- /dev/null +++ b/content/handlers/image/svg.h @@ -0,0 +1,28 @@ +/* + * Copyright 2007-2008 James Bursa + * + * 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/svg (interface). + */ + +#ifndef _NETSURF_IMAGE_SVG_H_ +#define _NETSURF_IMAGE_SVG_H_ + +nserror svg_init(void); + +#endif diff --git a/content/handlers/image/video.c b/content/handlers/image/video.c new file mode 100644 index 000000000..53b654337 --- /dev/null +++ b/content/handlers/image/video.c @@ -0,0 +1,202 @@ +/* + * Copyright 2011 John-Mark Bell + * + * 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 . + */ + +#include + +#include "content/content_factory.h" +#include "content/content_protected.h" + +#include "video.h" + +typedef struct nsvideo_content { + struct content base; + + GstElement *playbin; + GstElement *appsrc; +} nsvideo_content; + +static gboolean nsvideo_bus_call(GstBus *bus, GstMessage *msg, + nsvideo_content *video) +{ + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + break; + case GST_MESSAGE_EOS: + break; + default: + break; + } + + return TRUE; +} + +static void nsvideo_need_data_event(GstElement *playbin, guint size, + nsvideo_content *video) +{ +} + +static void nsvideo_enough_data_event(GstElement *playbin, + nsvideo_content *video) +{ +} + +static void nsvideo_source_event(GObject *object, GObject *orig, + GParamSpec *pspec, nsvideo_content *video) +{ + g_object_get(orig, pspec->name, &video->appsrc, NULL); + + g_signal_connect(video->appsrc, "need-data", + G_CALLBACK(nsvideo_need_data_event), video); + g_signal_connect(video->appsrc, "enough-data", + G_CALLBACK(nsvideo_enough_data_event), video); +} + +static nserror nsvideo_create(const content_handler *handler, + lwc_string *imime_type, const http_parameter *params, + llcache_handle *llcache, + const char *fallback_charset, bool quirks, + struct content **c) +{ + nsvideo_content *video; + nserror error; + GstBus *bus; + + video = calloc(1, sizeof(nsvideo_content)); + if (video == NULL) + return NSERROR_NOMEM; + + error = content__init(&video->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(video); + return error; + } + + error = llcache_handle_force_stream(llcache); + if (error != NSERROR_OK) { + free(video); + return error; + } + + video->playbin = gst_element_factory_make("playbin2", NULL); + if (video->playbin == NULL) { + free(video); + return NSERROR_NOMEM; + } + + bus = gst_pipeline_get_bus(GST_PIPELINE(video->playbin)); + gst_bus_add_watch(bus, (GstBusFunc) nsvideo_bus_call, video); + gst_object_unref(bus); + + g_object_set(video->playbin, "uri", "appsrc://", NULL); + g_signal_connect(video->playbin, "deep-notify::source", + G_CALLBACK(nsvideo_source_event), video); + + /** \todo Create appsink & register with playbin */ + + gst_element_set_state(video->playbin, GST_STATE_PLAYING); + + *c = (struct content *) video; + + return NSERROR_OK; +} + +static bool nsvideo_process_data(struct content *c, const char *data, + unsigned int size) +{ + nsvideo_content *video = (nsvideo_content *) c; + GstBuffer *buffer; + GstFlowReturn ret; + + buffer = gst_buffer_new(); + GST_BUFFER_DATA(buffer) = (guint8 *) data; + GST_BUFFER_SIZE(buffer) = (gsize) size; + + /* Send data to appsrc */ + g_signal_emit_by_name(video->appsrc, "push-buffer", buffer, &ret); + + return ret == GST_FLOW_OK; +} + +static bool nsvideo_convert(struct content *c) +{ + nsvideo_content *video = (nsvideo_content *) c; + GstFlowReturn ret; + + /* Tell appsrc we're done */ + g_signal_emit_by_name(video->appsrc, "end-of-stream", &ret); + + /* Appsink will flag DONE on receipt of first frame */ + + return ret == GST_FLOW_OK; +} + +static void nsvideo_destroy(struct content *c) +{ + nsvideo_content *video = (nsvideo_content *) c; + + gst_element_set_state(video->playbin, GST_STATE_NULL); + gst_object_unref(video->playbin); +} + +static bool nsvideo_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + /** \todo Implement */ + return true; +} + +static nserror nsvideo_clone(const struct content *old, struct content **newc) +{ + /** \todo Implement */ + return NSERROR_CLONE_FAILED; +} + +static content_type nsvideo_type(void) +{ + /** \todo Lies */ + return CONTENT_IMAGE; +} + +static void *nsvideo_get_internal(const struct content *c, void *context) +{ + /** \todo Return pointer to bitmap containing current frame, if any? */ + return NULL; +} + +static const content_handler nsvideo_content_handler = { + .create = nsvideo_create, + .process_data = nsvideo_process_data, + .data_complete = nsvideo_convert, + .destroy = nsvideo_destroy, + .redraw = nsvideo_redraw, + .clone = nsvideo_clone, + .type = nsvideo_type, + .get_internal = nsvideo_get_internal, + /* Can't share videos because we stream them */ + .no_share = true +}; + +static const char *nsvideo_types[] = { + "video/mp4", + "video/webm" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsvideo, nsvideo_types, + nsvideo_content_handler); + diff --git a/content/handlers/image/video.h b/content/handlers/image/video.h new file mode 100644 index 000000000..376c837ba --- /dev/null +++ b/content/handlers/image/video.h @@ -0,0 +1,26 @@ +/* + * Copyright 2011 John-Mark Bell + * + * 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 . + */ + +#ifndef NETSURF_IMAGE_VIDEO_H_ +#define NETSURF_IMAGE_VIDEO_H_ + +#include "utils/errors.h" + +nserror nsvideo_init(void); + +#endif -- cgit v1.2.3