mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
Highlights of the patch: - Supersede MR !249 - Added FP16 and FP32 images support thus preserving HDR values (read / write, required libjxl 0.9+). - Added Gray8 and Gray16 support (read / write). - Indexed images are saved as Gray8 when palette is gray scale. - Binary images are saved as Gray8 (does JXL natively support binary images?). - Simplified writing process by partially removing the use of additional buffers. - Added XMP metadata support by decoding/encoding Boxes. - Changed maximum image size in pixels in accordance with JXL feature level 5 (still limited to 256 megapixels). Compatibility: - Older versions of this plugin load FP images correctly as UINT16 (obviously losing HDR info). - HDR images saved with this patch are also loaded correctly by Gimp and Photoshop. - Grayscale images saved with this patch are also loaded correctly by Gimp and Photoshop. Compilation modifiers for cmake file: - `JXL_HDR_PRESERVATION_DISABLED`: disable the FP support (behaves like previous versions). - `JXL_DECODE_BOXES_DISABLED`: disable metadata reading (behaves like previous versions).
1231 lines
39 KiB
C++
1231 lines
39 KiB
C++
/*
|
|
JPEG XL (JXL) support for QImage.
|
|
|
|
SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <QThread>
|
|
#include <QtGlobal>
|
|
|
|
#include "jxl_p.h"
|
|
#include "util_p.h"
|
|
|
|
#include <jxl/encode.h>
|
|
#include <jxl/thread_parallel_runner.h>
|
|
#include <string.h>
|
|
|
|
// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575)
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 7) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) || (QT_VERSION >= QT_VERSION_CHECK(6, 7, 3))
|
|
#ifndef JXL_QT_AUTOTRANSFORM
|
|
#define JXL_QT_AUTOTRANSFORM
|
|
#endif
|
|
#endif
|
|
|
|
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
|
#ifndef JXL_HDR_PRESERVATION_DISABLED
|
|
// Define JXL_HDR_PRESERVATION_DISABLED to disable HDR preservation
|
|
// (HDR images are saved as UINT16).
|
|
#define JXL_HDR_PRESERVATION_DISABLED
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef JXL_DECODE_BOXES_DISABLED
|
|
// Decode Boxes in order to read optional metadata (XMP, Exif, etc...).
|
|
// Define JXL_DECODE_BOXES_DISABLED to disable Boxes decoding.
|
|
// #define JXL_DECODE_BOXES_DISABLED
|
|
#endif
|
|
|
|
#define FEATURE_LEVEL_5_WIDTH 262144
|
|
#define FEATURE_LEVEL_5_HEIGHT 262144
|
|
#define FEATURE_LEVEL_5_PIXELS 268435456
|
|
|
|
#if QT_POINTER_SIZE < 8
|
|
#define MAX_IMAGE_WIDTH 32767
|
|
#define MAX_IMAGE_HEIGHT 32767
|
|
#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
|
|
#else // JXL code stream level 5
|
|
#define MAX_IMAGE_WIDTH FEATURE_LEVEL_5_WIDTH
|
|
#define MAX_IMAGE_HEIGHT FEATURE_LEVEL_5_HEIGHT
|
|
#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
|
|
#endif
|
|
|
|
QJpegXLHandler::QJpegXLHandler()
|
|
: m_parseState(ParseJpegXLNotParsed)
|
|
, m_quality(90)
|
|
, m_currentimage_index(0)
|
|
, m_previousimage_index(-1)
|
|
, m_transformations(QImageIOHandler::TransformationNone)
|
|
, m_decoder(nullptr)
|
|
, m_runner(nullptr)
|
|
, m_next_image_delay(0)
|
|
, m_input_image_format(QImage::Format_Invalid)
|
|
, m_target_image_format(QImage::Format_Invalid)
|
|
, m_buffer_size(0)
|
|
{
|
|
}
|
|
|
|
QJpegXLHandler::~QJpegXLHandler()
|
|
{
|
|
if (m_runner) {
|
|
JxlThreadParallelRunnerDestroy(m_runner);
|
|
}
|
|
if (m_decoder) {
|
|
JxlDecoderDestroy(m_decoder);
|
|
}
|
|
}
|
|
|
|
bool QJpegXLHandler::canRead() const
|
|
{
|
|
if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
|
|
return false;
|
|
}
|
|
|
|
if (m_parseState != ParseJpegXLError) {
|
|
setFormat("jxl");
|
|
|
|
if (m_parseState == ParseJpegXLFinished) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QJpegXLHandler::canRead(QIODevice *device)
|
|
{
|
|
if (!device) {
|
|
return false;
|
|
}
|
|
QByteArray header = device->peek(32);
|
|
if (header.size() < 12) {
|
|
return false;
|
|
}
|
|
|
|
JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(header.constData()), header.size());
|
|
if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QJpegXLHandler::ensureParsed() const
|
|
{
|
|
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
|
|
return true;
|
|
}
|
|
if (m_parseState == ParseJpegXLError) {
|
|
return false;
|
|
}
|
|
|
|
QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
|
|
|
|
return that->ensureDecoder();
|
|
}
|
|
|
|
bool QJpegXLHandler::ensureALLCounted() const
|
|
{
|
|
if (!ensureParsed()) {
|
|
return false;
|
|
}
|
|
|
|
if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
|
|
return true;
|
|
}
|
|
|
|
QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
|
|
|
|
return that->countALLFrames();
|
|
}
|
|
|
|
bool QJpegXLHandler::ensureDecoder()
|
|
{
|
|
if (m_decoder) {
|
|
return true;
|
|
}
|
|
|
|
m_rawData = device()->readAll();
|
|
|
|
if (m_rawData.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size());
|
|
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
m_decoder = JxlDecoderCreate(nullptr);
|
|
if (!m_decoder) {
|
|
qWarning("ERROR: JxlDecoderCreate failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
#ifdef JXL_QT_AUTOTRANSFORM
|
|
// Let Qt handle the orientation.
|
|
JxlDecoderSetKeepOrientation(m_decoder, true);
|
|
#endif
|
|
|
|
int num_worker_threads = QThread::idealThreadCount();
|
|
if (!m_runner && num_worker_threads >= 4) {
|
|
/* use half of the threads because plug-in is usually used in environment
|
|
* where application performs another tasks in backround (pre-load other images) */
|
|
num_worker_threads = num_worker_threads / 2;
|
|
num_worker_threads = qBound(2, num_worker_threads, 64);
|
|
m_runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
|
|
|
|
if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSetParallelRunner failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSetInput failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
JxlDecoderCloseInput(m_decoder);
|
|
#ifndef JXL_DECODE_BOXES_DISABLED
|
|
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_BOX);
|
|
#else
|
|
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
|
|
#endif
|
|
if (status == JXL_DEC_ERROR) {
|
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (!decodeBoxes()) {
|
|
return false;
|
|
}
|
|
|
|
status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
|
|
if (status != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JXL basic info not available");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
|
|
qWarning("ERROR: JXL image has zero dimensions");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
|
|
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
m_parseState = ParseJpegXLBasicInfoParsed;
|
|
return true;
|
|
}
|
|
|
|
bool QJpegXLHandler::countALLFrames()
|
|
{
|
|
if (m_parseState != ParseJpegXLBasicInfoParsed) {
|
|
return false;
|
|
}
|
|
|
|
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
|
|
if (status != JXL_DEC_COLOR_ENCODING) {
|
|
qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
JxlColorEncoding color_encoding;
|
|
if (m_basicinfo.uses_original_profile == JXL_FALSE) {
|
|
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
|
|
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
|
|
}
|
|
|
|
bool loadalpha;
|
|
|
|
if (m_basicinfo.alpha_bits > 0) {
|
|
loadalpha = true;
|
|
} else {
|
|
loadalpha = false;
|
|
}
|
|
|
|
m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
|
m_input_pixel_format.align = 0;
|
|
m_input_pixel_format.num_channels = m_basicinfo.num_color_channels == 1 ? 1 : 4;
|
|
|
|
if (m_basicinfo.bits_per_sample > 8) { // high bit depth
|
|
#ifdef JXL_HDR_PRESERVATION_DISABLED
|
|
bool is_fp = false;
|
|
#else
|
|
bool is_fp = m_basicinfo.exponent_bits_per_sample > 0 && m_basicinfo.num_color_channels == 3;
|
|
#endif
|
|
m_input_pixel_format.data_type = is_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
|
|
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
|
|
|
|
if (m_basicinfo.num_color_channels == 1) {
|
|
m_input_pixel_format.data_type = JXL_TYPE_UINT16;
|
|
m_input_image_format = m_target_image_format = QImage::Format_Grayscale16;
|
|
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
|
|
} else if (m_basicinfo.bits_per_sample > 16 && is_fp) {
|
|
m_input_pixel_format.data_type = JXL_TYPE_FLOAT;
|
|
m_input_image_format = QImage::Format_RGBA32FPx4;
|
|
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 4;
|
|
if (loadalpha)
|
|
m_target_image_format = QImage::Format_RGBA32FPx4;
|
|
else
|
|
m_target_image_format = QImage::Format_RGBX32FPx4;
|
|
} else {
|
|
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
|
|
m_input_image_format = is_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
|
|
if (loadalpha)
|
|
m_target_image_format = is_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
|
|
else
|
|
m_target_image_format = is_fp ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
|
|
}
|
|
} else { // 8bit depth
|
|
m_input_pixel_format.data_type = JXL_TYPE_UINT8;
|
|
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels;
|
|
|
|
if (m_basicinfo.num_color_channels == 1) {
|
|
m_input_image_format = m_target_image_format = QImage::Format_Grayscale8;
|
|
} else {
|
|
m_input_image_format = QImage::Format_RGBA8888;
|
|
if (loadalpha) {
|
|
m_target_image_format = QImage::Format_ARGB32;
|
|
} else {
|
|
m_target_image_format = QImage::Format_RGB32;
|
|
}
|
|
}
|
|
}
|
|
|
|
status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
|
|
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
|
&m_input_pixel_format,
|
|
#endif
|
|
JXL_COLOR_PROFILE_TARGET_DATA,
|
|
&color_encoding);
|
|
|
|
if (status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
|
|
&& color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
|
|
m_colorspace = QColorSpace(QColorSpace::SRgb);
|
|
} else {
|
|
size_t icc_size = 0;
|
|
if (JxlDecoderGetICCProfileSize(m_decoder,
|
|
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
|
&m_input_pixel_format,
|
|
#endif
|
|
JXL_COLOR_PROFILE_TARGET_DATA,
|
|
&icc_size)
|
|
== JXL_DEC_SUCCESS) {
|
|
if (icc_size > 0) {
|
|
QByteArray icc_data(icc_size, 0);
|
|
if (JxlDecoderGetColorAsICCProfile(m_decoder,
|
|
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
|
&m_input_pixel_format,
|
|
#endif
|
|
JXL_COLOR_PROFILE_TARGET_DATA,
|
|
reinterpret_cast<uint8_t *>(icc_data.data()),
|
|
icc_data.size())
|
|
== JXL_DEC_SUCCESS) {
|
|
m_colorspace = QColorSpace::fromIccProfile(icc_data);
|
|
|
|
if (!m_colorspace.isValid()) {
|
|
qWarning("JXL image has Qt-unsupported or invalid ICC profile!");
|
|
}
|
|
} else {
|
|
qWarning("Failed to obtain data from JPEG XL decoder");
|
|
}
|
|
} else {
|
|
qWarning("Empty ICC data");
|
|
}
|
|
} else {
|
|
qWarning("no ICC, other color profile");
|
|
}
|
|
}
|
|
|
|
if (m_basicinfo.have_animation) { // count all frames
|
|
JxlFrameHeader frame_header;
|
|
int delay;
|
|
|
|
for (status = JxlDecoderProcessInput(m_decoder); status != JXL_DEC_SUCCESS; status = JxlDecoderProcessInput(m_decoder)) {
|
|
if (status != JXL_DEC_FRAME) {
|
|
switch (status) {
|
|
case JXL_DEC_ERROR:
|
|
qWarning("ERROR: JXL decoding failed");
|
|
break;
|
|
case JXL_DEC_NEED_MORE_INPUT:
|
|
qWarning("ERROR: JXL data incomplete");
|
|
break;
|
|
default:
|
|
qWarning("Unexpected event %d instead of JXL_DEC_FRAME", status);
|
|
break;
|
|
}
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderGetFrameHeader failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
|
|
delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
|
|
} else {
|
|
delay = 0;
|
|
}
|
|
|
|
m_framedelays.append(delay);
|
|
}
|
|
|
|
if (m_framedelays.isEmpty()) {
|
|
qWarning("no frames loaded by the JXL plug-in");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (m_framedelays.count() == 1) {
|
|
qWarning("JXL file was marked as animation but it has only one frame.");
|
|
m_basicinfo.have_animation = JXL_FALSE;
|
|
}
|
|
} else { // static picture
|
|
m_framedelays.resize(1);
|
|
m_framedelays[0] = 0;
|
|
}
|
|
|
|
#ifndef JXL_DECODE_BOXES_DISABLED
|
|
if (!decodeBoxes()) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (!rewind()) {
|
|
return false;
|
|
}
|
|
|
|
m_next_image_delay = m_framedelays[0];
|
|
m_parseState = ParseJpegXLSuccess;
|
|
return true;
|
|
}
|
|
|
|
bool QJpegXLHandler::decode_one_frame()
|
|
{
|
|
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
|
|
if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
|
qWarning("Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
|
|
if (m_current_image.isNull()) {
|
|
qWarning("Memory cannot be allocated");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
m_current_image.setColorSpace(m_colorspace);
|
|
if (!m_xmp.isEmpty()) {
|
|
m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromUtf8(m_xmp));
|
|
}
|
|
|
|
if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
status = JxlDecoderProcessInput(m_decoder);
|
|
if (status != JXL_DEC_FULL_IMAGE) {
|
|
qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
if (m_target_image_format != m_input_image_format) {
|
|
m_current_image.convertTo(m_target_image_format);
|
|
}
|
|
|
|
m_next_image_delay = m_framedelays[m_currentimage_index];
|
|
m_previousimage_index = m_currentimage_index;
|
|
|
|
if (m_framedelays.count() > 1) {
|
|
m_currentimage_index++;
|
|
|
|
if (m_currentimage_index >= m_framedelays.count()) {
|
|
if (!rewind()) {
|
|
return false;
|
|
}
|
|
|
|
// all frames in animation have been read
|
|
m_parseState = ParseJpegXLFinished;
|
|
} else {
|
|
m_parseState = ParseJpegXLSuccess;
|
|
}
|
|
} else {
|
|
// the static image has been read
|
|
m_parseState = ParseJpegXLFinished;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QJpegXLHandler::read(QImage *image)
|
|
{
|
|
if (!ensureALLCounted()) {
|
|
return false;
|
|
}
|
|
|
|
if (m_currentimage_index == m_previousimage_index) {
|
|
*image = m_current_image;
|
|
return jumpToNextImage();
|
|
}
|
|
|
|
if (decode_one_frame()) {
|
|
*image = m_current_image;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
void packRGBPixels(QImage &img)
|
|
{
|
|
// pack pixel data
|
|
auto dest_pixels = reinterpret_cast<T *>(img.bits());
|
|
for (qint32 y = 0; y < img.height(); y++) {
|
|
auto src_pixels = reinterpret_cast<const T *>(img.constScanLine(y));
|
|
for (qint32 x = 0; x < img.width(); x++) {
|
|
// R
|
|
*dest_pixels = *src_pixels;
|
|
dest_pixels++;
|
|
src_pixels++;
|
|
// G
|
|
*dest_pixels = *src_pixels;
|
|
dest_pixels++;
|
|
src_pixels++;
|
|
// B
|
|
*dest_pixels = *src_pixels;
|
|
dest_pixels++;
|
|
src_pixels += 2; // skipalpha
|
|
}
|
|
}
|
|
}
|
|
|
|
bool QJpegXLHandler::write(const QImage &image)
|
|
{
|
|
if (image.format() == QImage::Format_Invalid) {
|
|
qWarning("No image data to save");
|
|
return false;
|
|
}
|
|
|
|
if ((image.width() == 0) || (image.height() == 0)) {
|
|
qWarning("Image has zero dimension!");
|
|
return false;
|
|
}
|
|
|
|
if ((image.width() > MAX_IMAGE_WIDTH) || (image.height() > MAX_IMAGE_HEIGHT)) {
|
|
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
|
|
return false;
|
|
}
|
|
|
|
size_t pixel_count = size_t(image.width()) * image.height();
|
|
if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
|
|
qWarning("Image (%dx%d) will not be saved because it has more than %d megapixels!", image.width(), image.height(), MAX_IMAGE_PIXELS / 1024 / 1024);
|
|
return false;
|
|
}
|
|
|
|
int save_depth = 8; // 8 / 16 / 32
|
|
bool save_fp = false;
|
|
bool is_gray = false;
|
|
// depth detection
|
|
switch (image.format()) {
|
|
case QImage::Format_RGBX32FPx4:
|
|
case QImage::Format_RGBA32FPx4:
|
|
case QImage::Format_RGBA32FPx4_Premultiplied:
|
|
#ifndef JXL_HDR_PRESERVATION_DISABLED
|
|
save_depth = 32;
|
|
save_fp = true;
|
|
break;
|
|
#endif
|
|
case QImage::Format_RGBX16FPx4:
|
|
case QImage::Format_RGBA16FPx4:
|
|
case QImage::Format_RGBA16FPx4_Premultiplied:
|
|
#ifndef JXL_HDR_PRESERVATION_DISABLED
|
|
save_depth = 16;
|
|
save_fp = true;
|
|
break;
|
|
#endif
|
|
case QImage::Format_BGR30:
|
|
case QImage::Format_A2BGR30_Premultiplied:
|
|
case QImage::Format_RGB30:
|
|
case QImage::Format_A2RGB30_Premultiplied:
|
|
case QImage::Format_RGBX64:
|
|
case QImage::Format_RGBA64:
|
|
case QImage::Format_RGBA64_Premultiplied:
|
|
save_depth = 16;
|
|
break;
|
|
case QImage::Format_RGB32:
|
|
case QImage::Format_ARGB32:
|
|
case QImage::Format_ARGB32_Premultiplied:
|
|
case QImage::Format_RGB888:
|
|
case QImage::Format_RGBX8888:
|
|
case QImage::Format_RGBA8888:
|
|
case QImage::Format_RGBA8888_Premultiplied:
|
|
save_depth = 8;
|
|
break;
|
|
case QImage::Format_Grayscale16:
|
|
save_depth = 16;
|
|
is_gray = true;
|
|
break;
|
|
case QImage::Format_Grayscale8:
|
|
case QImage::Format_Alpha8:
|
|
case QImage::Format_Mono:
|
|
case QImage::Format_MonoLSB:
|
|
save_depth = 8;
|
|
is_gray = true;
|
|
break;
|
|
case QImage::Format_Indexed8:
|
|
save_depth = 8;
|
|
is_gray = image.isGrayscale();
|
|
break;
|
|
default:
|
|
if (image.depth() > 32) {
|
|
save_depth = 16;
|
|
} else {
|
|
save_depth = 8;
|
|
}
|
|
break;
|
|
}
|
|
|
|
JxlEncoder *encoder = JxlEncoderCreate(nullptr);
|
|
if (!encoder) {
|
|
qWarning("Failed to create Jxl encoder");
|
|
return false;
|
|
}
|
|
JxlEncoderUseBoxes(encoder);
|
|
|
|
if (m_quality > 100) {
|
|
m_quality = 100;
|
|
} else if (m_quality < 0) {
|
|
m_quality = 90;
|
|
}
|
|
|
|
JxlBasicInfo output_info;
|
|
JxlEncoderInitBasicInfo(&output_info);
|
|
|
|
QByteArray iccprofile;
|
|
QColorSpace tmpcs = image.colorSpace();
|
|
if (!tmpcs.isValid() || tmpcs.primaries() != QColorSpace::Primaries::SRgb || tmpcs.transferFunction() != QColorSpace::TransferFunction::SRgb || m_quality == 100) {
|
|
// no profile or Qt-unsupported ICC profile
|
|
iccprofile = tmpcs.iccProfile();
|
|
// note: lossless encoding requires uses_original_profile = JXL_TRUE
|
|
if (iccprofile.size() > 0 || m_quality == 100) {
|
|
output_info.uses_original_profile = JXL_TRUE;
|
|
}
|
|
}
|
|
|
|
// clang-format off
|
|
if ( (save_depth > 8 && (image.hasAlphaChannel() || output_info.uses_original_profile))
|
|
|| (save_depth > 16)
|
|
|| (pixel_count > FEATURE_LEVEL_5_PIXELS)
|
|
|| (image.width() > FEATURE_LEVEL_5_WIDTH)
|
|
|| (image.height() > FEATURE_LEVEL_5_HEIGHT)) {
|
|
output_info.have_container = JXL_TRUE;
|
|
JxlEncoderUseContainer(encoder, JXL_TRUE);
|
|
JxlEncoderSetCodestreamLevel(encoder, 10);
|
|
}
|
|
// clang-format on
|
|
|
|
void *runner = nullptr;
|
|
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
|
|
|
|
if (num_worker_threads > 1) {
|
|
runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
|
|
if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
|
|
qWarning("JxlEncoderSetParallelRunner failed");
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JxlPixelFormat pixel_format;
|
|
QImage::Format tmpformat;
|
|
JxlEncoderStatus status;
|
|
|
|
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
|
pixel_format.align = 0;
|
|
|
|
output_info.animation.tps_numerator = 10;
|
|
output_info.animation.tps_denominator = 1;
|
|
output_info.orientation = JXL_ORIENT_IDENTITY;
|
|
if (m_transformations == QImageIOHandler::TransformationMirror) {
|
|
output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
|
|
} else if (m_transformations == QImageIOHandler::TransformationRotate180) {
|
|
output_info.orientation = JXL_ORIENT_ROTATE_180;
|
|
} else if (m_transformations == QImageIOHandler::TransformationFlip) {
|
|
output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
|
|
} else if (m_transformations == QImageIOHandler::TransformationFlipAndRotate90) {
|
|
output_info.orientation = JXL_ORIENT_TRANSPOSE;
|
|
} else if (m_transformations == QImageIOHandler::TransformationRotate90) {
|
|
output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
|
|
} else if (m_transformations == QImageIOHandler::TransformationMirrorAndRotate90) {
|
|
output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
|
|
} else if (m_transformations == QImageIOHandler::TransformationRotate270) {
|
|
output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
|
|
}
|
|
|
|
if (save_depth > 8 && is_gray) { // 16bit depth gray
|
|
pixel_format.data_type = JXL_TYPE_UINT16;
|
|
pixel_format.align = 4;
|
|
output_info.num_color_channels = 1;
|
|
output_info.bits_per_sample = 16;
|
|
tmpformat = QImage::Format_Grayscale16;
|
|
pixel_format.num_channels = 1;
|
|
} else if (is_gray) { // 8bit depth gray
|
|
pixel_format.data_type = JXL_TYPE_UINT8;
|
|
pixel_format.align = 4;
|
|
output_info.num_color_channels = 1;
|
|
output_info.bits_per_sample = 8;
|
|
tmpformat = QImage::Format_Grayscale8;
|
|
pixel_format.num_channels = 1;
|
|
} else if (save_depth > 16) { // 32bit depth rgb
|
|
pixel_format.data_type = JXL_TYPE_FLOAT;
|
|
output_info.exponent_bits_per_sample = 8;
|
|
output_info.num_color_channels = 3;
|
|
output_info.bits_per_sample = 32;
|
|
|
|
if (image.hasAlphaChannel()) {
|
|
tmpformat = QImage::Format_RGBA32FPx4;
|
|
pixel_format.num_channels = 4;
|
|
output_info.alpha_bits = 32;
|
|
output_info.alpha_exponent_bits = 8;
|
|
output_info.num_extra_channels = 1;
|
|
} else {
|
|
tmpformat = QImage::Format_RGBX32FPx4;
|
|
pixel_format.num_channels = 3;
|
|
output_info.alpha_bits = 0;
|
|
output_info.num_extra_channels = 0;
|
|
}
|
|
} else if (save_depth > 8) { // 16bit depth rgb
|
|
pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
|
|
output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
|
|
output_info.num_color_channels = 3;
|
|
output_info.bits_per_sample = 16;
|
|
|
|
if (image.hasAlphaChannel()) {
|
|
tmpformat = save_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
|
|
pixel_format.num_channels = 4;
|
|
output_info.alpha_bits = 16;
|
|
output_info.alpha_exponent_bits = save_fp ? 5 : 0;
|
|
output_info.num_extra_channels = 1;
|
|
} else {
|
|
tmpformat = save_fp ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
|
|
pixel_format.num_channels = 3;
|
|
output_info.alpha_bits = 0;
|
|
output_info.num_extra_channels = 0;
|
|
}
|
|
} else { // 8bit depth rgb
|
|
pixel_format.data_type = JXL_TYPE_UINT8;
|
|
pixel_format.align = 4;
|
|
output_info.num_color_channels = 3;
|
|
output_info.bits_per_sample = 8;
|
|
|
|
if (image.hasAlphaChannel()) {
|
|
tmpformat = QImage::Format_RGBA8888;
|
|
pixel_format.num_channels = 4;
|
|
output_info.alpha_bits = 8;
|
|
output_info.num_extra_channels = 1;
|
|
} else {
|
|
tmpformat = QImage::Format_RGB888;
|
|
pixel_format.num_channels = 3;
|
|
output_info.alpha_bits = 0;
|
|
output_info.num_extra_channels = 0;
|
|
}
|
|
}
|
|
|
|
QImage tmpimage = image.convertToFormat(tmpformat);
|
|
const size_t xsize = tmpimage.width();
|
|
const size_t ysize = tmpimage.height();
|
|
|
|
if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
|
|
qWarning("Unable to allocate memory for output image");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
|
|
output_info.xsize = tmpimage.width();
|
|
output_info.ysize = tmpimage.height();
|
|
|
|
status = JxlEncoderSetBasicInfo(encoder, &output_info);
|
|
if (status != JXL_ENC_SUCCESS) {
|
|
qWarning("JxlEncoderSetBasicInfo failed!");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
|
|
auto xmp_data = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
|
|
if (!xmp_data.isEmpty()) {
|
|
const char *box_type = "xml ";
|
|
status = JxlEncoderAddBox(encoder, box_type, reinterpret_cast<const uint8_t *>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
|
|
if (status != JXL_ENC_SUCCESS) {
|
|
qWarning("JxlEncoderAddBox failed!");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
}
|
|
JxlEncoderCloseBoxes(encoder); // no more metadata
|
|
|
|
if (iccprofile.size() > 0) {
|
|
status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
|
if (status != JXL_ENC_SUCCESS) {
|
|
qWarning("JxlEncoderSetICCProfile failed!");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
} else {
|
|
JxlColorEncoding color_profile;
|
|
JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
|
|
|
|
status = JxlEncoderSetColorEncoding(encoder, &color_profile);
|
|
if (status != JXL_ENC_SUCCESS) {
|
|
qWarning("JxlEncoderSetColorEncoding failed!");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
|
|
|
|
JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
|
|
|
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
|
|
|
size_t buffer_size = size_t(tmpimage.bytesPerLine()) * tmpimage.height();
|
|
if (!image.hasAlphaChannel() && save_depth > 8 && !is_gray) { // pack pixel on tmpimage
|
|
buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
|
|
|
|
// detaching image
|
|
tmpimage.detach();
|
|
if (tmpimage.isNull()) {
|
|
qWarning("Memory allocation error");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
|
|
// pack pixel data
|
|
if (save_depth > 16 && save_fp)
|
|
packRGBPixels<float>(tmpimage);
|
|
else if (save_fp)
|
|
packRGBPixels<qfloat16>(tmpimage);
|
|
else
|
|
packRGBPixels<quint16>(tmpimage);
|
|
}
|
|
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
|
|
|
|
if (status == JXL_ENC_ERROR) {
|
|
qWarning("JxlEncoderAddImageFrame failed!");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
|
|
JxlEncoderCloseInput(encoder);
|
|
|
|
std::vector<uint8_t> compressed;
|
|
compressed.resize(4096);
|
|
size_t offset = 0;
|
|
uint8_t *next_out;
|
|
size_t avail_out;
|
|
do {
|
|
next_out = compressed.data() + offset;
|
|
avail_out = compressed.size() - offset;
|
|
status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
|
|
|
|
if (status == JXL_ENC_NEED_MORE_OUTPUT) {
|
|
offset = next_out - compressed.data();
|
|
compressed.resize(compressed.size() * 2);
|
|
} else if (status == JXL_ENC_ERROR) {
|
|
qWarning("JxlEncoderProcessOutput failed!");
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
return false;
|
|
}
|
|
} while (status != JXL_ENC_SUCCESS);
|
|
|
|
if (runner) {
|
|
JxlThreadParallelRunnerDestroy(runner);
|
|
}
|
|
JxlEncoderDestroy(encoder);
|
|
|
|
compressed.resize(next_out - compressed.data());
|
|
|
|
if (compressed.size() > 0) {
|
|
qint64 write_status = device()->write(reinterpret_cast<const char *>(compressed.data()), compressed.size());
|
|
|
|
if (write_status > 0) {
|
|
return true;
|
|
} else if (write_status == -1) {
|
|
qWarning("Write error: %s\n", qUtf8Printable(device()->errorString()));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QVariant QJpegXLHandler::option(ImageOption option) const
|
|
{
|
|
if (!supportsOption(option)) {
|
|
return QVariant();
|
|
}
|
|
|
|
if (option == Quality) {
|
|
return m_quality;
|
|
}
|
|
|
|
if (!ensureParsed()) {
|
|
#ifdef JXL_QT_AUTOTRANSFORM
|
|
if (option == ImageTransformation) {
|
|
return int(m_transformations);
|
|
}
|
|
#endif
|
|
return QVariant();
|
|
}
|
|
|
|
|
|
switch (option) {
|
|
case Size:
|
|
return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
|
|
case Animation:
|
|
if (m_basicinfo.have_animation) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
#ifdef JXL_QT_AUTOTRANSFORM
|
|
case ImageTransformation:
|
|
if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
|
|
return int(QImageIOHandler::TransformationNone);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
|
|
return int(QImageIOHandler::TransformationMirror);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
|
|
return int(QImageIOHandler::TransformationRotate180);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
|
|
return int(QImageIOHandler::TransformationFlip);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
|
|
return int(QImageIOHandler::TransformationFlipAndRotate90);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
|
|
return int(QImageIOHandler::TransformationRotate90);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
|
|
return int(QImageIOHandler::TransformationMirrorAndRotate90);
|
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
|
|
return int(QImageIOHandler::TransformationRotate270);
|
|
}
|
|
#endif
|
|
default:
|
|
return QVariant();
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
|
|
{
|
|
switch (option) {
|
|
case Quality:
|
|
m_quality = value.toInt();
|
|
if (m_quality > 100) {
|
|
m_quality = 100;
|
|
} else if (m_quality < 0) {
|
|
m_quality = 90;
|
|
}
|
|
return;
|
|
#ifdef JXL_QT_AUTOTRANSFORM
|
|
case ImageTransformation:
|
|
if (auto t = value.toInt()) {
|
|
if (t > 0 && t < 8)
|
|
m_transformations = QImageIOHandler::Transformations(t);
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
QImageIOHandler::setOption(option, value);
|
|
}
|
|
|
|
bool QJpegXLHandler::supportsOption(ImageOption option) const
|
|
{
|
|
auto supported = option == Quality || option == Size || option == Animation;
|
|
#ifdef JXL_QT_AUTOTRANSFORM
|
|
supported = supported || option == ImageTransformation;
|
|
#endif
|
|
return supported;
|
|
}
|
|
|
|
int QJpegXLHandler::imageCount() const
|
|
{
|
|
if (!ensureParsed()) {
|
|
return 0;
|
|
}
|
|
|
|
if (m_parseState == ParseJpegXLBasicInfoParsed) {
|
|
if (!m_basicinfo.have_animation) {
|
|
return 1;
|
|
}
|
|
|
|
if (!ensureALLCounted()) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!m_framedelays.isEmpty()) {
|
|
return m_framedelays.count();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int QJpegXLHandler::currentImageNumber() const
|
|
{
|
|
if (m_parseState == ParseJpegXLNotParsed) {
|
|
return -1;
|
|
}
|
|
|
|
if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
|
|
return 0;
|
|
}
|
|
|
|
return m_currentimage_index;
|
|
}
|
|
|
|
bool QJpegXLHandler::jumpToNextImage()
|
|
{
|
|
if (!ensureALLCounted()) {
|
|
return false;
|
|
}
|
|
|
|
if (m_framedelays.count() > 1) {
|
|
m_currentimage_index++;
|
|
|
|
if (m_currentimage_index >= m_framedelays.count()) {
|
|
if (!rewind()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
JxlDecoderSkipFrames(m_decoder, 1);
|
|
}
|
|
}
|
|
|
|
m_parseState = ParseJpegXLSuccess;
|
|
return true;
|
|
}
|
|
|
|
bool QJpegXLHandler::jumpToImage(int imageNumber)
|
|
{
|
|
if (!ensureALLCounted()) {
|
|
return false;
|
|
}
|
|
|
|
if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
|
|
return false;
|
|
}
|
|
|
|
if (imageNumber == m_currentimage_index) {
|
|
m_parseState = ParseJpegXLSuccess;
|
|
return true;
|
|
}
|
|
|
|
if (imageNumber > m_currentimage_index) {
|
|
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
|
|
m_currentimage_index = imageNumber;
|
|
m_parseState = ParseJpegXLSuccess;
|
|
return true;
|
|
}
|
|
|
|
if (!rewind()) {
|
|
return false;
|
|
}
|
|
|
|
if (imageNumber > 0) {
|
|
JxlDecoderSkipFrames(m_decoder, imageNumber);
|
|
}
|
|
m_currentimage_index = imageNumber;
|
|
m_parseState = ParseJpegXLSuccess;
|
|
return true;
|
|
}
|
|
|
|
int QJpegXLHandler::nextImageDelay() const
|
|
{
|
|
if (!ensureALLCounted()) {
|
|
return 0;
|
|
}
|
|
|
|
if (m_framedelays.count() < 2) {
|
|
return 0;
|
|
}
|
|
|
|
return m_next_image_delay;
|
|
}
|
|
|
|
int QJpegXLHandler::loopCount() const
|
|
{
|
|
if (!ensureParsed()) {
|
|
return 0;
|
|
}
|
|
|
|
if (m_basicinfo.have_animation) {
|
|
return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool QJpegXLHandler::rewind()
|
|
{
|
|
m_currentimage_index = 0;
|
|
|
|
JxlDecoderReleaseInput(m_decoder);
|
|
JxlDecoderRewind(m_decoder);
|
|
if (m_runner) {
|
|
if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSetParallelRunner failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSetInput failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
JxlDecoderCloseInput(m_decoder);
|
|
|
|
if (m_basicinfo.uses_original_profile) {
|
|
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
} else {
|
|
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
|
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
|
|
if (status != JXL_DEC_COLOR_ENCODING) {
|
|
qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
|
|
JxlColorEncoding color_encoding;
|
|
JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
|
|
JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QJpegXLHandler::decodeBoxes()
|
|
{
|
|
JxlDecoderStatus status;
|
|
do { // decode metadata
|
|
status = JxlDecoderProcessInput(m_decoder);
|
|
if (status == JXL_DEC_BOX) {
|
|
JxlBoxType type;
|
|
JxlDecoderGetBoxType(m_decoder, type, JXL_FALSE);
|
|
if (memcmp(type, "xml ", 4) == 0) {
|
|
uint64_t size;
|
|
if (JxlDecoderGetBoxSizeRaw(m_decoder, &size) == JXL_DEC_SUCCESS) {
|
|
m_xmp = QByteArray(size, '\0');
|
|
JxlDecoderSetBoxBuffer(m_decoder, reinterpret_cast<uint8_t *>(m_xmp.data()), m_xmp.size());
|
|
}
|
|
}
|
|
}
|
|
} while (status == JXL_DEC_BOX);
|
|
|
|
if (status == JXL_DEC_ERROR) {
|
|
qWarning("ERROR: JXL decoding failed");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
if (status == JXL_DEC_NEED_MORE_INPUT) {
|
|
qWarning("ERROR: JXL data incomplete");
|
|
m_parseState = ParseJpegXLError;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QImageIOPlugin::Capabilities QJpegXLPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
if (format == "jxl") {
|
|
return Capabilities(CanRead | CanWrite);
|
|
}
|
|
|
|
if (!format.isEmpty()) {
|
|
return {};
|
|
}
|
|
if (!device->isOpen()) {
|
|
return {};
|
|
}
|
|
|
|
Capabilities cap;
|
|
if (device->isReadable() && QJpegXLHandler::canRead(device)) {
|
|
cap |= CanRead;
|
|
}
|
|
|
|
if (device->isWritable()) {
|
|
cap |= CanWrite;
|
|
}
|
|
|
|
return cap;
|
|
}
|
|
|
|
QImageIOHandler *QJpegXLPlugin::create(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
QImageIOHandler *handler = new QJpegXLHandler;
|
|
handler->setDevice(device);
|
|
handler->setFormat(format);
|
|
return handler;
|
|
}
|
|
|
|
#include "moc_jxl_p.cpp"
|