/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2021 Free Software Foundation, Inc.
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#if HAVE_LCMS2
#include
#endif
#include
#include "cairo-image-surface-jxl.h"
static void
convert_pixels (int width,
int height,
guchar *buffer)
{
int x, y;
guchar *p = buffer, r, g, b, a;
for (y = 0; y < height; y++)
for (x = width; x; x--, p += 4) {
a = p[3];
if (a == 0) {
*(guint32*)p = 0;
continue;
}
r = p[0];
g = p[1];
b = p[2];
if (a < 0xff) {
r = _cairo_multiply_alpha(r, a);
g = _cairo_multiply_alpha(g, a);
b = _cairo_multiply_alpha(b, a);
}
*(guint32*)p = CAIRO_RGBA_TO_UINT32(r, g, b, a);
}
}
#define BUFFER_SIZE (1024*1024)
GthImage *
_cairo_image_surface_create_from_jxl(GInputStream *istream,
GthFileData *file_data,
int requested_size,
int *original_width,
int *original_height,
gboolean *loaded_original,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
GthImage *image;
guchar *filebuffer;
gsize filebuffer_size, read_size, buffer_read, unprocessed_len, processed_len;
int width = 0, height = 0;
cairo_surface_t *surface = NULL;
cairo_surface_metadata_t *metadata;
JxlDecoder *dec;
void *runner;
JxlDecoderStatus status;
JxlBasicInfo info;
JxlPixelFormat pixel_format;
guchar *surface_data = NULL;
image = gth_image_new();
dec = JxlDecoderCreate(NULL);
if (dec == NULL) {
g_error("Could not create JXL decoder.\n");
return image;
}
filebuffer_size = read_size = JxlDecoderSizeHintBasicInfo(dec);
filebuffer = g_new(guchar, filebuffer_size);
if (! g_input_stream_read_all(istream,
filebuffer,
filebuffer_size,
&buffer_read,
cancellable,
error))
{
g_error("Could not read start of JXL file.\n");
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
if (JxlSignatureCheck(filebuffer, buffer_read) < JXL_SIG_CODESTREAM) {
g_error("Signature does not match for JPEG XL codestream or container.\n");
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
read_size = 1048576;
runner = JxlThreadParallelRunnerCreate(NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
if (runner == NULL) {
g_error("Could not create threaded parallel runner.\n");
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
if (JxlDecoderSetParallelRunner(dec,
JxlThreadParallelRunner,
runner) > 0) {
g_error("Could not set parallel runner.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
if (JxlDecoderSubscribeEvents(dec,
JXL_DEC_BASIC_INFO
| JXL_DEC_COLOR_ENCODING
| JXL_DEC_FULL_IMAGE) > 0) {
g_error("Could not subscribe to decoder events.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
if (JxlDecoderSetInput(dec, filebuffer, buffer_read) > 0) {
g_error("Could not set decoder input.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
status = JXL_DEC_NEED_MORE_INPUT;
while (status != JXL_DEC_SUCCESS) {
status = JxlDecoderProcessInput(dec);
switch (status) {
case JXL_DEC_ERROR:
g_error("jxl: decoder error.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
case JXL_DEC_NEED_MORE_INPUT:
if (buffer_read == 0) {
g_message("Reached end of file but decoder still wants more.\n");
status = JXL_DEC_SUCCESS;
break;
}
{
unprocessed_len = JxlDecoderReleaseInput(dec);
processed_len = filebuffer_size - unprocessed_len;
guchar *old_filebuffer = filebuffer;
filebuffer_size = unprocessed_len + read_size;
filebuffer = g_new(guchar, filebuffer_size);
if (unprocessed_len > 0)
memcpy(filebuffer, old_filebuffer + processed_len, unprocessed_len);
g_free(old_filebuffer);
gssize signed_buffer_read = g_input_stream_read(istream,
filebuffer + unprocessed_len,
read_size,
cancellable,
error);
if (signed_buffer_read <= 0) {
buffer_read = 0;
break;
}
buffer_read = signed_buffer_read;
if (JxlDecoderSetInput(dec, filebuffer, unprocessed_len + buffer_read) > 0) {
g_error("Could not set decoder input.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
}
break;
case JXL_DEC_BASIC_INFO:
if (JxlDecoderGetBasicInfo(dec, &info) > 0) {
g_error("Could not get basic info from decoder.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
pixel_format.num_channels = 4;
pixel_format.data_type = JXL_TYPE_UINT8;
pixel_format.endianness = JXL_NATIVE_ENDIAN;
pixel_format.align = 0;
width = info.xsize;
height = info.ysize;
if (original_width != NULL)
*original_width = width;
if (original_height != NULL)
*original_height = height;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
surface_data = _cairo_image_surface_flush_and_get_data(surface);
metadata = _cairo_image_surface_get_metadata(surface);
_cairo_metadata_set_has_alpha(metadata, info.num_extra_channels);
break;
case JXL_DEC_COLOR_ENCODING:
#if HAVE_LCMS2
if (JxlDecoderGetColorAsEncodedProfile(dec,
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
&pixel_format,
#endif
JXL_COLOR_PROFILE_TARGET_DATA, NULL) == JXL_DEC_SUCCESS)
break;
{
gsize profile_size;
if (JxlDecoderGetICCProfileSize(dec,
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
&pixel_format,
#endif
JXL_COLOR_PROFILE_TARGET_DATA, &profile_size) > 0) {
g_message("Could not get ICC profile size.\n");
break;
}
guchar *profile_data = g_new(guchar, profile_size);
if (JxlDecoderGetColorAsICCProfile(dec,
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
&pixel_format,
#endif
JXL_COLOR_PROFILE_TARGET_DATA, profile_data, profile_size) > 0) {
g_message("Could not get ICC profile.\n");
g_free(profile_data);
break;
}
GthICCProfile *profile = NULL;
profile = gth_icc_profile_new(GTH_ICC_PROFILE_ID_UNKNOWN, cmsOpenProfileFromMem(profile_data, profile_size));
if (profile != NULL) {
gth_image_set_icc_profile(image, profile);
g_object_unref(profile);
}
}
#endif
break;
case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
if (JxlDecoderSetImageOutBuffer(dec, &pixel_format, surface_data, width * height * 4) > 0) {
g_error("Could not set image-out buffer.\n");
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
return image;
}
break;
case JXL_DEC_SUCCESS:
continue;
case JXL_DEC_FULL_IMAGE:
continue;
default:
break;
}
}
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
g_free(filebuffer);
convert_pixels(width, height, surface_data);
cairo_surface_mark_dirty(surface);
if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS)
gth_image_set_cairo_surface(image, surface);
cairo_surface_destroy (surface);
return image;
}