From b051cf466f7c9f008f2ab8c3f6ccac34882a1b9e Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sun, 4 Sep 2011 23:50:14 +0000 Subject: Add Image cache and inegrate png and jpeg content handlers Current periodic cache clean algorithm is poor and requires replacing with something suitable (probably a segregated LRU) The speculative load algorithm is likewise poor and only uses the image size to make a decision. svn path=/trunk/netsurf/; revision=12720 --- Makefile.sources | 2 +- image/gif.c | 1 + image/image.c | 9 + image/image_cache.c | 471 ++++++++++++++++++++++++++++++++++++++++++++++++++++ image/image_cache.h | 101 +++++++++++ image/jpeg.c | 224 ++++++++++++++----------- image/png.c | 373 +++++++++++++++++++++++++++++------------ 7 files changed, 970 insertions(+), 211 deletions(-) create mode 100644 image/image_cache.c create mode 100644 image/image_cache.h diff --git a/Makefile.sources b/Makefile.sources index 8689e8961..6dcf106b6 100644 --- a/Makefile.sources +++ b/Makefile.sources @@ -37,7 +37,7 @@ S_COMMON := $(addprefix content/,$(S_CONTENT)) \ $(addprefix desktop/,$(S_DESKTOP)) # S_IMAGE are sources related to image management -S_IMAGE_YES := image.c +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 diff --git a/image/gif.c b/image/gif.c index aa05a8c81..dec9653b6 100644 --- a/image/gif.c +++ b/image/gif.c @@ -33,6 +33,7 @@ #include #include #include + #include "utils/config.h" #include "content/content_protected.h" #include "content/hlcache.h" diff --git a/image/image.c b/image/image.c index 779826a17..fb8197b28 100644 --- a/image/image.c +++ b/image/image.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -23,6 +24,7 @@ #include "utils/errors.h" #include "image/image.h" +#include "image/image_cache.h" #include "image/bmp.h" #include "image/gif.h" #include "image/ico.h" @@ -45,6 +47,10 @@ nserror image_init(void) { nserror error; + error = image_cache_init(); + if (error != NSERROR_OK) + return error; + #ifdef WITH_BMP error = nsbmp_init(); if (error != NSERROR_OK) @@ -158,5 +164,8 @@ void image_fini(void) #ifdef WITH_WEBP webp_fini(); #endif + + /* dump any remaining cache entries */ + image_cache_fini(); } diff --git a/image/image_cache.c b/image/image_cache.c new file mode 100644 index 000000000..f70d8ddb2 --- /dev/null +++ b/image/image_cache.c @@ -0,0 +1,471 @@ +/* + * 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 "utils/errors.h" +#include "utils/log.h" +#include "utils/config.h" +#include "utils/schedule.h" +#include "content/content_protected.h" +#include "image/image_cache.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*/ +}; + +/** 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_age current_age; /** the "age" of the current operation */ + struct image_cache_entry_s *entries; /* cache objects */ + + /* Statistics for replacement algorithm */ + + /** total size of bitmaps currently allocated */ + size_t total_bitmap_size; + + /** Max size of bitmaps allocated at any one time */ + size_t max_bitmap_size; + int max_bitmap_size_count; + + int bitmap_count; + int max_bitmap_count; + size_t max_bitmap_count_size; + + int miss_count; /* bitmap was not available at plot time required conversion */ + int specultive_miss_count; /* bitmap was available but never actually required conversion */ + int hit_count; /* bitmap was available at plot time required no conversion */ + int fail_count; /* bitmap was not available at plot time, required conversion which failed */ +}; + +static struct image_cache_s *image_cache = NULL; + +/** low water mark for speculative pre-conversion */ + +/* Experimenting by visiting every page from default page in order and + * then netsurf homepage + * + * 0 : Cache hit/miss/speculative miss/fail 604/147/ 0/0 (80%/19%/ 0%/ 0%) + * 2048 : Cache hit/miss/speculative miss/fail 622/119/ 17/0 (82%/15%/ 2%/ 0%) + * 4096 : Cache hit/miss/speculative miss/fail 656/109/ 25/0 (83%/13%/ 3%/ 0%) + * 8192 : Cache hit/miss/speculative miss/fail 648/104/ 40/0 (81%/13%/ 5%/ 0%) + * ALL : Cache hit/miss/speculative miss/fail 775/ 0/161/0 (82%/ 0%/17%/ 0%) +*/ +#define SPECULATE_SMALL 4096 + +/* the time between cache clean runs in ms */ +#define CACHE_CLEAN_TIME (10 * 1000) + + +/** 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; + + 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; + } +} + +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) { + 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)); + + 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) +{ + LOG(("freeing %p ", centry)); + + image_cache__free_bitmap(centry); + + image_cache__unlink(centry); + + free(centry); +} + +/* exported interface documented in image_cache.h */ +struct bitmap *image_cache_get_bitmap(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++; + } else { + image_cache->fail_count++; + } + } else { + image_cache->hit_count++; + } + + return centry->bitmap; +} + +/* exported interface documented in image_cache.h */ +bool image_cache_speculate(struct content *c) +{ + bool decision = false; + + if (c->size <= SPECULATE_SMALL) { + LOG(("content size (%d) is smaller than minimum (%d)", c->size, SPECULATE_SMALL)); + decision = true; + } + + LOG(("returning %d", decision)); + 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; +} + +static void image_cache__clean(void *p) +{ + struct image_cache_s *icache = p; + struct image_cache_entry_s *centry = icache->entries; + + /* increment current cache age */ + icache->current_age += CACHE_CLEAN_TIME; + + LOG(("Running cache clean at cache age %ds", icache->current_age / 1000)); + + LOG(("Brain dead cache cleaner removing all bitmaps not redraw in last %ds", CACHE_CLEAN_TIME / 1000)); + while (centry != NULL) { + if ((icache->current_age - centry->redraw_age) > CACHE_CLEAN_TIME) { + image_cache__free_bitmap(centry); + } + centry=centry->next; + } + + schedule((CACHE_CLEAN_TIME / 10), image_cache__clean, icache); +} + +/* exported interface documented in image_cache.h */ +nserror image_cache_init(void) +{ + image_cache = calloc(1, sizeof(struct image_cache_s)); + + schedule((CACHE_CLEAN_TIME / 10), image_cache__clean, image_cache); + + return NSERROR_OK; +} + +/* exported interface documented in image_cache.h */ +nserror image_cache_fini(void) +{ + int op_count; + + schedule_remove(image_cache__clean, image_cache); + + op_count = image_cache->hit_count + + image_cache->miss_count + + image_cache->specultive_miss_count + + image_cache->fail_count; + + LOG(("Destroying Remaining Image cache entries")); + + while (image_cache->entries != NULL) { + image_cache__free_entry(image_cache->entries); + } + + LOG(("Image cache size at finish %d (in %d)", image_cache->total_bitmap_size, image_cache->bitmap_count)); + LOG(("Peak size %d (in %d)", image_cache->max_bitmap_size, image_cache->max_bitmap_size_count )); + LOG(("Peak image count %d (size %d)", image_cache->max_bitmap_count, image_cache->max_bitmap_count_size)); + LOG(("Cache hit/miss/speculative miss/fail %d/%d/%d/%d (%d%%/%d%%/%d%%/%d%%)", + image_cache->hit_count, + image_cache->miss_count, + image_cache->specultive_miss_count, + image_cache->fail_count, + (image_cache->hit_count * 100) / op_count, + (image_cache->miss_count * 100) / op_count, + (image_cache->specultive_miss_count * 100) / op_count, + (image_cache->fail_count * 100) / op_count)); + 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) { + 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 */ +bool image_cache_redraw(struct content *c, + struct content_redraw_data *data, + const struct rect *clip, + const struct redraw_context *ctx) +{ + bitmap_flags_t flags = BITMAPF_NONE; + 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 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++; + } else { + image_cache->fail_count++; + return false; + } + } else { + image_cache->hit_count++; + } + + + /* update statistics */ + centry->redraw_count++; + centry->redraw_age = image_cache->current_age; + + /* 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, + centry->bitmap, data->background_colour, flags); +} + +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) +{ + struct image_cache_entry_s *centry; + + centry = image_cache__find(c); + if (centry == NULL) { + return NULL; + } + + return centry->bitmap; +} + +content_type image_cache_content_type(void) +{ + return CONTENT_IMAGE; +} + diff --git a/image/image_cache.h b/image/image_cache.h new file mode 100644 index 000000000..c0ccfac90 --- /dev/null +++ b/image/image_cache.h @@ -0,0 +1,101 @@ +/* + * 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" +#include "desktop/plotters.h" +#include "image/bitmap.h" + +typedef struct bitmap * (image_cache_convert_fn) (struct content *content); + +/** Initialise the image cache */ +nserror image_cache_init(void); +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(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 otehrwise. + */ +bool image_cache_speculate(struct content *c); + +/* 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/image/jpeg.c b/image/jpeg.c index ec1be8eab..2c4610091 100644 --- a/image/jpeg.c +++ b/image/jpeg.c @@ -31,7 +31,7 @@ #include "content/content_protected.h" #include "desktop/plotters.h" -#include "image/bitmap.h" +#include "image/image_cache.h" #include "utils/log.h" #include "utils/messages.h" @@ -43,6 +43,11 @@ #include "jpeglib.h" #include "image/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. */ @@ -54,12 +59,6 @@ static char nsjpeg_error_buffer[JMSG_LENGTH_MAX]; -typedef struct nsjpeg_content { - struct content base; /**< base content */ - - struct bitmap *bitmap; /**< Created NetSurf bitmap */ -} nsjpeg_content; - struct nsjpeg_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; @@ -75,21 +74,21 @@ static nserror nsjpeg_create(const content_handler *handler, llcache_handle *llcache, const char *fallback_charset, bool quirks, struct content **c) { - nsjpeg_content *jpeg; + struct content *jpeg; nserror error; - jpeg = talloc_zero(0, nsjpeg_content); + jpeg = talloc_zero(0, struct content); if (jpeg == NULL) return NSERROR_NOMEM; - error = content__init(&jpeg->base, handler, imime_type, params, + error = content__init(jpeg, handler, imime_type, params, llcache, fallback_charset, quirks); if (error != NSERROR_OK) { talloc_free(jpeg); return error; } - *c = (struct content *) jpeg; + *c = jpeg; return NSERROR_OK; } @@ -164,83 +163,98 @@ static void nsjpeg_error_exit(j_common_ptr cinfo) { struct nsjpeg_error_mgr *err = (struct nsjpeg_error_mgr *) cinfo->err; err->pub.format_message(cinfo, nsjpeg_error_buffer); + LOG(("%s", nsjpeg_error_buffer)); + longjmp(err->setjmp_buffer, 1); } - -/** - * Convert a CONTENT_JPEG for display. - */ -static bool nsjpeg_convert(struct content *c) +static struct bitmap * +jpeg_cache_convert(struct content *c) { - struct nsjpeg_content *jpeg_content = (nsjpeg_content *)c; + uint8_t *source_data; /* Jpeg source data */ + unsigned long source_size; /* length of Jpeg source data */ struct jpeg_decompress_struct cinfo; struct nsjpeg_error_mgr jerr; - 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 }; unsigned int height; unsigned int width; struct bitmap * volatile bitmap = NULL; uint8_t * volatile pixels = NULL; size_t rowstride; - union content_msg_data msg_data; - const char *data; - unsigned long size; - char title[100]; + 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 }; - data = content__get_source_data(c, &size); + /* 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.pub); jerr.pub.error_exit = nsjpeg_error_exit; jerr.pub.output_message = nsjpeg_error_log; + + /* handler for fatal errors during decompression */ if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); - if (bitmap) - bitmap_destroy(bitmap); - - msg_data.error = nsjpeg_error_buffer; - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - return false; + return bitmap; } + jpeg_create_decompress(&cinfo); - source_mgr.next_input_byte = (unsigned char *) data; - source_mgr.bytes_in_buffer = size; + + /* 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 = bitmap_create(width, height, BITMAP_NEW | BITMAP_OPAQUE); - if (bitmap) - pixels = bitmap_get_buffer(bitmap); - if ((!bitmap) || (!pixels)) { + if (bitmap == NULL) { + /* empty bitmap could not be created */ jpeg_destroy_decompress(&cinfo); - if (bitmap) - bitmap_destroy(bitmap); + return NULL; + } - msg_data.error = messages_get("NoMemory"); - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - return false; + pixels = bitmap_get_buffer(bitmap); + if (pixels == NULL) { + /* bitmap with no buffer available */ + bitmap_destroy(bitmap); + jpeg_destroy_decompress(&cinfo); + return NULL; } + /* Convert scanlines from jpeg into bitmap */ rowstride = bitmap_get_rowstride(bitmap); do { + JSAMPROW scanlines[1]; #if RGB_RED != 0 || RGB_GREEN != 1 || RGB_BLUE != 2 || RGB_PIXELSIZE != 4 int i; -#endif - 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 /* expand to RGBA */ for (i = width - 1; 0 <= i; i--) { int r = scanlines[0][i * RGB_PIXELSIZE + RGB_RED]; @@ -251,6 +265,11 @@ static bool nsjpeg_convert(struct content *c) scanlines[0][i * 4 + 2] = b; scanlines[0][i * 4 + 3] = 0xff; } +#else + scanlines[0] = (JSAMPROW) (pixels + + rowstride * cinfo.output_scanline); + jpeg_read_scanlines(&cinfo, scanlines, 1); + #endif } while (cinfo.output_scanline != cinfo.output_height); bitmap_modified(bitmap); @@ -258,50 +277,65 @@ static bool nsjpeg_convert(struct content *c) jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); - c->width = width; - c->height = height; - jpeg_content->bitmap = bitmap; - snprintf(title, sizeof(title), messages_get("JPEGTitle"), - width, height, size); - content__set_title(c, title); - c->size += height * rowstride; - content_set_ready(c); - content_set_done(c); - /* Done: update status bar */ - content_set_status(c, ""); - return true; + return bitmap; } - /** - * Destroy a CONTENT_JPEG and free all resources it owns. + * Convert a CONTENT_JPEG for display. */ -static void nsjpeg_destroy(struct content *c) +static bool nsjpeg_convert(struct content *c) { - struct nsjpeg_content *jpeg_content = (nsjpeg_content *)c; + struct jpeg_decompress_struct cinfo; + struct nsjpeg_error_mgr jerr; + 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[100]; - if (jpeg_content->bitmap) { - bitmap_destroy(jpeg_content->bitmap); + /* check image header is valid and get width/height */ + data = content__get_source_data(c, &size); + + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = nsjpeg_error_exit; + jerr.pub.output_message = nsjpeg_error_log; + if (setjmp(jerr.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); + 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); -/** - * Redraw a CONTENT_JPEG with appropriate tiling. - */ -static bool nsjpeg_redraw(struct content *c, struct content_redraw_data *data, - const struct rect *clip, const struct redraw_context *ctx) -{ - struct nsjpeg_content *jpeg_content = (nsjpeg_content *)c; - bitmap_flags_t flags = BITMAPF_NONE; + c->width = cinfo.output_width; + c->height = cinfo.output_height; + c->size = c->width * c->height * 4; + + jpeg_destroy_decompress(&cinfo); - if (data->repeat_x) - flags |= BITMAPF_REPEAT_X; - if (data->repeat_y) - flags |= BITMAPF_REPEAT_Y; + image_cache_add(c, NULL, jpeg_cache_convert); - return ctx->plot->bitmap(data->x, data->y, data->width, data->height, - jpeg_content->bitmap, data->background_colour, flags); + snprintf(title, sizeof(title), messages_get("JPEGTitle"), + c->width, c->height, size); + content__set_title(c, title); + + content_set_ready(c); + content_set_done(c); + content_set_status(c, ""); /* Done: update status bar */ + + return true; } @@ -311,53 +345,41 @@ static bool nsjpeg_redraw(struct content *c, struct content_redraw_data *data, */ static nserror nsjpeg_clone(const struct content *old, struct content **newc) { - nsjpeg_content *jpeg_c; + struct content *jpeg_c; nserror error; - jpeg_c = talloc_zero(0, nsjpeg_content); + jpeg_c = talloc_zero(0, struct content); if (jpeg_c == NULL) return NSERROR_NOMEM; - error = content__clone(old, &jpeg_c->base); + error = content__clone(old, jpeg_c); if (error != NSERROR_OK) { - content_destroy(&jpeg_c->base); + 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->base) == false) { - content_destroy(&jpeg_c->base); + if (nsjpeg_convert(jpeg_c) == false) { + content_destroy(jpeg_c); return NSERROR_CLONE_FAILED; } } - *newc = (struct content *)jpeg_c; + *newc = jpeg_c; return NSERROR_OK; } -static void *nsjpeg_get_internal(const struct content *c, void *context) -{ - nsjpeg_content *jpeg_c = (nsjpeg_content *)c; - - return jpeg_c->bitmap; -} - -static content_type nsjpeg_content_type(void) -{ - return CONTENT_IMAGE; -} - static const content_handler nsjpeg_content_handler = { .create = nsjpeg_create, .data_complete = nsjpeg_convert, - .destroy = nsjpeg_destroy, - .redraw = nsjpeg_redraw, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, .clone = nsjpeg_clone, - .get_internal = nsjpeg_get_internal, - .type = nsjpeg_content_type, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, .no_share = false, }; diff --git a/image/png.c b/image/png.c index b2dc0e145..a94e1a68b 100644 --- a/image/png.c +++ b/image/png.c @@ -30,6 +30,7 @@ #include "content/content_protected.h" #include "image/bitmap.h" +#include "image/image_cache.h" #include "image/png.h" #include "utils/log.h" @@ -51,6 +52,7 @@ 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; @@ -64,6 +66,13 @@ 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 */ @@ -78,7 +87,58 @@ static void nspng_warning(png_structp png_ptr, png_const_charp warning_message) static void nspng_error(png_structp png_ptr, png_const_charp error_message) { LOG(("%s", error_message)); - longjmp(png_jmpbuf(png_ptr), 1); + 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); } /** @@ -87,62 +147,40 @@ static void nspng_error(png_structp png_ptr, png_const_charp error_message) */ static void info_callback(png_structp png_s, png_infop info) { - int bit_depth, color_type, interlace, intent; - double gamma; + int interlace; png_uint_32 width, height; nspng_content *png_c = png_get_progressive_ptr(png_s); - /* Read the PNG details */ - png_get_IHDR(png_s, info, &width, &height, &bit_depth, - &color_type, &interlace, 0, 0); + 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 = bitmap_create(width, height, BITMAP_NEW); if (png_c->bitmap == NULL) { - /* Failed -- bail out */ - longjmp(png_jmpbuf(png_s), 1); + /* Failed to create bitmap skip pre-conversion */ + longjmp(png_jmpbuf(png_s), CBERR_NOPRE); } png_c->rowstride = bitmap_get_rowstride(png_c->bitmap); png_c->bpp = bitmap_get_bpp(png_c->bitmap); - /* Set up our transformations */ - if (color_type == PNG_COLOR_TYPE_PALETTE) - png_set_palette_to_rgb(png_s); - if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8)) - png_set_expand_gray_1_2_4_to_8(png_s); - if (png_get_valid(png_s, info, PNG_INFO_tRNS)) - png_set_tRNS_to_alpha(png_s); - if (bit_depth == 16) - png_set_strip_16(png_s); - if (color_type == PNG_COLOR_TYPE_GRAY || - color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png_s); - if (!(color_type & PNG_COLOR_MASK_ALPHA)) - png_set_filler(png_s, 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_s, info, &intent)) { - png_set_gamma(png_s, 2.2, 0.45455); - } else { - if (png_get_gAMA(png_s, info, &gamma)) { - png_set_gamma(png_s, 2.2, gamma); - } else { - png_set_gamma(png_s, 2.2, 0.45455); - } - } - - png_read_update_info(png_s, info); + nspng_setup_transforms(png_s, info); png_c->rowbytes = png_get_rowbytes(png_s, info); png_c->interlace = (interlace == PNG_INTERLACE_ADAM7); - png_c->base.width = width; - png_c->base.height = height; - LOG(("size %li * %li, bpp %i, rowbytes %zu", (unsigned long)width, - (unsigned long)height, bit_depth, png_c->rowbytes)); + LOG(("size %li * %li, rowbytes %zu", (unsigned long)width, + (unsigned long)height, png_c->rowbytes)); } static void row_callback(png_structp png_s, png_bytep new_row, @@ -276,30 +314,191 @@ static nserror nspng_create(const content_handler *handler, } -static bool nspng_process_data(struct content *c, const char *data, +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; + bool ret = true; - if (setjmp(png_jmpbuf(png_c->png))) { - png_destroy_read_struct(&png_c->png, &png_c->info, 0); - LOG(("Failed to process data")); - png_c->png = NULL; - png_c->info = NULL; + 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) { - bitmap_destroy(png_c->bitmap); - 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; + } - msg_data.error = messages_get("PNGError"); - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - return false; + 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; } - png_process_data(png_c->png, png_c->info, (uint8_t *)data, size); + if (length == 0) { + png_error(png_ptr, "Read Error"); + } - return true; + 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 = bitmap_get_height(bitmap); + unsigned char *buffer= bitmap_get_buffer(bitmap); + size_t rowstride = bitmap_get_rowstride(bitmap); + png_bytep *row_ptrs; + int hloop; + + 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; + struct bitmap *bitmap = NULL; + struct png_cache_read_data_s png_cache_read_data; + png_uint_32 width, height; + png_bytep *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 (png_ptr == NULL) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return NULL; + } + + end_info = png_create_info_struct(png_ptr); + if (png_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 = bitmap_create(width, height, BITMAP_NEW); + if (bitmap == NULL) { + /* cleanup and bail */ + goto png_cache_convert_error; + } + + row_pointers = calc_row_pointers(bitmap); + + if (row_pointers != NULL) { + png_read_image(png_ptr, row_pointers); + } + +png_cache_convert_error: + + /* cleanup png read */ + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + + free(row_pointers); + + return bitmap; } static bool nspng_convert(struct content *c) @@ -310,26 +509,21 @@ static bool nspng_convert(struct content *c) assert(png_c->png != NULL); assert(png_c->info != NULL); - if (png_c->bitmap == NULL) { - union content_msg_data msg_data; - - msg_data.error = messages_get("PNGError"); - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - return false; - } - /* clean up png structures */ png_destroy_read_struct(&png_c->png, &png_c->info, 0); - c->size += (c->width * c->height * 4); - /* set title text */ snprintf(title, sizeof(title), messages_get("PNGTitle"), c->width, c->height, c->size); + content__set_title(c, title); - bitmap_set_opaque(png_c->bitmap, bitmap_test_opaque(png_c->bitmap)); - bitmap_modified(png_c->bitmap); + if (png_c->bitmap != NULL) { + bitmap_set_opaque(png_c->bitmap, bitmap_test_opaque(png_c->bitmap)); + bitmap_modified(png_c->bitmap); + } + + image_cache_add(c, png_c->bitmap, png_cache_convert); content_set_ready(c); content_set_done(c); @@ -339,33 +533,6 @@ static bool nspng_convert(struct content *c) } -static void nspng_destroy(struct content *c) -{ - nspng_content *png_c = (nspng_content *) c; - - if (png_c->bitmap != NULL) { - bitmap_destroy(png_c->bitmap); - } -} - - -static bool nspng_redraw(struct content *c, struct content_redraw_data *data, - const struct rect *clip, const struct redraw_context *ctx) -{ - nspng_content *png_c = (nspng_content *) c; - bitmap_flags_t flags = BITMAPF_NONE; - - assert(png_c->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, - png_c->bitmap, data->background_colour, flags); -} - static nserror nspng_clone(const struct content *old_c, struct content **new_c) { nspng_content *clone_png_c; @@ -411,27 +578,15 @@ static nserror nspng_clone(const struct content *old_c, struct content **new_c) return NSERROR_OK; } -static void *nspng_get_internal(const struct content *c, void *context) -{ - nspng_content *png_c = (nspng_content *) c; - - return png_c->bitmap; -} - -static content_type nspng_content_type(void) -{ - return CONTENT_IMAGE; -} - static const content_handler nspng_content_handler = { .create = nspng_create, .process_data = nspng_process_data, .data_complete = nspng_convert, - .destroy = nspng_destroy, - .redraw = nspng_redraw, .clone = nspng_clone, - .get_internal = nspng_get_internal, - .type = nspng_content_type, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, .no_share = false, }; -- cgit v1.2.3