Implement simple jpeg xl image handler

This commit is contained in:
Vincent Sanders 2023-11-26 15:51:40 +00:00 committed by Michael Drake
parent bbeb93d512
commit dbe5d1ef87
8 changed files with 379 additions and 0 deletions

View File

@ -171,6 +171,7 @@ endif
$(eval $(call pkg_config_find_and_add_enabled,OPENSSL,openssl,OpenSSL))
$(eval $(call pkg_config_find_and_add_enabled,UTF8PROC,libutf8proc,utf8))
$(eval $(call pkg_config_find_and_add_enabled,JPEGXL,libjxl,JPEGXL))
$(eval $(call pkg_config_find_and_add_enabled,WEBP,libwebp,WEBP))
$(eval $(call pkg_config_find_and_add_enabled,PNG,libpng,PNG))
$(eval $(call pkg_config_find_and_add_enabled,BMP,libnsbmp,BMP))

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,327 @@
/*
* 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 = {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);

View File

@ -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

View File

@ -260,6 +260,13 @@ static nserror mimesniff__match_unknown_exact(const uint8_t *data, size_t len,
SIG(&corestring_lwc_application_x_gzip, "\x1f\x8b\x08", true),
SIG(&corestring_lwc_application_postscript, "%!PS-Adobe-",true),
SIG(&corestring_lwc_application_pdf, "%PDF-", false),
SIG(&corestring_lwc_image_jxl, "\xFF\x0A", true), /* containerless jpeg xl*/
{
(const uint8_t *)"\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A",
12,
true,
&corestring_lwc_image_jxl
}, /* containered jpeg xl*/
{ NULL, 0, false, NULL }
};
#undef SIG
@ -376,6 +383,12 @@ static nserror mimesniff__compute_image(lwc_string *official_type,
SIG(&corestring_lwc_image_jpeg, "\xff\xd8\xff"),
SIG(&corestring_lwc_image_bmp, "BM"),
SIG(&corestring_lwc_image_vnd_microsoft_icon, "\x00\x00\x01\x00"),
SIG(&corestring_lwc_image_jxl, "\xFF\x0A"), /* containerless jpeg xl*/
{
(const uint8_t *)"\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A",
12,
&corestring_lwc_image_jxl
}, /* containered jpeg xl*/
{ NULL, 0, NULL }
};
#undef SIG

View File

@ -75,6 +75,7 @@ void gtk_fetch_filetype_init(const char *mimefile)
hash_add(mime_hash, "html", "text/html");
hash_add(mime_hash, "jpg", "image/jpeg");
hash_add(mime_hash, "jpeg", "image/jpeg");
hash_add(mime_hash, "jxl", "image/jxl");
hash_add(mime_hash, "gif", "image/gif");
hash_add(mime_hash, "png", "image/png");
hash_add(mime_hash, "jng", "image/jng");

View File

@ -167,6 +167,7 @@ CORESTRING_LWC_VALUE(application_octet_stream, "application/octet-stream");
CORESTRING_LWC_VALUE(image_gif, "image/gif");
CORESTRING_LWC_VALUE(image_png, "image/png");
CORESTRING_LWC_VALUE(image_jpeg, "image/jpeg");
CORESTRING_LWC_VALUE(image_jxl, "image/jxl");
CORESTRING_LWC_VALUE(image_bmp, "image/bmp");
CORESTRING_LWC_VALUE(image_vnd_microsoft_icon, "image/vnd.microsoft.icon");
CORESTRING_LWC_VALUE(image_webp, "image/webp");