From dbe5d1ef87ff5a348ae758bdb9635f767822d7d4 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sun, 26 Nov 2023 15:51:40 +0000 Subject: Implement simple jpeg xl image handler --- content/handlers/image/Makefile | 1 + content/handlers/image/image.c | 7 + content/handlers/image/jpegxl.c | 327 ++++++++++++++++++++++++++++++++++++++++ content/handlers/image/jpegxl.h | 28 ++++ 4 files changed, 363 insertions(+) create mode 100644 content/handlers/image/jpegxl.c create mode 100644 content/handlers/image/jpegxl.h (limited to 'content/handlers') diff --git a/content/handlers/image/Makefile b/content/handlers/image/Makefile index afc90f407..ac052b37a 100644 --- a/content/handlers/image/Makefile +++ b/content/handlers/image/Makefile @@ -7,6 +7,7 @@ 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 diff --git a/content/handlers/image/image.c b/content/handlers/image/image.c index 3107ee495..2bd5f5f8d 100644 --- a/content/handlers/image/image.c +++ b/content/handlers/image/image.c @@ -32,6 +32,7 @@ #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" @@ -72,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) diff --git a/content/handlers/image/jpegxl.c b/content/handlers/image/jpegxl.c new file mode 100644 index 000000000..4bba02d1b --- /dev/null +++ b/content/handlers/image/jpegxl.c @@ -0,0 +1,327 @@ +/* + * Copyright 2023 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 . + */ + +/** + * \file + * implementation of content handling for image/jpegxl + * + * This implementation uses the JXL library. + */ + +#include +#include +#include +#include + +#include + +#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 = {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 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; +} + +static void image_out_callback(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels) +{ + struct bitmap * bitmap = opaque; + uint8_t * output; + + output = guit->bitmap->get_buffer(bitmap); + if (output != NULL) { + /* bitmap buffer available */ + memcpy(output + (x*jxl_output_format.num_channels) + (y * guit->bitmap->get_rowstride(bitmap)), + pixels, + num_pixels*jxl_output_format.num_channels); + } +} + +/** + * 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; + + jxldec = JxlDecoderCreate(NULL); + if (jxldec == NULL) { + NSLOG(netsurf, ERROR, "Unable to allocate decoder"); + 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 */ + if (guit->bitmap->get_buffer(bitmap) == NULL) { + /* bitmap with no buffer available */ + guit->bitmap->destroy(bitmap); + JxlDecoderDestroy(jxldec); + return NULL; + } + + decstatus = JxlDecoderSetImageOutCallback(jxldec, &jxl_output_format, image_out_callback, bitmap); + 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); + + 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 + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** \file + * Content for image/jpegxl (interface). + */ + +#ifndef _NETSURF_IMAGE_JPEGXL_H_ +#define _NETSURF_IMAGE_JPEGXL_H_ + +nserror nsjpegxl_init(void); + +#endif -- cgit v1.2.3