You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
netsurf/content/handlers/image/jpegxl.c

341 lines
8.9 KiB

/*
* 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);