diff options
Diffstat (limited to 'content/handlers/image')
-rw-r--r-- | content/handlers/image/Makefile | 7 | ||||
-rw-r--r-- | content/handlers/image/bmp.c | 48 | ||||
-rw-r--r-- | content/handlers/image/bmp.h | 13 | ||||
-rw-r--r-- | content/handlers/image/gif.c | 412 | ||||
-rw-r--r-- | content/handlers/image/ico.c | 90 | ||||
-rw-r--r-- | content/handlers/image/image.c | 17 | ||||
-rw-r--r-- | content/handlers/image/image_cache.c | 37 | ||||
-rw-r--r-- | content/handlers/image/image_cache.h | 6 | ||||
-rw-r--r-- | content/handlers/image/jpeg.c | 208 | ||||
-rw-r--r-- | content/handlers/image/jpegxl.c | 340 | ||||
-rw-r--r-- | content/handlers/image/jpegxl.h | 28 | ||||
-rw-r--r-- | content/handlers/image/nssprite.c | 47 | ||||
-rw-r--r-- | content/handlers/image/png.c | 74 | ||||
-rw-r--r-- | content/handlers/image/rsvg.c | 77 | ||||
-rw-r--r-- | content/handlers/image/rsvg246.c | 280 | ||||
-rw-r--r-- | content/handlers/image/svg.c | 23 | ||||
-rw-r--r-- | content/handlers/image/webp.c | 260 | ||||
-rw-r--r-- | content/handlers/image/webp.h | 29 |
18 files changed, 1553 insertions, 443 deletions
diff --git a/content/handlers/image/Makefile b/content/handlers/image/Makefile index 541cd2cf9..ac052b37a 100644 --- a/content/handlers/image/Makefile +++ b/content/handlers/image/Makefile @@ -3,13 +3,16 @@ # 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_BMP) += bmp.c S_IMAGE_$(NETSURF_USE_GIF) += gif.c +S_IMAGE_$(NETSURF_USE_BMP) += ico.c S_IMAGE_$(NETSURF_USE_JPEG) += jpeg.c +S_IMAGE_$(NETSURF_USE_JPEGXL) += jpegxl.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_RSVG) += rsvg$(RSVG_API).c S_IMAGE_$(NETSURF_USE_VIDEO) += video.c +S_IMAGE_$(NETSURF_USE_WEBP) += webp.c S_IMAGE := $(S_IMAGE_YES) diff --git a/content/handlers/image/bmp.c b/content/handlers/image/bmp.c index 48a37fb24..7139d86b4 100644 --- a/content/handlers/image/bmp.c +++ b/content/handlers/image/bmp.c @@ -33,7 +33,9 @@ #include "netsurf/content.h" #include "content/llcache.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/bmp.h" @@ -56,12 +58,12 @@ typedef struct nsbmp_content { */ static void *nsbmp_bitmap_create(int width, int height, unsigned int bmp_state) { - unsigned int bitmap_state = BITMAP_NEW; + unsigned int bitmap_state = BITMAP_NONE; /* 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; + BITMAP_CLEAR : 0; /* return the created bitmap */ return guit->bitmap->create(width, height, bitmap_state); @@ -73,12 +75,11 @@ static nserror nsbmp_create_bmp_data(nsbmp_content *bmp) .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); + bmp->bmp = calloc(1, sizeof(struct bmp_image)); if (bmp->bmp == NULL) { - content_broadcast_errorcode(&bmp->base, NSERROR_NOMEM); + content_broadcast_error(&bmp->base, NSERROR_NOMEM, NULL); return NSERROR_NOMEM; } @@ -87,10 +88,14 @@ static nserror nsbmp_create_bmp_data(nsbmp_content *bmp) 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) +static nserror +nsbmp_create(const struct 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; @@ -122,8 +127,8 @@ static bool nsbmp_convert(struct content *c) nsbmp_content *bmp = (nsbmp_content *) c; bmp_result res; uint32_t swidth; - const char *data; - unsigned long size; + const uint8_t *data; + size_t size; char *title; /* set the bmp data */ @@ -135,19 +140,18 @@ static bool nsbmp_convert(struct content *c) case BMP_OK: break; case BMP_INSUFFICIENT_MEMORY: - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); return false; case BMP_INSUFFICIENT_DATA: case BMP_DATA_ERROR: - content_broadcast_errorcode(c, NSERROR_BMP_ERROR); + content_broadcast_error(c, NSERROR_BMP_ERROR, NULL); 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; + swidth = sizeof(uint32_t) * bmp->bmp->width; c->size += (swidth * bmp->bmp->height) + 16 + 44; /* set title text */ @@ -186,6 +190,9 @@ static bool nsbmp_redraw(struct content *c, struct content_redraw_data *data, return false; } + bitmap_format_to_client(bmp->bitmap, &(bitmap_fmt_t) { + .layout = BITMAP_LAYOUT_R8G8B8A8, + }); guit->bitmap->modified(bmp->bitmap); } @@ -259,6 +266,16 @@ static content_type nsbmp_content_type(void) return CONTENT_IMAGE; } +static bool nsbmp_content_is_opaque(struct content *c) +{ + nsbmp_content *bmp = (nsbmp_content *)c; + + if (bmp->bitmap != NULL) { + return guit->bitmap->get_opaque(bmp->bitmap); + } + + return false; +} static const content_handler nsbmp_content_handler = { .create = nsbmp_create, @@ -268,6 +285,7 @@ static const content_handler nsbmp_content_handler = { .clone = nsbmp_clone, .get_internal = nsbmp_get_internal, .type = nsbmp_content_type, + .is_opaque = nsbmp_content_is_opaque, .no_share = false, }; diff --git a/content/handlers/image/bmp.h b/content/handlers/image/bmp.h index f3b398584..d3d7623ed 100644 --- a/content/handlers/image/bmp.h +++ b/content/handlers/image/bmp.h @@ -17,16 +17,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** \file - * Content for image/bmp (interface). +/** + * \file + * interface to image/bmp content handler initialisation. */ -#ifndef _NETSURF_IMAGE_BMP_H_ -#define _NETSURF_IMAGE_BMP_H_ - -#include <libnsbmp.h> - -extern bmp_bitmap_callback_vt bmp_bitmap_callbacks; /** Only to be used by ICO code. */ +#ifndef NETSURF_IMAGE_BMP_H_ +#define NETSURF_IMAGE_BMP_H_ nserror nsbmp_init(void); diff --git a/content/handlers/image/gif.c b/content/handlers/image/gif.c index 253265caa..4671d1df5 100644 --- a/content/handlers/image/gif.c +++ b/content/handlers/image/gif.c @@ -34,8 +34,12 @@ #include <string.h> #include <stdbool.h> #include <stdlib.h> -#include <libnsgif.h> +#include <nsutils/assert.h> + +#include <nsgif.h> + +#include "utils/log.h" #include "utils/utils.h" #include "utils/messages.h" #include "utils/nsoption.h" @@ -43,19 +47,37 @@ #include "netsurf/bitmap.h" #include "netsurf/content.h" #include "content/llcache.h" +#include "content/content.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/image.h" #include "image/gif.h" -typedef struct nsgif_content { +typedef struct gif_content { struct content base; - struct gif_animation *gif; /**< GIF animation data */ - int current_frame; /**< current frame to display [0...(max-1)] */ -} nsgif_content; + nsgif_t *gif; /**< GIF animation data */ + uint32_t current_frame; /**< current frame to display [0...(max-1)] */ +} gif_content; +static inline nserror gif__nsgif_error_to_ns(nsgif_error gif_res) +{ + nserror err; + + switch (gif_res) { + case NSGIF_ERR_OOM: + err = NSERROR_NOMEM; + break; + default: + err = NSERROR_GIF_ERROR; + break; + } + + return err; +} /** * Callback for libnsgif; forwards the call to bitmap_create() @@ -64,44 +86,60 @@ typedef struct nsgif_content { * \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) +static void *gif_bitmap_create(int width, int height) { - return guit->bitmap->create(width, height, BITMAP_NEW); + return guit->bitmap->create(width, height, BITMAP_NONE); } +/** + * Convert client bitmap format to a LibNSGIF format specifier. + */ +static nsgif_bitmap_fmt_t nsgif__get_bitmap_format(void) +{ + ns_static_assert((int)BITMAP_LAYOUT_R8G8B8A8 == (int)NSGIF_BITMAP_FMT_R8G8B8A8); + ns_static_assert((int)BITMAP_LAYOUT_B8G8R8A8 == (int)NSGIF_BITMAP_FMT_B8G8R8A8); + ns_static_assert((int)BITMAP_LAYOUT_A8R8G8B8 == (int)NSGIF_BITMAP_FMT_A8R8G8B8); + ns_static_assert((int)BITMAP_LAYOUT_A8B8G8R8 == (int)NSGIF_BITMAP_FMT_A8B8G8R8); + ns_static_assert((int)BITMAP_LAYOUT_RGBA8888 == (int)NSGIF_BITMAP_FMT_RGBA8888); + ns_static_assert((int)BITMAP_LAYOUT_BGRA8888 == (int)NSGIF_BITMAP_FMT_BGRA8888); + ns_static_assert((int)BITMAP_LAYOUT_ARGB8888 == (int)NSGIF_BITMAP_FMT_ARGB8888); + ns_static_assert((int)BITMAP_LAYOUT_ABGR8888 == (int)NSGIF_BITMAP_FMT_ABGR8888); + + return (nsgif_bitmap_fmt_t)bitmap_fmt.layout; +} -static nserror nsgif_create_gif_data(nsgif_content *c) +static nserror gif_create_gif_data(gif_content *c) { - 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 + nsgif_error gif_res; + const nsgif_bitmap_cb_vt gif_bitmap_callbacks = { + .create = gif_bitmap_create, + .destroy = guit->bitmap->destroy, + .get_buffer = guit->bitmap->get_buffer, + .set_opaque = guit->bitmap->set_opaque, + .test_opaque = bitmap_test_opaque, + .modified = guit->bitmap->modified, }; - /* Initialise our data structure */ - c->gif = calloc(sizeof(gif_animation), 1); - if (c->gif == NULL) { - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - return NSERROR_NOMEM; + gif_res = nsgif_create(&gif_bitmap_callbacks, + nsgif__get_bitmap_format(), &c->gif); + if (gif_res != NSGIF_OK) { + nserror err = gif__nsgif_error_to_ns(gif_res); + content_broadcast_error(&c->base, err, NULL); + return err; } - 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, +static nserror gif_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; + gif_content *result; nserror error; - result = calloc(1, sizeof(nsgif_content)); + result = calloc(1, sizeof(gif_content)); if (result == NULL) return NSERROR_NOMEM; @@ -112,7 +150,7 @@ static nserror nsgif_create(const content_handler *handler, return error; } - error = nsgif_create_gif_data(result); + error = gif_create_gif_data(result); if (error != NSERROR_OK) { free(result); return error; @@ -124,157 +162,98 @@ static nserror nsgif_create(const content_handler *handler, } /** + * Scheduler callback. Performs any necessary animation. + * + * \param p The content to animate +*/ +static void gif_animate_cb(void *p); + +/** * Performs any necessary animation. * * \param p The content to animate */ -static void nsgif_animate(void *p) +static nserror gif__animate(gif_content *gif, bool redraw) { - 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; - } - } + nsgif_error gif_res; + nsgif_rect_t rect; + uint32_t delay; + uint32_t f; + + gif_res = nsgif_frame_prepare(gif->gif, &rect, &delay, &f); + if (gif_res != NSGIF_OK) { + return gif__nsgif_error_to_ns(gif_res); } + gif->current_frame = f; + /* 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) && delay != NSGIF_INFINITE) { + guit->misc->schedule(delay * 10, gif_animate_cb, gif); } - if ((!nsoption_bool(animate_images)) || - (!gif->gif->frames[gif->current_frame].display)) { - return; - } + if (redraw) { + union content_msg_data data; - /* 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; - } + /* area within gif to redraw */ + data.redraw.x = rect.x0; + data.redraw.y = rect.y0; + data.redraw.width = rect.x1 - rect.x0; + data.redraw.height = rect.y1 - rect.y0; + + content_broadcast(&gif->base, CONTENT_MSG_REDRAW, &data); } - /* 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; + return NSERROR_OK; +} - content_broadcast(&gif->base, CONTENT_MSG_REDRAW, &data); +static void gif_animate_cb(void *p) +{ + gif_content *gif = p; + + gif__animate(gif, true); } -static bool nsgif_convert(struct content *c) +static bool gif_convert(struct content *c) { - nsgif_content *gif = (nsgif_content *) c; - int res; - const char *data; - unsigned long size; + gif_content *gif = (gif_content *) c; + const nsgif_info_t *gif_info; + const uint8_t *data; + nsgif_error gif_err; + nserror err; + size_t 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) { - nserror error = NSERROR_UNKNOWN; - switch (res) { - case GIF_FRAME_DATA_ERROR: - case GIF_INSUFFICIENT_DATA: - case GIF_DATA_ERROR: - error = NSERROR_GIF_ERROR; - break; - case GIF_INSUFFICIENT_MEMORY: - error = NSERROR_NOMEM; - break; - } - content_broadcast_errorcode(c, error); - return false; - } - } while (res != GIF_OK && res != GIF_INSUFFICIENT_FRAME_DATA); + gif_err = nsgif_data_scan(gif->gif, size, data); + if (gif_err != NSGIF_OK) { + NSLOG(netsurf, INFO, "nsgif scan: %s", nsgif_strerror(gif_err)); + /* Not fatal unless we have no frames. */ + } + + nsgif_data_complete(gif->gif); + + gif_info = nsgif_get_info(gif->gif); + assert(gif_info != NULL); /* Abort on bad GIFs */ - if ((gif->gif->frame_count_partial == 0) || (gif->gif->width == 0) || - (gif->gif->height == 0)) { - content_broadcast_errorcode(c, NSERROR_GIF_ERROR); + if (gif_info->frame_count == 0) { + err = gif__nsgif_error_to_ns(gif_err); + content_broadcast_error(c, err, "GIF with no frames."); + return false; + } else if (gif_info->width == 0 || gif_info->height == 0) { + err = gif__nsgif_error_to_ns(gif_err); + content_broadcast_error(c, err, "Zero size image."); 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; + c->width = gif_info->width; + c->height = gif_info->height; + c->size += (gif_info->width * gif_info->height * 4) + 16 + 44; /* set title text */ title = messages_get_buff("GIFTitle", @@ -285,12 +264,11 @@ static bool nsgif_convert(struct content *c) 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); + err = gif__animate(gif, false); + if (err != NSERROR_OK) { + content_broadcast_error(c, NSERROR_GIF_ERROR, NULL); + return false; + } /* Exit as a success */ content_set_ready(c); @@ -301,68 +279,51 @@ static bool nsgif_convert(struct content *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. + * \return NSGIF_OK on success else apropriate error code. */ -static gif_result nsgif_get_frame(nsgif_content *gif) +static nsgif_error gif_get_frame(gif_content *gif, + nsgif_bitmap_t **bitmap) { - int previous_frame, current_frame, frame; - gif_result res = GIF_OK; - - current_frame = gif->current_frame; + uint32_t 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; + return nsgif_frame_decode(gif->gif, current_frame, bitmap); } -static bool nsgif_redraw(struct content *c, struct content_redraw_data *data, +static bool gif_redraw(struct content *c, struct content_redraw_data *data, const struct rect *clip, const struct redraw_context *ctx) { - nsgif_content *gif = (nsgif_content *) c; + gif_content *gif = (gif_content *) c; + nsgif_bitmap_t *bitmap; - if (gif->current_frame != gif->gif->decoded_frame) { - if (nsgif_get_frame(gif) != GIF_OK) { - return false; - } + if (gif_get_frame(gif, &bitmap) != NSGIF_OK) { + return false; } - return image_bitmap_plot(gif->gif->frame_image, data, clip, ctx); + return image_bitmap_plot(bitmap, data, clip, ctx); } - -static void nsgif_destroy(struct content *c) +static void gif_destroy(struct content *c) { - nsgif_content *gif = (nsgif_content *) c; + gif_content *gif = (gif_content *) c; /* Free all the associated memory buffers */ - guit->misc->schedule(-1, nsgif_animate, c); - gif_finalise(gif->gif); - free(gif->gif); + guit->misc->schedule(-1, gif_animate_cb, c); + nsgif_destroy(gif->gif); } - -static nserror nsgif_clone(const struct content *old, struct content **newc) +static nserror gif_clone(const struct content *old, struct content **newc) { - nsgif_content *gif; + gif_content *gif; nserror error; - gif = calloc(1, sizeof(nsgif_content)); + gif = calloc(1, sizeof(gif_content)); if (gif == NULL) return NSERROR_NOMEM; @@ -373,7 +334,7 @@ static nserror nsgif_clone(const struct content *old, struct content **newc) } /* Simply replay creation and conversion of content */ - error = nsgif_create_gif_data(gif); + error = gif_create_gif_data(gif); if (error != NSERROR_OK) { content_destroy(&gif->base); return error; @@ -381,7 +342,7 @@ static nserror nsgif_clone(const struct content *old, struct content **newc) if (old->status == CONTENT_STATUS_READY || old->status == CONTENT_STATUS_DONE) { - if (nsgif_convert(&gif->base) == false) { + if (gif_convert(&gif->base) == false) { content_destroy(&gif->base); return NSERROR_CLONE_FAILED; } @@ -392,9 +353,9 @@ static nserror nsgif_clone(const struct content *old, struct content **newc) return NSERROR_OK; } -static void nsgif_add_user(struct content *c) +static void gif_add_user(struct content *c) { - nsgif_content *gif = (nsgif_content *) c; + gif_content *gif = (gif_content *) c; /* Ensure this content has already been converted. * If it hasn't, the animation will start at the conversion phase instead. */ @@ -402,53 +363,66 @@ static void nsgif_add_user(struct content *c) 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); + if (nsgif_reset(gif->gif) == NSGIF_OK) { + gif__animate(gif, true); } } } -static void nsgif_remove_user(struct content *c) +static void gif_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); + guit->misc->schedule(-1, gif_animate_cb, c); } } -static void *nsgif_get_internal(const struct content *c, void *context) +static nsgif_bitmap_t *gif_get_bitmap( + const struct content *c, void *context) { - nsgif_content *gif = (nsgif_content *) c; + gif_content *gif = (gif_content *) c; + nsgif_bitmap_t *bitmap; - if (gif->current_frame != gif->gif->decoded_frame) { - if (nsgif_get_frame(gif) != GIF_OK) - return NULL; + if (gif_get_frame(gif, &bitmap) != NSGIF_OK) { + return NULL; } - return gif->gif->frame_image; + return bitmap; } -static content_type nsgif_content_type(void) +static content_type gif_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, +static bool gif_content_is_opaque(struct content *c) +{ + gif_content *gif = (gif_content *) c; + nsgif_bitmap_t *bitmap; + + if (gif_get_frame(gif, &bitmap) != NSGIF_OK) { + return false; + } + + return guit->bitmap->get_opaque(bitmap); +} + +static const content_handler gif_content_handler = { + .create = gif_create, + .data_complete = gif_convert, + .destroy = gif_destroy, + .redraw = gif_redraw, + .clone = gif_clone, + .add_user = gif_add_user, + .remove_user = gif_remove_user, + .get_internal = gif_get_bitmap, + .type = gif_content_type, + .is_opaque = gif_content_is_opaque, .no_share = false, }; -static const char *nsgif_types[] = { +static const char *gif_types[] = { "image/gif" }; -CONTENT_FACTORY_REGISTER_TYPES(nsgif, nsgif_types, nsgif_content_handler); +CONTENT_FACTORY_REGISTER_TYPES(nsgif, gif_types, gif_content_handler); diff --git a/content/handlers/image/ico.c b/content/handlers/image/ico.c index 85aab9f64..6bca2a137 100644 --- a/content/handlers/image/ico.c +++ b/content/handlers/image/ico.c @@ -16,8 +16,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** \file - * Content for image/ico (implementation) +/** + * \file + * implementation for image/ico content handler */ #include <stdbool.h> @@ -31,7 +32,9 @@ #include "netsurf/content.h" #include "content/llcache.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/image.h" #include "image/ico.h" @@ -39,8 +42,7 @@ typedef struct nsico_content { struct content base; - struct ico_collection *ico; /** ICO collection data */ - + struct ico_collection *ico; /** ICO collection data */ } nsico_content; /** @@ -53,12 +55,12 @@ typedef struct nsico_content { */ static void *nsico_bitmap_create(int width, int height, unsigned int bmp_state) { - unsigned int bitmap_state = BITMAP_NEW; + unsigned int bitmap_state = BITMAP_NONE; /* 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; + BITMAP_CLEAR : 0; /* return the created bitmap */ return guit->bitmap->create(width, height, bitmap_state); @@ -70,12 +72,11 @@ static nserror nsico_create_ico_data(nsico_content *c) .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); + c->ico = calloc(1, sizeof(ico_collection)); if (c->ico == NULL) { - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return NSERROR_NOMEM; } ico_collection_create(c->ico, &bmp_bitmap_callbacks); @@ -83,7 +84,7 @@ static nserror nsico_create_ico_data(nsico_content *c) } -static nserror nsico_create(const content_handler *handler, +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) @@ -120,8 +121,8 @@ static bool nsico_convert(struct content *c) nsico_content *ico = (nsico_content *) c; struct bmp_image *bmp; bmp_result res; - const char *data; - unsigned long size; + const uint8_t *data; + size_t size; char *title; /* set the ico data */ @@ -134,11 +135,11 @@ static bool nsico_convert(struct content *c) case BMP_OK: break; case BMP_INSUFFICIENT_MEMORY: - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); return false; case BMP_INSUFFICIENT_DATA: case BMP_DATA_ERROR: - content_broadcast_errorcode(c, NSERROR_ICO_ERROR); + content_broadcast_error(c, NSERROR_ICO_ERROR, NULL); return false; } @@ -172,6 +173,23 @@ static bool nsico_convert(struct content *c) return true; } +static bool nsico__decode(struct bmp_image *ico) +{ + if (ico->decoded == false) { + NSLOG(netsurf, DEBUG, "Decoding ICO %p", ico); + if (bmp_decode(ico) != BMP_OK) { + return false; + } + + bitmap_format_to_client(ico->bitmap, &(bitmap_fmt_t) { + .layout = BITMAP_LAYOUT_R8G8B8A8, + }); + guit->bitmap->modified(ico->bitmap); + + } + + return true; +} static bool nsico_redraw(struct content *c, struct content_redraw_data *data, const struct rect *clip, const struct redraw_context *ctx) @@ -188,14 +206,8 @@ static bool nsico_redraw(struct content *c, struct content_redraw_data *data, } /* ensure its decided */ - if (bmp->decoded == false) { - if (bmp_decode(bmp) != BMP_OK) { - return false; - } else { - NSLOG(netsurf, INFO, "Decoding bitmap"); - guit->bitmap->modified(bmp->bitmap); - } - + if (!nsico__decode(bmp)) { + return false; } return image_bitmap_plot(bmp->bitmap, data, clip, ctx); @@ -250,7 +262,7 @@ 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; + struct bmp_image *bmp; bmp = ico_find(ico->ico, 16, 16); if (bmp == NULL) { @@ -259,12 +271,8 @@ static void *nsico_get_internal(const struct content *c, void *context) return NULL; } - if (bmp->decoded == false) { - if (bmp_decode(bmp) != BMP_OK) { - return NULL; - } else { - guit->bitmap->modified(bmp->bitmap); - } + if (!nsico__decode(bmp)) { + return NULL; } return bmp->bitmap; @@ -275,6 +283,29 @@ static content_type nsico_content_type(void) return CONTENT_IMAGE; } +static bool nsico_is_opaque(struct content *c) +{ + nsico_content *ico = (nsico_content *) c; + struct bmp_image *bmp; + + /** + * \todo Pick best size for purpose. Currently assumes + * it's for a URL bar. + */ + bmp = ico_find(ico->ico, 16, 16); + if (bmp == NULL) { + /* return error */ + NSLOG(netsurf, INFO, "Failed to select icon"); + return false; + } + + if (!nsico__decode(bmp)) { + return false; + } + + return guit->bitmap->get_opaque(bmp->bitmap); +} + static const content_handler nsico_content_handler = { .create = nsico_create, .data_complete = nsico_convert, @@ -283,6 +314,7 @@ static const content_handler nsico_content_handler = { .clone = nsico_clone, .get_internal = nsico_get_internal, .type = nsico_content_type, + .is_opaque = nsico_is_opaque, .no_share = false, }; diff --git a/content/handlers/image/image.c b/content/handlers/image/image.c index 675fdd691..2bd5f5f8d 100644 --- a/content/handlers/image/image.c +++ b/content/handlers/image/image.c @@ -26,15 +26,18 @@ #include "netsurf/bitmap.h" #include "netsurf/content.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/bmp.h" #include "image/gif.h" #include "image/ico.h" #include "image/jpeg.h" +#include "image/jpegxl.h" #include "image/nssprite.h" #include "image/png.h" #include "image/rsvg.h" #include "image/svg.h" +#include "image/webp.h" #include "image/image.h" /** @@ -70,6 +73,12 @@ nserror image_init(void) return error; #endif +#ifdef WITH_JPEGXL + error = nsjpegxl_init(); + if (error != NSERROR_OK) + return error; +#endif + #ifdef WITH_PNG error = nspng_init(); if (error != NSERROR_OK) @@ -94,6 +103,12 @@ nserror image_init(void) return error; #endif +#ifdef WITH_WEBP + error = nswebp_init(); + if (error != NSERROR_OK) + return error; +#endif + return error; } @@ -117,7 +132,7 @@ bool image_bitmap_plot(struct bitmap *bitmap, if (height == 1) { /* optimise 1x1 bitmap plot */ pixel = guit->bitmap->get_buffer(bitmap); - fill_style.fill_colour = pixel_to_colour(pixel); + fill_style.fill_colour = bitmap_pixel_to_colour(pixel); if (guit->bitmap->get_opaque(bitmap) || ((fill_style.fill_colour & 0xff000000) == 0xff000000)) { diff --git a/content/handlers/image/image_cache.c b/content/handlers/image/image_cache.c index a1de01da5..8f7ff89ff 100644 --- a/content/handlers/image/image_cache.c +++ b/content/handlers/image/image_cache.c @@ -438,6 +438,7 @@ image_cache_init(const struct image_cache_parameters *image_cache_parameters) nserror image_cache_fini(void) { unsigned int op_count; + uint64_t op_size; guit->misc->schedule(-1, image_cache__background_update, image_cache); @@ -452,6 +453,10 @@ nserror image_cache_fini(void) image_cache->miss_count + image_cache->fail_count; + op_size = image_cache->hit_size + + image_cache->miss_size + + image_cache->fail_size; + NSLOG(netsurf, INFO, "Age %ds", image_cache->current_age / 1000); NSLOG(netsurf, INFO, "Peak size %"PRIsizet" (in %d)", image_cache->max_bitmap_size, @@ -460,12 +465,7 @@ nserror image_cache_fini(void) 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; + if ((op_count > 0) && (op_size >0)) { NSLOG(netsurf, INFO, "Cache total/hit/miss/fail (counts) %d/%d/%d/%d (100%%/%d%%/%d%%/%d%%)", @@ -528,7 +528,7 @@ nserror image_cache_add(struct content *content, image_cache__link(centry); centry->content = content; - centry->bitmap_size = content->width * content->height * 4; + centry->bitmap_size = content->width * content->height * 4llu; } NSLOG(netsurf, INFO, "centry %p, content %p, bitmap %p", centry, @@ -632,15 +632,15 @@ case chr : \ slen++; break; - FMTCHR('a', PRIssizet, params.limit); - FMTCHR('b', PRIssizet, params.hysteresis); - FMTCHR('c', PRIssizet, total_bitmap_size); + FMTCHR('a', PRIsizet, params.limit); + FMTCHR('b', PRIsizet, params.hysteresis); + FMTCHR('c', PRIsizet, total_bitmap_size); FMTCHR('d', "d", bitmap_count); FMTCHR('e', "u", current_age / 1000); - FMTCHR('f', PRIssizet, max_bitmap_size); + FMTCHR('f', PRIsizet, max_bitmap_size); FMTCHR('g', "d", max_bitmap_size_count); FMTCHR('h', "d", max_bitmap_count); - FMTCHR('i', PRIssizet, max_bitmap_count_size); + FMTCHR('i', PRIsizet, max_bitmap_count_size); case 'j': @@ -770,7 +770,7 @@ image_cache_snentryf(char *string, if (centry->bitmap != NULL) { slen += snprintf(string + slen, size - slen, - "%" PRIssizet, + "%" PRIsizet, centry->bitmap_size); } else { slen += snprintf(string + slen, @@ -859,6 +859,17 @@ void *image_cache_get_internal(const struct content *c, void *context) } /* exported interface documented in image_cache.h */ +bool image_cache_is_opaque(struct content *c) +{ + struct bitmap *bmp; + bmp = image_cache_get_bitmap(c); + if (bmp != NULL) { + return guit->bitmap->get_opaque(bmp); + } + return false; +} + +/* exported interface documented in image_cache.h */ 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 index d57a3a956..1c2d621a6 100644 --- a/content/handlers/image/image_cache.h +++ b/content/handlers/image/image_cache.h @@ -40,6 +40,7 @@ #include "utils/errors.h" #include "netsurf/content_type.h" +struct content; struct content_redraw_data; struct redraw_context; @@ -170,7 +171,8 @@ int image_cache_snsummaryf(char *string, size_t size, const char *fmt); /********* Image content handler generic cache callbacks ************/ -/** Generic content redraw callback +/** + * Generic content redraw callback * * May be used by image content handlers as their redraw * callback. Performs all neccissary cache lookups and conversions and @@ -185,6 +187,8 @@ void image_cache_destroy(struct content *c); void *image_cache_get_internal(const struct content *c, void *context); +bool image_cache_is_opaque(struct content *c); + content_type image_cache_content_type(void); #endif diff --git a/content/handlers/image/jpeg.c b/content/handlers/image/jpeg.c index 123a0bf70..93372f15a 100644 --- a/content/handlers/image/jpeg.c +++ b/content/handlers/image/jpeg.c @@ -17,8 +17,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** \file - * Content for image/jpeg (implementation). +/** + * \file + * implementation of content handling for image/jpeg * * This implementation uses the IJG JPEG library. */ @@ -32,8 +33,11 @@ #include "utils/messages.h" #include "netsurf/bitmap.h" #include "content/llcache.h" +#include "content/content.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/image_cache.h" @@ -46,13 +50,8 @@ */ #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 */ +#ifndef LIBJPEG_TURBO_VERSION +#warning Using libjpeg (libjpeg-turbo is recommended) #endif static char nsjpeg_error_buffer[JMSG_LENGTH_MAX]; @@ -161,16 +160,107 @@ static void nsjpeg_error_exit(j_common_ptr cinfo) longjmp(*setjmp_buffer, 1); } +/** + * Convert scan lines from CMYK to core client bitmap layout. + */ +static inline void nsjpeg__decode_cmyk( + struct jpeg_decompress_struct *cinfo, + uint8_t * volatile pixels, + size_t rowstride) +{ + int width = cinfo->output_width * 4; + + do { + JSAMPROW scanlines[1] = { + [0] = (JSAMPROW) + (pixels + rowstride * cinfo->output_scanline), + }; + jpeg_read_scanlines(cinfo, scanlines, 1); + + for (int i = width - 4; 0 <= i; i -= 4) { + /* Trivial inverse CMYK -> RGBA */ + const int c = scanlines[0][i + 0]; + const int m = scanlines[0][i + 1]; + const int y = scanlines[0][i + 2]; + const int k = scanlines[0][i + 3]; + + const int ck = c * k; + const int mk = m * k; + const int yk = y * k; + +#define DIV255(x) ((x) + 1 + ((x) >> 8)) >> 8 + scanlines[0][i + bitmap_layout.r] = DIV255(ck); + scanlines[0][i + bitmap_layout.g] = DIV255(mk); + scanlines[0][i + bitmap_layout.b] = DIV255(yk); + scanlines[0][i + bitmap_layout.a] = 0xff; +#undef DIV255 + } + } while (cinfo->output_scanline != cinfo->output_height); +} + +/** + * Convert scan lines from CMYK to core client bitmap layout. + */ +static inline void nsjpeg__decode_rgb( + struct jpeg_decompress_struct *cinfo, + uint8_t * volatile pixels, + size_t rowstride) +{ +#if RGB_RED != 0 || RGB_GREEN != 1 || RGB_BLUE != 2 || RGB_PIXELSIZE != 4 + int width = cinfo->output_width; +#endif + + do { + JSAMPROW scanlines[1] = { + [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 */ + for (int 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 + bitmap_layout.r] = r; + scanlines[0][i * 4 + bitmap_layout.g] = g; + scanlines[0][i * 4 + bitmap_layout.b] = b; + scanlines[0][i * 4 + bitmap_layout.a] = 0xff; + } +#endif + } while (cinfo->output_scanline != cinfo->output_height); +} + +/** + * Convert scan lines from CMYK to core client bitmap layout. + */ +static inline void nsjpeg__decode_client_fmt( + struct jpeg_decompress_struct *cinfo, + uint8_t * volatile pixels, + size_t rowstride) +{ + do { + JSAMPROW scanlines[1] = { + [0] = (JSAMPROW) + (pixels + rowstride * cinfo->output_scanline), + }; + jpeg_read_scanlines(cinfo, scanlines, 1); + } while (cinfo->output_scanline != cinfo->output_height); +} + +/** + * create a bitmap from jpeg content. + */ 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 */ + const uint8_t *source_data; /* Jpeg source data */ + size_t 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; @@ -184,7 +274,7 @@ jpeg_cache_convert(struct content *c) nsjpeg_term_source }; /* obtain jpeg source data and perfom minimal sanity checks */ - source_data = (uint8_t *)content__get_source_data(c, &source_size); + source_data = content__get_source_data(c, &source_size); if ((source_data == NULL) || (source_size < MIN_JPEG_SIZE)) { @@ -215,21 +305,42 @@ jpeg_cache_convert(struct content *c) /* set output processing parameters */ if (cinfo.jpeg_color_space == JCS_CMYK || - cinfo.jpeg_color_space == JCS_YCCK) { + cinfo.jpeg_color_space == JCS_YCCK) { cinfo.out_color_space = JCS_CMYK; } else { +#ifdef JCS_ALPHA_EXTENSIONS + switch (bitmap_fmt.layout) { + case BITMAP_LAYOUT_R8G8B8A8: + cinfo.out_color_space = JCS_EXT_RGBA; + break; + case BITMAP_LAYOUT_B8G8R8A8: + cinfo.out_color_space = JCS_EXT_BGRA; + break; + case BITMAP_LAYOUT_A8R8G8B8: + cinfo.out_color_space = JCS_EXT_ARGB; + break; + case BITMAP_LAYOUT_A8B8G8R8: + cinfo.out_color_space = JCS_EXT_ABGR; + break; + default: + NSLOG(netsurf, ERROR, "Unexpected bitmap format: %u", + bitmap_fmt.layout); + jpeg_destroy_decompress(&cinfo); + return NULL; + } +#else cinfo.out_color_space = JCS_RGB; +#endif } 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); + bitmap = guit->bitmap->create( + cinfo.output_width, + cinfo.output_height, BITMAP_OPAQUE); if (bitmap == NULL) { /* empty bitmap could not be created */ jpeg_destroy_decompress(&cinfo); @@ -246,50 +357,21 @@ jpeg_cache_convert(struct content *c) /* 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); + switch (cinfo.out_color_space) { + case JCS_CMYK: + nsjpeg__decode_cmyk(&cinfo, pixels, rowstride); + break; - if (cinfo.out_color_space == JCS_CMYK) { - int i; - for (i = width - 1; 0 <= i; i--) { - /* Trivial inverse CMYK -> RGBA */ - const int c = scanlines[0][i * 4 + 0]; - const int m = scanlines[0][i * 4 + 1]; - const int y = scanlines[0][i * 4 + 2]; - const int k = scanlines[0][i * 4 + 3]; + case JCS_RGB: + nsjpeg__decode_rgb(&cinfo, pixels, rowstride); + break; - const int ck = c * k; - const int mk = m * k; - const int yk = y * k; + default: + nsjpeg__decode_client_fmt(&cinfo, pixels, rowstride); + break; + } -#define DIV255(x) ((x) + 1 + ((x) >> 8)) >> 8 - scanlines[0][i * 4 + 0] = DIV255(ck); - scanlines[0][i * 4 + 1] = DIV255(mk); - scanlines[0][i * 4 + 2] = DIV255(yk); - scanlines[0][i * 4 + 3] = 0xff; -#undef DIV255 - } - } else { -#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); @@ -311,8 +393,8 @@ static bool nsjpeg_convert(struct content *c) nsjpeg_skip_input_data, jpeg_resync_to_restart, nsjpeg_term_source }; union content_msg_data msg_data; - const char *data; - unsigned long size; + const uint8_t *data; + size_t size; char *title; /* check image header is valid and get width/height */ @@ -325,7 +407,8 @@ static bool nsjpeg_convert(struct content *c) if (setjmp(setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); - msg_data.error = nsjpeg_error_buffer; + msg_data.errordata.errorcode = NSERROR_UNKNOWN; + msg_data.errordata.errormsg = nsjpeg_error_buffer; content_broadcast(c, CONTENT_MSG_ERROR, &msg_data); return false; } @@ -407,6 +490,7 @@ static const content_handler nsjpeg_content_handler = { .clone = nsjpeg_clone, .get_internal = image_cache_get_internal, .type = image_cache_content_type, + .is_opaque = image_cache_is_opaque, .no_share = false, }; diff --git a/content/handlers/image/jpegxl.c b/content/handlers/image/jpegxl.c new file mode 100644 index 000000000..01c704577 --- /dev/null +++ b/content/handlers/image/jpegxl.c @@ -0,0 +1,340 @@ +/* + * Copyright 2023 Vincent Sanders <vince@netsurf-browser.org> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of content handling for image/jpegxl + * + * This implementation uses the JXL library. + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <setjmp.h> +#include <string.h> + +#include <jxl/decode.h> + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "netsurf/bitmap.h" +#include "content/llcache.h" +#include "content/content.h" +#include "content/content_protected.h" +#include "content/content_factory.h" +#include "desktop/gui_internal.h" +#include "desktop/bitmap.h" + +#include "image/image_cache.h" + +#include "image/jpegxl.h" + + +/** + * output image format + */ +static const JxlPixelFormat jxl_output_format = { + .num_channels = 4, + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_LITTLE_ENDIAN, + .align = 0, +}; + +/** + * Content create entry point. + */ +static nserror +nsjpegxl_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; +} + +/** + * create a bitmap from jpeg xl content. + */ +static struct bitmap * +jpegxl_cache_convert(struct content *c) +{ + struct bitmap * bitmap = NULL; + JxlDecoder *jxldec; + JxlDecoderStatus decstatus; + JxlBasicInfo binfo; + const uint8_t *src_data; + size_t src_size; + uint8_t * output; + bitmap_fmt_t jxl_fmt = { + /** TODO: At the moment we have to set the layout to the only + * pixel layout that libjxl supports. It looks like they + * plan to add support for decoding to other layouts + * in the future, as shown by the TODO in the docs: + * + * https://libjxl.readthedocs.io/en/latest/api_common.html#_CPPv414JxlPixelFormat + */ + .layout = BITMAP_LAYOUT_R8G8B8A8, + .pma = bitmap_fmt.pma, + }; + + jxldec = JxlDecoderCreate(NULL); + if (jxldec == NULL) { + NSLOG(netsurf, ERROR, "Unable to allocate decoder"); + return NULL; + } + + decstatus = JxlDecoderSetUnpremultiplyAlpha(jxldec, !bitmap_fmt.pma); + if (decstatus != JXL_DEC_SUCCESS) { + NSLOG(netsurf, ERROR, "unable to set premultiplied alpha status: %d", + decstatus); + JxlDecoderDestroy(jxldec); + return NULL; + } + + decstatus= JxlDecoderSubscribeEvents(jxldec, JXL_DEC_FULL_IMAGE); + if (decstatus != JXL_DEC_SUCCESS) { + NSLOG(netsurf, ERROR, "Unable to subscribe"); + return NULL; + } + src_data = content__get_source_data(c, &src_size); + + decstatus = JxlDecoderSetInput(jxldec, src_data, src_size); + if (decstatus != JXL_DEC_SUCCESS) { + NSLOG(netsurf, ERROR, "unable to set input"); + return NULL; + } + + decstatus = JxlDecoderProcessInput(jxldec); + if (decstatus != JXL_DEC_NEED_IMAGE_OUT_BUFFER) { + NSLOG(netsurf, ERROR, + "expected status JXL_DEC_NEED_IMAGE_OUT_BUFFER(%d) got %d", + JXL_DEC_NEED_IMAGE_OUT_BUFFER, + decstatus); + JxlDecoderDestroy(jxldec); + return NULL; + } + + decstatus = JxlDecoderGetBasicInfo(jxldec, &binfo); + if (decstatus != JXL_DEC_SUCCESS) { + NSLOG(netsurf, ERROR, "unable to get basic info status:%d",decstatus); + JxlDecoderDestroy(jxldec); + return NULL; + } + + /* create bitmap with appropriate opacity */ + if (binfo.alpha_bits > 0) { + bitmap = guit->bitmap->create(c->width, c->height, BITMAP_OPAQUE); + } else { + bitmap = guit->bitmap->create(c->width, c->height, BITMAP_NONE); + } + if (bitmap == NULL) { + /* empty bitmap could not be created */ + JxlDecoderDestroy(jxldec); + return NULL; + } + + /* ensure buffer was allocated */ + output = guit->bitmap->get_buffer(bitmap); + if (output == NULL) { + /* bitmap with no buffer available */ + guit->bitmap->destroy(bitmap); + JxlDecoderDestroy(jxldec); + return NULL; + } + decstatus = JxlDecoderSetImageOutBuffer(jxldec, &jxl_output_format, output, c->size); + if (decstatus != JXL_DEC_SUCCESS) { + NSLOG(netsurf, ERROR, "unable to set output buffer callback status:%d",decstatus); + guit->bitmap->destroy(bitmap); + JxlDecoderDestroy(jxldec); + return NULL; + } + + decstatus = JxlDecoderProcessInput(jxldec); + if (decstatus != JXL_DEC_FULL_IMAGE) { + NSLOG(netsurf, ERROR, "did not get decode event"); + guit->bitmap->destroy(bitmap); + JxlDecoderDestroy(jxldec); + return NULL; + } + + JxlDecoderDestroy(jxldec); + + bitmap_format_to_client(bitmap, &jxl_fmt); + guit->bitmap->modified(bitmap); + + return bitmap; +} + +/** + * report failiure + */ +static bool jxl_report_fail(struct content *c, JxlDecoderStatus decstatus, const char *msg) +{ + union content_msg_data msg_data; + NSLOG(netsurf, ERROR, "%s decoder status:%d", msg, decstatus); + msg_data.errordata.errorcode = NSERROR_UNKNOWN; + msg_data.errordata.errormsg = msg; + content_broadcast(c, CONTENT_MSG_ERROR, &msg_data); + return false; +} + +/** + * Convert a CONTENT_JPEGXL for display. + */ +static bool nsjpegxl_convert(struct content *c) +{ + JxlDecoder *jxldec; + JxlSignature decsig; + JxlDecoderStatus decstatus = JXL_DEC_ERROR; + JxlBasicInfo binfo; + union content_msg_data msg_data; + const uint8_t *data; + size_t size; + char *title; + size_t image_size; + + /* check image header is valid and get width/height */ + data = content__get_source_data(c, &size); + + decsig = JxlSignatureCheck(data,size); + if ((decsig != JXL_SIG_CODESTREAM) && (decsig != JXL_SIG_CONTAINER)) { + NSLOG(netsurf, ERROR, "signature failed"); + msg_data.errordata.errorcode = NSERROR_UNKNOWN; + msg_data.errordata.errormsg = "Signature failed"; + content_broadcast(c, CONTENT_MSG_ERROR, &msg_data); + return false; + } + + jxldec = JxlDecoderCreate(NULL); + if (jxldec == NULL) { + return jxl_report_fail(c, decstatus, "Unable to allocate decoder"); + } + decstatus= JxlDecoderSubscribeEvents(jxldec, JXL_DEC_BASIC_INFO); + if (decstatus != JXL_DEC_SUCCESS) { + return jxl_report_fail(c, decstatus, "Unable to subscribe"); + } + decstatus = JxlDecoderSetInput(jxldec, data,size); + if (decstatus != JXL_DEC_SUCCESS) { + return jxl_report_fail(c, decstatus, "unable to set input"); + } + decstatus = JxlDecoderProcessInput(jxldec); + if (decstatus != JXL_DEC_BASIC_INFO) { + return jxl_report_fail(c, decstatus, "did not get basic info event"); + } + decstatus = JxlDecoderGetBasicInfo(jxldec, &binfo); + if (decstatus != JXL_DEC_SUCCESS) { + return jxl_report_fail(c, decstatus, "unable to get basic info"); + } + decstatus = JxlDecoderImageOutBufferSize(jxldec, &jxl_output_format, &image_size); + if (decstatus != JXL_DEC_SUCCESS) { + return jxl_report_fail(c, decstatus, "unable get image size"); + } + + JxlDecoderDestroy(jxldec); + + NSLOG(netsurf, INFO, "got basic info size:%ld x:%d y:%d", image_size, binfo.xsize, binfo.ysize); + + c->width = binfo.xsize; + c->height = binfo.ysize; + c->size = image_size; + + image_cache_add(c, NULL, jpegxl_cache_convert); + + /* set title text */ + title = messages_get_buff("JPEGXLTitle", + 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 nsjpegxl_clone(const struct content *old, struct content **newc) +{ + struct content *jpegxl_c; + nserror error; + + jpegxl_c = calloc(1, sizeof(struct content)); + if (jpegxl_c == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, jpegxl_c); + if (error != NSERROR_OK) { + content_destroy(jpegxl_c); + return error; + } + + /* re-convert if the content is ready */ + if ((old->status == CONTENT_STATUS_READY) || + (old->status == CONTENT_STATUS_DONE)) { + if (nsjpegxl_convert(jpegxl_c) == false) { + content_destroy(jpegxl_c); + return NSERROR_CLONE_FAILED; + } + } + + *newc = jpegxl_c; + + return NSERROR_OK; +} + +static const content_handler nsjpegxl_content_handler = { + .create = nsjpegxl_create, + .data_complete = nsjpegxl_convert, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, + .clone = nsjpegxl_clone, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, + .is_opaque = image_cache_is_opaque, + .no_share = false, +}; + +static const char *nsjpegxl_types[] = { + "image/jxl", +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsjpegxl, nsjpegxl_types, nsjpegxl_content_handler); diff --git a/content/handlers/image/jpegxl.h b/content/handlers/image/jpegxl.h new file mode 100644 index 000000000..e37d9344e --- /dev/null +++ b/content/handlers/image/jpegxl.h @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Vincent Sanders <vince@netsurf-browser.org> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** \file + * Content for image/jpegxl (interface). + */ + +#ifndef _NETSURF_IMAGE_JPEGXL_H_ +#define _NETSURF_IMAGE_JPEGXL_H_ + +nserror nsjpegxl_init(void); + +#endif diff --git a/content/handlers/image/nssprite.c b/content/handlers/image/nssprite.c index 269c24356..c18f49063 100644 --- a/content/handlers/image/nssprite.c +++ b/content/handlers/image/nssprite.c @@ -23,6 +23,8 @@ #include <stdbool.h> #include <stdlib.h> +#include <string.h> + #include <librosprite.h> #include "utils/utils.h" @@ -33,7 +35,9 @@ #include "netsurf/content.h" #include "content/llcache.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/nssprite.h" @@ -98,8 +102,8 @@ static bool nssprite_convert(struct content *c) struct rosprite_mem_context* ctx = NULL; - const char *data; - unsigned long size; + const uint8_t *data; + size_t size; char *title; data = content__get_source_data(c, &size); @@ -115,31 +119,19 @@ static bool nssprite_convert(struct content *c) struct rosprite* sprite = sprite_area->sprites[0]; - nssprite->bitmap = guit->bitmap->create(sprite->width, sprite->height, BITMAP_NEW); + nssprite->bitmap = guit->bitmap->create(sprite->width, sprite->height, BITMAP_NONE); if (!nssprite->bitmap) { - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); return false; } - uint32_t* imagebuf = (uint32_t *)guit->bitmap->get_buffer(nssprite->bitmap); + uint32_t* imagebuf = (uint32_t *)(void *)guit->bitmap->get_buffer(nssprite->bitmap); if (!imagebuf) { - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); 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++; - } - } + memcpy(imagebuf, spritebuf, sprite->width * sprite->height * 4); c->width = sprite->width; c->height = sprite->height; @@ -153,6 +145,9 @@ static bool nssprite_convert(struct content *c) free(title); } + bitmap_format_to_client(nssprite->bitmap, &(bitmap_fmt_t) { + .layout = BITMAP_LAYOUT_A8B8G8R8, + }); guit->bitmap->modified(nssprite->bitmap); content_set_ready(c); @@ -165,7 +160,7 @@ ro_sprite_error: if (ctx != NULL) { rosprite_destroy_mem_context(ctx); } - content_broadcast_errorcode(c, NSERROR_SPRITE_ERROR); + content_broadcast_error(c, NSERROR_SPRITE_ERROR, NULL); return false; } @@ -257,6 +252,17 @@ static content_type nssprite_content_type(void) } +static bool nssprite_content_is_opaque(struct content *c) +{ + nssprite_content *nssprite = (nssprite_content *) c; + + if (nssprite->bitmap != NULL) { + return guit->bitmap->get_opaque(nssprite->bitmap); + } + + return false; +} + static const content_handler nssprite_content_handler = { .create = nssprite_create, .data_complete = nssprite_convert, @@ -265,6 +271,7 @@ static const content_handler nssprite_content_handler = { .clone = nssprite_clone, .get_internal = nssprite_get_internal, .type = nssprite_content_type, + .is_opaque = nssprite_content_is_opaque, .no_share = false, }; diff --git a/content/handlers/image/png.c b/content/handlers/image/png.c index 7a4ce3010..97a5795b3 100644 --- a/content/handlers/image/png.c +++ b/content/handlers/image/png.c @@ -19,6 +19,7 @@ */ #include <stdbool.h> +#include <string.h> #include <stdlib.h> #include <png.h> @@ -29,7 +30,9 @@ #include "netsurf/bitmap.h" #include "content/llcache.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/image_cache.h" #include "image/png.h" @@ -116,8 +119,37 @@ static void nspng_setup_transforms(png_structp png_ptr, png_infop info_ptr) png_set_gray_to_rgb(png_ptr); } + switch (bitmap_fmt.layout) { + case BITMAP_LAYOUT_B8G8R8A8: /* Fall through. */ + case BITMAP_LAYOUT_A8B8G8R8: + png_set_bgr(png_ptr); + break; + default: + /* RGB is the default. */ + break; + } + if (!(color_type & PNG_COLOR_MASK_ALPHA)) { - png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + switch (bitmap_fmt.layout) { + case BITMAP_LAYOUT_A8R8G8B8: /* Fall through. */ + case BITMAP_LAYOUT_A8B8G8R8: + png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); + break; + + default: + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + break; + } + } else { + switch (bitmap_fmt.layout) { + case BITMAP_LAYOUT_A8R8G8B8: /* Fall through. */ + case BITMAP_LAYOUT_A8B8G8R8: + png_set_swap_alpha(png_ptr); + break; + default: + /* Alpha as final component is the default. */ + break; + } } /* gamma correction - we use 2.2 as our screen gamma @@ -161,14 +193,14 @@ static void info_callback(png_structp png_s, png_infop info) } /* Claim the required memory for the converted PNG */ - png_c->bitmap = guit->bitmap->create(width, height, BITMAP_NEW); + png_c->bitmap = guit->bitmap->create(width, height, BITMAP_NONE); 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); + png_c->bpp = sizeof(uint32_t); nspng_setup_transforms(png_s, info); @@ -242,7 +274,7 @@ static nserror nspng_create_png_data(nspng_content *png_c) png_c->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (png_c->png == NULL) { - content_broadcast_errorcode(&png_c->base, NSERROR_NOMEM); + content_broadcast_error(&png_c->base, NSERROR_NOMEM, NULL); return NSERROR_NOMEM; } @@ -252,7 +284,7 @@ static nserror nspng_create_png_data(nspng_content *png_c) if (png_c->info == NULL) { png_destroy_read_struct(&png_c->png, &png_c->info, 0); - content_broadcast_errorcode(&png_c->base, NSERROR_NOMEM); + content_broadcast_error(&png_c->base, NSERROR_NOMEM, NULL); return NSERROR_NOMEM; } @@ -262,7 +294,7 @@ static nserror nspng_create_png_data(nspng_content *png_c) png_c->png = NULL; png_c->info = NULL; - content_broadcast_errorcode(&png_c->base, NSERROR_PNG_ERROR); + content_broadcast_error(&png_c->base, NSERROR_PNG_ERROR, NULL); return NSERROR_NOMEM; } @@ -355,7 +387,7 @@ static bool nspng_process_data(struct content *c, const char *data, png_c->png = NULL; png_c->info = NULL; - content_broadcast_errorcode(c, NSERROR_PNG_ERROR); + content_broadcast_error(c, NSERROR_PNG_ERROR, NULL); ret = false; @@ -367,8 +399,8 @@ static bool nspng_process_data(struct content *c, const char *data, } struct png_cache_read_data_s { - const char *data; - unsigned long size; + const uint8_t *data; + size_t size; }; /** PNG library read fucntion to read data from a memory array @@ -481,7 +513,7 @@ png_cache_convert(struct content *c) 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); + bitmap = guit->bitmap->create(width, height, BITMAP_NONE); if (bitmap == NULL) { /* cleanup and bail */ goto png_cache_convert_error; @@ -506,7 +538,13 @@ png_cache_convert_error: } if (bitmap != NULL) { - guit->bitmap->modified((struct bitmap *)bitmap); + bool opaque = bitmap_test_opaque((void *)bitmap); + guit->bitmap->set_opaque((void *)bitmap, opaque); + bitmap_format_to_client((void *)bitmap, &(bitmap_fmt_t) { + .layout = bitmap_fmt.layout, + .pma = opaque ? bitmap_fmt.pma : false, + }); + guit->bitmap->modified((void *)bitmap); } return (struct bitmap *)bitmap; @@ -533,7 +571,12 @@ static bool nspng_convert(struct content *c) } if (png_c->bitmap != NULL) { - guit->bitmap->set_opaque(png_c->bitmap, guit->bitmap->test_opaque(png_c->bitmap)); + bool opaque = bitmap_test_opaque(png_c->bitmap); + guit->bitmap->set_opaque(png_c->bitmap, opaque); + bitmap_format_to_client(png_c->bitmap, &(bitmap_fmt_t) { + .layout = bitmap_fmt.layout, + .pma = opaque ? bitmap_fmt.pma : false, + }); guit->bitmap->modified(png_c->bitmap); } @@ -551,8 +594,8 @@ 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; + const uint8_t *data; + size_t size; clone_png_c = calloc(1, sizeof(nspng_content)); if (clone_png_c == NULL) @@ -573,7 +616,7 @@ static nserror nspng_clone(const struct content *old_c, struct content **new_c) data = content__get_source_data(&clone_png_c->base, &size); if (size > 0) { - if (nspng_process_data(&clone_png_c->base, data, size) == false) { + if (nspng_process_data(&clone_png_c->base, (const char *)data, size) == false) { content_destroy(&clone_png_c->base); return NSERROR_NOMEM; } @@ -601,6 +644,7 @@ static const content_handler nspng_content_handler = { .redraw = image_cache_redraw, .get_internal = image_cache_get_internal, .type = image_cache_content_type, + .is_opaque = image_cache_is_opaque, .no_share = false, }; diff --git a/content/handlers/image/rsvg.c b/content/handlers/image/rsvg.c index 2ba1b49f5..24fc1a4e0 100644 --- a/content/handlers/image/rsvg.c +++ b/content/handlers/image/rsvg.c @@ -49,7 +49,9 @@ #include "netsurf/content.h" #include "content/llcache.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "desktop/gui_internal.h" +#include "desktop/bitmap.h" #include "image/rsvg.h" @@ -71,7 +73,7 @@ static nserror rsvg_create_svg_data(rsvg_content *c) if ((c->rsvgh = rsvg_handle_new()) == NULL) { NSLOG(netsurf, INFO, "rsvg_handle_new() returned NULL."); - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return NSERROR_NOMEM; } @@ -120,48 +122,13 @@ static bool rsvg_process_data(struct content *c, const char *data, &err) == FALSE) { NSLOG(netsurf, INFO, "rsvg_handle_write returned an error: %s", err->message); - content_broadcast_errorcode(c, NSERROR_SVG_ERROR); + content_broadcast_error(c, NSERROR_SVG_ERROR, NULL); 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; - int boff = 0, roff = 2; - - if (endian_host_is_le() == false) { - boff = 1; - roff = 3; - } - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - /* Swap R and B */ - const uint8_t r = p[4*x+roff]; - - p[4*x+roff] = p[4*x+boff]; - - p[4*x+boff] = r; - } - - p += rowstride; - } -} - static bool rsvg_convert(struct content *c) { rsvg_content *d = (rsvg_content *) c; @@ -171,7 +138,7 @@ static bool rsvg_convert(struct content *c) if (rsvg_handle_close(d->rsvgh, &err) == FALSE) { NSLOG(netsurf, INFO, "rsvg_handle_close returned an error: %s", err->message); - content_broadcast_errorcode(c, NSERROR_SVG_ERROR); + content_broadcast_error(c, NSERROR_SVG_ERROR, NULL); return false; } @@ -186,10 +153,10 @@ static bool rsvg_convert(struct content *c) c->height = rsvgsize.height; if ((d->bitmap = guit->bitmap->create(c->width, c->height, - BITMAP_NEW)) == NULL) { + BITMAP_NONE)) == NULL) { NSLOG(netsurf, INFO, "Failed to create bitmap for rsvg render."); - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); return false; } @@ -200,22 +167,22 @@ static bool rsvg_convert(struct content *c) guit->bitmap->get_rowstride(d->bitmap))) == NULL) { NSLOG(netsurf, INFO, "Failed to create Cairo image surface for rsvg render."); - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); return false; } if ((d->ct = cairo_create(d->cs)) == NULL) { NSLOG(netsurf, INFO, "Failed to create Cairo drawing context for rsvg render."); - content_broadcast_errorcode(c, NSERROR_NOMEM); + content_broadcast_error(c, NSERROR_NOMEM, NULL); 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)); + bitmap_format_to_client(d->bitmap, &(bitmap_fmt_t) { + .layout = BITMAP_LAYOUT_ARGB8888, + }); guit->bitmap->modified(d->bitmap); content_set_ready(c); content_set_done(c); @@ -262,8 +229,8 @@ static nserror rsvg_clone(const struct content *old, struct content **newc) { rsvg_content *svg; nserror error; - const char *data; - unsigned long size; + const uint8_t *data; + size_t size; svg = calloc(1, sizeof(rsvg_content)); if (svg == NULL) @@ -284,7 +251,7 @@ static nserror rsvg_clone(const struct content *old, struct content **newc) data = content__get_source_data(&svg->base, &size); if (size > 0) { - if (rsvg_process_data(&svg->base, data, size) == false) { + if (rsvg_process_data(&svg->base, (const char *)data, size) == false) { content_destroy(&svg->base); return NSERROR_NOMEM; } @@ -315,6 +282,19 @@ static content_type rsvg_content_type(void) return CONTENT_IMAGE; } + +static bool rsvg_content_is_opaque(struct content *c) +{ + rsvg_content *d = (rsvg_content *) c; + + if (d->bitmap != NULL) { + return guit->bitmap->get_opaque(d->bitmap); + } + + return false; +} + + static const content_handler rsvg_content_handler = { .create = rsvg_create, .process_data = rsvg_process_data, @@ -324,6 +304,7 @@ static const content_handler rsvg_content_handler = { .clone = rsvg_clone, .get_internal = rsvg_get_internal, .type = rsvg_content_type, + .is_opaque = rsvg_content_is_opaque, .no_share = false, }; diff --git a/content/handlers/image/rsvg246.c b/content/handlers/image/rsvg246.c new file mode 100644 index 000000000..0e337132f --- /dev/null +++ b/content/handlers/image/rsvg246.c @@ -0,0 +1,280 @@ +/* + * Copyright 2022 Vincent Sanders <vince@netsurf-browser.org> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of content handler for image/svg using librsvg 2.46 API. + * + * 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 <stdlib.h> +#include <stdbool.h> +#include <assert.h> +#include <string.h> +#include <sys/types.h> + +#include <librsvg/rsvg.h> + +#include <nsutils/endian.h> + +#include "utils/log.h" +#include "utils/utils.h" +#include "utils/messages.h" +#include "netsurf/plotters.h" +#include "netsurf/bitmap.h" +#include "netsurf/content.h" +#include "content/llcache.h" +#include "content/content_protected.h" +#include "content/content_factory.h" +#include "desktop/gui_internal.h" +#include "desktop/bitmap.h" + +#include "image/image_cache.h" + +#include "image/rsvg.h" + + +typedef struct rsvg_content { + struct content base; + + RsvgHandle *rsvgh; /**< Context handle for RSVG renderer */ +} rsvg_content; + + +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; + } + + *c = (struct content *)svg; + + return NSERROR_OK; +} + + +/** + * create a bitmap from jpeg content for the image cache. + */ +static struct bitmap * +rsvg_cache_convert(struct content *c) +{ + rsvg_content *svgc = (rsvg_content *)c; + struct bitmap *bitmap; + cairo_surface_t *cs; + cairo_t *cr; + RsvgRectangle viewport; + gboolean renderres; + + if ((bitmap = guit->bitmap->create(c->width, c->height, BITMAP_NONE)) == NULL) { + NSLOG(netsurf, INFO, "Failed to create bitmap for rsvg render."); + return NULL; + } + + if ((cs = cairo_image_surface_create_for_data( + (unsigned char *)guit->bitmap->get_buffer(bitmap), + CAIRO_FORMAT_ARGB32, + c->width, c->height, + guit->bitmap->get_rowstride(bitmap))) == NULL) { + NSLOG(netsurf, INFO, "Failed to create Cairo image surface for rsvg render."); + guit->bitmap->destroy(bitmap); + return NULL; + } + if ((cr = cairo_create(cs)) == NULL) { + NSLOG(netsurf, INFO, + "Failed to create Cairo drawing context for rsvg render."); + cairo_surface_destroy(cs); + guit->bitmap->destroy(bitmap); + return NULL; + } + + viewport.x = 0; + viewport.y = 0; + viewport.width = c->width; + viewport.height = c->height; + renderres = rsvg_handle_render_document(svgc->rsvgh, cr, &viewport, NULL); + NSLOG(netsurf, DEBUG, "rsvg render:%d, width:%d, height %d", renderres, c->width, c->height); + + bitmap_format_to_client(bitmap, &(bitmap_fmt_t) { + .layout = BITMAP_LAYOUT_ARGB8888, + }); + guit->bitmap->modified(bitmap); + + cairo_destroy(cr); + cairo_surface_destroy(cs); + + return bitmap; +} + +static void rsvg__get_demensions(const rsvg_content *svgc, + int *width, int *height) +{ +#if LIBRSVG_MAJOR_VERSION >= 2 && LIBRSVG_MINOR_VERSION >= 52 + gdouble rwidth; + gdouble rheight; + gboolean gotsize; + + gotsize = rsvg_handle_get_intrinsic_size_in_pixels(svgc->rsvgh, + &rwidth, + &rheight); + if (gotsize == TRUE) { + *width = rwidth; + *height = rheight; + } else { + RsvgRectangle ink_rect; + RsvgRectangle logical_rect; + rsvg_handle_get_geometry_for_element(svgc->rsvgh, + NULL, + &ink_rect, + &logical_rect, + NULL); + *width = ink_rect.width; + *height = ink_rect.height; + } +#else + RsvgDimensionData rsvgsize; + + rsvg_handle_get_dimensions(svgc->rsvgh, &rsvgsize); + *width = rsvgsize.width; + *height = rsvgsize.height; +#endif + NSLOG(netsurf, DEBUG, "rsvg width:%d height:%d.", *width, *height); +} + +static bool rsvg_convert(struct content *c) +{ + rsvg_content *svgc = (rsvg_content *)c; + const uint8_t *data; /* content data */ + size_t size; /* content data size */ + GInputStream * istream; + GError *gerror = NULL; + + /* check image header is valid and get width/height */ + + data = content__get_source_data(c, &size); + + istream = g_memory_input_stream_new_from_data(data, size, NULL); + svgc->rsvgh = rsvg_handle_new_from_stream_sync(istream, + NULL, + RSVG_HANDLE_FLAGS_NONE, + NULL, + &gerror); + g_object_unref(istream); + if (svgc->rsvgh == NULL) { + NSLOG(netsurf, INFO, "Failed to create rsvg handle for content."); + return false; + } + + rsvg__get_demensions(svgc, &c->width, &c->height); + + c->size = c->width * c->height * 4; + + image_cache_add(c, NULL, rsvg_cache_convert); + + content_set_ready(c); + content_set_done(c); + content_set_status(c, ""); /* Done: update status bar */ + + return true; +} + + +static nserror rsvg_clone(const struct content *old, struct content **newc) +{ + rsvg_content *svg; + nserror error; + + 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; + } + + /* re-convert if the content is ready */ + 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_destroy(struct content *c) +{ + rsvg_content *d = (rsvg_content *) c; + + if (d->rsvgh != NULL) { + g_object_unref(d->rsvgh); + d->rsvgh = NULL; + } + + return image_cache_destroy(c); +} + +static const content_handler rsvg_content_handler = { + .create = rsvg_create, + .data_complete = rsvg_convert, + .destroy = rsvg_destroy, + .redraw = image_cache_redraw, + .clone = rsvg_clone, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, + .is_opaque = image_cache_is_opaque, + .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/svg.c b/content/handlers/image/svg.c index 51260733d..895d799df 100644 --- a/content/handlers/image/svg.c +++ b/content/handlers/image/svg.c @@ -30,9 +30,11 @@ #include "utils/messages.h" #include "utils/utils.h" +#include "utils/nsurl.h" #include "netsurf/plotters.h" #include "netsurf/content.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "image/svg.h" @@ -59,7 +61,7 @@ static nserror svg_create_svg_data(svg_content *c) return NSERROR_OK; no_memory: - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return NSERROR_NOMEM; } @@ -126,8 +128,8 @@ static bool svg_convert(struct content *c) 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; + const uint8_t *source_data; + size_t source_size; assert(svg->diagram); @@ -135,9 +137,12 @@ static void svg_reformat(struct content *c, int width, int height) 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); + svgtiny_parse(svg->diagram, + (const char *)source_data, + source_size, + nsurl_access(content_get_url(c)), + width, + height); svg->current_width = width; svg->current_height = height; @@ -189,7 +194,7 @@ svg_redraw_internal(struct content *c, for (i = 0; i != diagram->shape_count; i++) { if (diagram->shape[i].path) { pstyle.stroke_width = plot_style_int_to_fixed( - diagram->shape[i].stroke); + diagram->shape[i].stroke_width); pstyle.stroke_colour = BGR(diagram->shape[i].stroke); pstyle.fill_colour = BGR(diagram->shape[i].fill); res = ctx->plot->path(ctx, @@ -222,7 +227,7 @@ svg_redraw_internal(struct content *c, return false; } } - } + } #undef BGR @@ -361,5 +366,3 @@ static const char *svg_types[] = { CONTENT_FACTORY_REGISTER_TYPES(svg, svg_types, svg_content_handler); - - diff --git a/content/handlers/image/webp.c b/content/handlers/image/webp.c new file mode 100644 index 000000000..c04c0efd2 --- /dev/null +++ b/content/handlers/image/webp.c @@ -0,0 +1,260 @@ +/* + * Copyright 2019 Vincent Sanders <vince@netsurf-browser.org> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of content handling for image/webp + * + * This implementation uses the google webp library. + * Image cache handling is performed by the generic NetSurf handler. + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <setjmp.h> + +#include <webp/decode.h> + +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "netsurf/bitmap.h" +#include "content/llcache.h" +#include "content/content_protected.h" +#include "content/content_factory.h" +#include "desktop/gui_internal.h" +#include "desktop/bitmap.h" + +#include "image/image_cache.h" + +#include "webp.h" + +/** + * Content create entry point. + * + * create a content object for the webp + */ +static nserror +webp_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 *webp_c; /* webp content object */ + nserror res; + + webp_c = calloc(1, sizeof(struct content)); + if (webp_c == NULL) { + return NSERROR_NOMEM; + } + + res = content__init(webp_c, + handler, + imime_type, + params, + llcache, + fallback_charset, + quirks); + if (res != NSERROR_OK) { + free(webp_c); + return res; + } + + *c = webp_c; + + return NSERROR_OK; +} + +/** + * create a bitmap from webp content. + */ +static struct bitmap * +webp_cache_convert(struct content *c) +{ + const uint8_t *source_data; /* webp source data */ + size_t source_size; /* length of webp source data */ + VP8StatusCode webpres; + WebPBitstreamFeatures webpfeatures; + unsigned int bmap_flags; + uint8_t *pixels = NULL; + uint8_t *decoded; + size_t rowstride; + struct bitmap *bitmap = NULL; + bitmap_fmt_t webp_fmt = { + .layout = bitmap_fmt.layout, + }; + + source_data = content__get_source_data(c, &source_size); + + webpres = WebPGetFeatures(source_data, source_size, &webpfeatures); + + if (webpres != VP8_STATUS_OK) { + return NULL; + } + + if (webpfeatures.has_alpha == 0) { + bmap_flags = BITMAP_OPAQUE; + /* Image has no alpha. Premultiplied alpha makes no difference. + * Optimisation: Avoid unnecessary conversion by copying format. + */ + webp_fmt.pma = bitmap_fmt.pma; + } else { + bmap_flags = BITMAP_NONE; + } + + /* create bitmap */ + bitmap = guit->bitmap->create(webpfeatures.width, + webpfeatures.height, + bmap_flags); + if (bitmap == NULL) { + /* empty bitmap could not be created */ + return NULL; + } + + pixels = guit->bitmap->get_buffer(bitmap); + if (pixels == NULL) { + /* bitmap with no buffer available */ + guit->bitmap->destroy(bitmap); + return NULL; + } + + rowstride = guit->bitmap->get_rowstride(bitmap); + + switch (webp_fmt.layout) { + default: + /* WebP has no ABGR function, fall back to default. */ + webp_fmt.layout = BITMAP_LAYOUT_R8G8B8A8; + fallthrough; + case BITMAP_LAYOUT_R8G8B8A8: + decoded = WebPDecodeRGBAInto(source_data, source_size, pixels, + rowstride * webpfeatures.height, rowstride); + break; + + case BITMAP_LAYOUT_B8G8R8A8: + decoded = WebPDecodeBGRAInto(source_data, source_size, pixels, + rowstride * webpfeatures.height, rowstride); + break; + + case BITMAP_LAYOUT_A8R8G8B8: + decoded = WebPDecodeARGBInto(source_data, source_size, pixels, + rowstride * webpfeatures.height, rowstride); + break; + } + if (decoded == NULL) { + /* decode failed */ + guit->bitmap->destroy(bitmap); + return NULL; + } + + bitmap_format_to_client(bitmap, &webp_fmt); + guit->bitmap->modified(bitmap); + + return bitmap; +} + +/** + * Convert the webp source data content. + * + * This ensures there is valid webp source data in the content object + * and then adds it to the image cache ready to be converted on + * demand. + * + * \param c The webp content object + * \return true on successful processing of teh webp content else false + */ +static bool webp_convert(struct content *c) +{ + int res; + const uint8_t* data; + size_t data_size; + int width; + int height; + + data = content__get_source_data(c, &data_size); + + res = WebPGetInfo(data, data_size, &width, &height); + if (res == 0) { + NSLOG(netsurf, INFO, "WebPGetInfo failed:%p", c); + return false; + } + + c->width = width; + c->height = height; + c->size = c->width * c->height * 4; + + image_cache_add(c, NULL, webp_cache_convert); + + content_set_ready(c); + content_set_done(c); + + return true; +} + +/** + * Clone content. + */ +static nserror webp_clone(const struct content *old, struct content **new_c) +{ + struct content *webp_c; /* cloned webp content */ + nserror res; + + webp_c = calloc(1, sizeof(struct content)); + if (webp_c == NULL) { + return NSERROR_NOMEM; + } + + res = content__clone(old, webp_c); + if (res != NSERROR_OK) { + content_destroy(webp_c); + return res; + } + + /* re-convert if the content is ready */ + if ((old->status == CONTENT_STATUS_READY) || + (old->status == CONTENT_STATUS_DONE)) { + if (webp_convert(webp_c) == false) { + content_destroy(webp_c); + return NSERROR_CLONE_FAILED; + } + } + + *new_c = webp_c; + + return NSERROR_OK; +} + +static const content_handler webp_content_handler = { + .create = webp_create, + .data_complete = webp_convert, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, + .clone = webp_clone, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, + .is_opaque = image_cache_is_opaque, + .no_share = false, +}; + +static const char *webp_types[] = { + "image/webp" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nswebp, webp_types, webp_content_handler); diff --git a/content/handlers/image/webp.h b/content/handlers/image/webp.h new file mode 100644 index 000000000..b219f2576 --- /dev/null +++ b/content/handlers/image/webp.h @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Vincent Sanders <vince@netsurf-browser.org> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Interface to image/webp content handlers + */ + +#ifndef _NETSURF_IMAGE_WEBP_H_ +#define _NETSURF_IMAGE_WEBP_H_ + +nserror nswebp_init(void); + +#endif |