Commit 13e5d2e5 authored by Amy spark's avatar Amy spark 👉
Browse files

[FEATURE] Add support for JPEG-XL

This commit adds support for the following features:

- RGB, Gray and CMYK U8/U16/F16/F32
- Animated JXLs r/w
- Exif, XMP, and IPTC metadata r/w
- Parallel encoding & decoding (there's a step that isn't parallelized
  on libjxl yet, though)

Note that:
- for F16 images, the import plugin will ask libjxl to return
a F32 image in order not to depend on OpenEXR.
- JXL is a single-layer format, the export will
use the document projection-- individual layers will be lost.

This is the first plugin to introduce full native animation import &
export in Krita.

CCMAIL: kimageshop@kde.org
parent f3c90150
Pipeline #172577 passed with stage
in 37 minutes and 52 seconds
......@@ -817,6 +817,16 @@ set_package_properties(LibRaw PROPERTIES
TYPE OPTIONAL
PURPOSE "Required to build the raw import plugin")
find_package(JPEGXL 0.7.0)
set_package_properties(JPEGXL PROPERTIES
DESCRIPTION "JPEG XL is a royalty-free raster-graphics file format that supports both lossy and lossless compression and is experimentally supported by Chrome, Firefox, and Edge."
URL "https://github.com/libjxl/libjxl"
TYPE OPTIONAL
PURPOSE "Required by the Krita JPEG-XL filter")
if (JPEGXL_FOUND)
list (APPEND ANDROID_EXTRA_LIBS ${JPEGXL_LIBRARIES})
endif()
find_package(FFTW3)
set_package_properties(FFTW3 PROPERTIES
DESCRIPTION "A fast, free C FFT library"
......
# Copyright (C) 2021 Sony Interactive Entertainment Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#[=======================================================================[.rst:
FindJPEGXL
---------
Find JPEGXL headers and libraries.
Imported Targets
^^^^^^^^^^^^^^^^
``JPEGXL::jxl``
The JPEGXL library, if found.
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables in your project:
``JPEGXL_FOUND``
true if (the requested version of) JPEGXL is available.
``JPEGXL_VERSION``
the version of JPEGXL.
``JPEGXL_LIBRARIES``
the libraries to link against to use JPEGXL.
``JPEGXL_INCLUDE_DIRS``
where to find the JPEGXL headers.
``JPEGXL_COMPILE_OPTIONS``
this should be passed to target_compile_options(), if the
target is not used for linking
#]=======================================================================]
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(PC_JPEGXL QUIET libjxl)
set(JPEGXL_COMPILE_OPTIONS "${PC_JPEGXL_CFLAGS_OTHER}")
set(JPEGXL_VERSION ${PC_JPEGXL_VERSION})
pkg_check_modules(PC_JPEGXL_THREADS QUIET libjxl_threads)
set(JPEGXL_THREADS_COMPILE_OPTIONS ${PC_JPEGXL_THREADS_CFLAGS_OTHER})
endif ()
find_path(JPEGXL_INCLUDE_DIR
NAMES jxl/decode.h
HINTS ${PC_JPEGXL_INCLUDEDIR} ${PC_JPEGXL_INCLUDE_DIRS} ${JPEGXL_INCLUDE_DIR}
)
find_library(JPEGXL_LIBRARY
NAMES ${JPEGXL_NAMES} jxl
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
find_library(JPEGXL_THREADS_LIBRARY
NAMES ${JPEGXL_NAMES} jxl_threads
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} ${PC_JPEGXL_THREADS_LIBDIR} ${PC_JPEGXL_THREADS_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(JPEGXL
FOUND_VAR JPEGXL_FOUND
REQUIRED_VARS JPEGXL_LIBRARY JPEGXL_INCLUDE_DIR
VERSION_VAR JPEGXL_VERSION
)
if (JPEGXL_LIBRARY AND NOT TARGET JPEGXL::jxl)
add_library(JPEGXL::jxl UNKNOWN IMPORTED GLOBAL)
set_target_properties(JPEGXL::jxl PROPERTIES
IMPORTED_LOCATION "${JPEGXL_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${JPEGXL_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${JPEGXL_INCLUDE_DIR}"
)
endif ()
if (JPEGXL_THREADS_LIBRARY AND NOT TARGET JPEGXL::jxl_threads)
add_library(JPEGXL::jxl_threads UNKNOWN IMPORTED GLOBAL)
set_target_properties(JPEGXL::jxl_threads PROPERTIES
IMPORTED_LOCATION "${JPEGXL_THREADS_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${JPEGXL_THREADS_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${JPEGXL_INCLUDE_DIR}"
)
endif ()
mark_as_advanced(JPEGXL_INCLUDE_DIR JPEGXL_LIBRARY JPEGXL_THREADS_LIBRARY)
if (JPEGXL_FOUND)
set(JPEGXL_LIBRARIES ${JPEGXL_LIBRARY})
set(JPEGXL_INCLUDE_DIRS ${JPEGXL_INCLUDE_DIR})
endif ()
if (JPEGXL_THREADS_LIBRARY)
list(APPEND JPEGXL_LIBRARIES ${JPEGXL_THREADS_LIBRARY})
endif ()
......@@ -340,6 +340,11 @@ void KisMimeDatabase::fillMimeData()
mimeType.suffixes = QStringList() << "apng";
s_mimeDatabase << mimeType;
mimeType.mimeType = "image/jxl";
mimeType.description = i18nc("description of a file type", "JPEG-XL Image");
mimeType.suffixes = QStringList() << "jxl";
s_mimeDatabase << mimeType;
dbgPlugins << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes";
}
}
......@@ -71,6 +71,7 @@
<data android:mimeType="application/x-krita-paintoppreset" />
<data android:mimeType="application/pdf" />
<data android:mimeType="image/jxl" />
</intent-filter>
<intent-filter>
......
......@@ -60,3 +60,7 @@ add_subdirectory(krz)
if (WebP_FOUND)
add_subdirectory(webp)
endif()
if (JPEGXL_FOUND)
add_subdirectory(jxl)
endif()
add_subdirectory(tests)
set(kritajxlimport_SOURCES
JPEGXLImport.cpp
)
add_library(kritajxlimport MODULE ${kritajxlimport_SOURCES})
target_link_libraries(kritajxlimport kritaui kritalibkra kritametadata ${JPEGXL_LIBRARIES})
install(TARGETS kritajxlimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritajxlexport_SOURCES
JPEGXLExport.cpp
kis_wdg_options_jpegxl.cpp
)
ki18n_wrap_ui(kritajxlexport_SOURCES kis_wdg_options_jpegxl.ui )
add_library(kritajxlexport MODULE ${kritajxlexport_SOURCES})
target_link_libraries(kritajxlexport kritaui kritalibkra kritaimpex ${JPEGXL_LIBRARIES})
install(TARGETS kritajxlexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_jxl.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
This diff is collapsed.
/*
* SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef JPEG_XL_EXPORT_H_
#define JPEG_XL_EXPORT_H_
#include <KisImportExportFilter.h>
class JPEGXLExport : public KisImportExportFilter
{
Q_OBJECT
public:
JPEGXLExport(QObject *parent, const QVariantList &);
~JPEGXLExport() override = default;
KisImportExportErrorCode
convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP cfg = nullptr) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray &from = "",
const QByteArray &to = "") const override;
KisConfigWidget *
createConfigurationWidget(QWidget *parent, const QByteArray &from = "", const QByteArray &to = "") const override;
void initializeCapabilities() override;
};
#endif
/*
* SPDX-FileCopyrightText: 2021 the JPEG XL Project Authors
* SPDX-License-Identifier: BSD-3-Clause
*
* SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "JPEGXLImport.h"
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <kpluginfactory.h>
#include <QBuffer>
#include <cstring>
#include <KisDocument.h>
#include <KisImportExportErrorCode.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpaceRegistry.h>
#include <kis_assert.h>
#include <kis_debug.h>
#include <kis_group_layer.h>
#include <kis_image_animation_interface.h>
#include <kis_iterator_ng.h>
#include <kis_meta_data_backend_registry.h>
#include <kis_paint_layer.h>
#include <kis_raster_keyframe_channel.h>
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_jxl_import.json", registerPlugin<JPEGXLImport>();)
constexpr static std::array<char, 5> exifTag = {'E', 'x', 'i', 'f', 0x0};
constexpr static std::array<char, 5> xmpTag = {'x', 'm', 'l', ' ', 0x0};
class Q_DECL_HIDDEN JPEGXLImportData
{
public:
JPEGXLImport *m{nullptr};
JxlBasicInfo m_info{};
JxlPixelFormat m_pixelFormat{};
JxlFrameHeader m_header{};
KisPaintDeviceSP m_currentFrame{nullptr};
int m_nextFrameTime{0};
KoID m_colorID;
KoID m_depthID;
bool m_forcedConversion;
};
template<class Traits>
void imageOutRgbCallback(void *that, size_t x, size_t y, size_t numPixels, const void *pixels)
{
auto *data = static_cast<JPEGXLImportData *>(that);
KIS_ASSERT(data);
using Pixel = typename Traits::Pixel;
using channels_type = typename Traits::channels_type;
auto it = data->m_currentFrame->createHLineIteratorNG(static_cast<int>(x),
static_cast<int>(y),
static_cast<int>(data->m_info.xsize));
const auto *src = static_cast<const channels_type *>(pixels);
for (size_t i = 0; i < numPixels; i++) {
auto *dst = reinterpret_cast<Pixel *>(it->rawData());
std::memcpy(dst, src, (data->m_pixelFormat.num_channels) * sizeof(channels_type));
std::swap(dst->blue, dst->red);
src += data->m_pixelFormat.num_channels;
it->nextPixel();
}
}
template<typename channels_type>
void imageOutSizedCallback(void *that, size_t x, size_t y, size_t numPixels, const void *pixels)
{
auto *data = static_cast<JPEGXLImportData *>(that);
KIS_ASSERT(data);
auto it = data->m_currentFrame->createHLineIteratorNG(static_cast<int>(x),
static_cast<int>(y),
static_cast<int>(data->m_info.xsize));
const auto *src = static_cast<const channels_type *>(pixels);
for (size_t i = 0; i < numPixels; i++) {
auto *dst = reinterpret_cast<channels_type *>(it->rawData());
std::memcpy(dst, src, (data->m_pixelFormat.num_channels) * sizeof(channels_type));
src += data->m_pixelFormat.num_channels;
it->nextPixel();
}
}
JPEGXLImport::JPEGXLImport(QObject *parent, const QVariantList &)
: KisImportExportFilter(parent)
{
}
KisImportExportErrorCode
JPEGXLImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
if (!io->isReadable()) {
errFile << "Cannot read image contents";
return ImportExportCodes::NoAccessToRead;
}
JPEGXLImportData d{};
// Multi-threaded parallel runner.
auto runner = JxlResizableParallelRunnerMake(nullptr);
auto dec = JxlDecoderMake(nullptr);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(runner && dec, ImportExportCodes::InternalError);
if (JXL_DEC_SUCCESS
!= JxlDecoderSubscribeEvents(dec.get(),
JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE | JXL_DEC_BOX
| JXL_DEC_FRAME)) {
errFile << "JxlDecoderSubscribeEvents failed";
return ImportExportCodes::InternalError;
}
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) {
errFile << "JxlDecoderSetParallelRunner failed";
return ImportExportCodes::InternalError;
}
const auto data = io->readAll();
const auto validation =
JxlSignatureCheck(reinterpret_cast<const uint8_t *>(data.constData()), static_cast<size_t>(data.size()));
switch (validation) {
case JXL_SIG_NOT_ENOUGH_BYTES:
errFile << "Failed magic byte validation, not enough data";
return ImportExportCodes::FileFormatIncorrect;
case JXL_SIG_INVALID:
errFile << "Failed magic byte validation, incorrect format";
return ImportExportCodes::FileFormatIncorrect;
default:
break;
}
if (JXL_DEC_SUCCESS
!= JxlDecoderSetInput(dec.get(),
reinterpret_cast<const uint8_t *>(data.constData()),
static_cast<size_t>(data.size()))) {
errFile << "JxlDecoderSetInput failed";
return ImportExportCodes::InternalError;
};
JxlDecoderCloseInput(dec.get());
if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) {
errFile << "JxlDecoderSetDecompressBoxes failed";
return ImportExportCodes::InternalError;
};
KisImageSP image{nullptr};
KisLayerSP layer{nullptr};
std::array<char, 5> boxType{};
QByteArray box(16384, 0x0);
auto boxSize = box.size();
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
errFile << "Decoder error";
return ImportExportCodes::InternalError;
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
errFile << "Error, already provided all input";
return ImportExportCodes::InternalError;
} else if (status == JXL_DEC_BASIC_INFO) {
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &d.m_info)) {
errFile << "JxlDecoderGetBasicInfo failed";
return ImportExportCodes::ErrorWhileReading;
}
dbgFile << "Info";
dbgFile << "Size:" << d.m_info.xsize << "x" << d.m_info.ysize;
dbgFile << "Depth:" << d.m_info.bits_per_sample << d.m_info.exponent_bits_per_sample;
dbgFile << "Number of channels:" << d.m_info.num_color_channels;
dbgFile << "Has alpha" << d.m_info.num_extra_channels << d.m_info.alpha_bits
<< d.m_info.alpha_exponent_bits;
dbgFile << "Has animation:" << d.m_info.have_animation << "loops:" << d.m_info.animation.num_loops
<< "tick:" << d.m_info.animation.tps_numerator << d.m_info.animation.tps_denominator;
JxlResizableParallelRunnerSetThreads(
runner.get(),
JxlResizableParallelRunnerSuggestThreads(d.m_info.xsize, d.m_info.ysize));
// XXX: libjxl does not yet provide a way to retrieve the real bit depth.
// See
// https://github.com/libjxl/libjxl/blame/35ca355660a89819e52013fa201284cb50768f80/lib/jxl/decode.cc#L568
if (d.m_info.exponent_bits_per_sample != 0) {
// Let's not rely on float16
d.m_pixelFormat.data_type = JXL_TYPE_FLOAT;
d.m_depthID = Float32BitsColorDepthID;
} else {
if (d.m_info.bits_per_sample == 8) {
d.m_pixelFormat.data_type = JXL_TYPE_UINT8;
d.m_depthID = Integer8BitsColorDepthID;
} else {
d.m_pixelFormat.data_type = JXL_TYPE_UINT16;
d.m_depthID = Integer16BitsColorDepthID;
}
}
if (d.m_info.num_color_channels == 1) {
// Grayscale
d.m_pixelFormat.num_channels = 2;
d.m_colorID = GrayAColorModelID;
} else if (d.m_info.num_color_channels == 3) {
// RGBA
d.m_pixelFormat.num_channels = 4;
d.m_colorID = RGBAColorModelID;
} else if (d.m_info.num_color_channels == 4) {
// CMYKA
d.m_pixelFormat.num_channels = 5;
d.m_colorID = CMYKAColorModelID;
} else {
warnFile << "Forcing a RGBA conversion, unknown color space";
d.m_pixelFormat.num_channels = 4;
d.m_colorID = RGBAColorModelID;
d.m_forcedConversion = true;
}
} else if (status == JXL_DEC_COLOR_ENCODING) {
// Get the ICC color profile of the pixel data
size_t icc_size{};
QByteArray icc_profile;
const KoColorSpace *cs{nullptr};
const auto tgt = d.m_forcedConversion ? JXL_COLOR_PROFILE_TARGET_DATA : JXL_COLOR_PROFILE_TARGET_ORIGINAL;
if (JXL_DEC_SUCCESS == JxlDecoderGetICCProfileSize(dec.get(), &d.m_pixelFormat, tgt, &icc_size)) {
dbgFile << "JxlDecoderGetICCProfileSize succeeded, ICC profile available";
icc_profile.resize(static_cast<int>(icc_size));
if (JXL_DEC_SUCCESS
!= JxlDecoderGetColorAsICCProfile(dec.get(),
&d.m_pixelFormat,
tgt,
reinterpret_cast<uint8_t *>(icc_profile.data()),
static_cast<size_t>(icc_profile.size()))) {
document->setErrorMessage(i18nd("JPEG-XL errors", "Unable to read the image profile."));
return ImportExportCodes::ErrorWhileReading;
}
// With the profile in hand, now we can create the image.
const auto *profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID.id(),
d.m_depthID.id(),
icc_profile);
cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile);
} else {
// XXX: Need to either create the LCMS profile manually
// here or inject it into createColorProfile
document->setErrorMessage(i18nd("JPEG-XL errors", "JPEG-XL encoded profile not implemented"));
return ImportExportCodes::FormatFeaturesUnsupported;
}
image = new KisImage(document->createUndoStore(),
static_cast<int>(d.m_info.xsize),
static_cast<int>(d.m_info.ysize),
cs,
"JPEG-XL image");
layer = new KisPaintLayer(image, image->nextLayerName(), UCHAR_MAX);
if (d.m_info.have_animation) {
dbgFile << "Animation detected, framerate:" << d.m_info.animation.tps_numerator
<< d.m_info.animation.tps_denominator;
const int framerate = static_cast<int>(static_cast<double>(d.m_info.animation.tps_denominator)
/ static_cast<double>(d.m_info.animation.tps_numerator));
layer->enableAnimation();
image->animationInterface()->setFullClipRangeStartTime(0);
image->animationInterface()->setFramerate(framerate);
}
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
d.m = this;
d.m_currentFrame = new KisPaintDevice(image->colorSpace());
const auto callback = [&]() {
if (d.m_colorID == RGBAColorModelID && d.m_depthID == Integer8BitsColorDepthID) {
return &::imageOutRgbCallback<KoBgrU8Traits>;
} else if (d.m_colorID == RGBAColorModelID && d.m_depthID == Integer16BitsColorDepthID) {
return &::imageOutRgbCallback<KoBgrU16Traits>;
} else if (d.m_pixelFormat.data_type == JXL_TYPE_UINT8) {
return &::imageOutSizedCallback<uint8_t>;
} else if (d.m_pixelFormat.data_type == JXL_TYPE_UINT16) {
return &::imageOutSizedCallback<uint16_t>;
} else {
return &::imageOutSizedCallback<float>;
}
}();
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutCallback(dec.get(), &d.m_pixelFormat, callback, &d)) {
errFile << "JxlDecoderSetImageOutBuffer failed";
return ImportExportCodes::InternalError;
}
} else if (status == JXL_DEC_FRAME) {
if (d.m_info.have_animation) {
if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec.get(), &d.m_header)) {
document->setErrorMessage(i18nd("JPEG-XL errors", "JPEG-XL image is animated, but cannot retrieve animation frame header."));
return ImportExportCodes::ErrorWhileReading;
}
}
} else if (status == JXL_DEC_FULL_IMAGE) {
if (d.m_info.have_animation) {
dbgFile << "Importing frame @" << d.m_nextFrameTime;
auto *channel = layer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true);
auto *frame = dynamic_cast<KisRasterKeyframeChannel *>(channel);
frame->importFrame(d.m_nextFrameTime, d.m_currentFrame, nullptr);
d.m_nextFrameTime += static_cast<int>(d.m_header.duration);
image->animationInterface()->setFullClipRangeEndTime(d.m_nextFrameTime);
} else {
layer->paintDevice()->makeCloneFrom(d.m_currentFrame, image->bounds());
}
} else if (status == JXL_DEC_SUCCESS || status == JXL_DEC_BOX) {
if (!boxType.empty()) {
// Release buffer and get its final size.
const auto availOut = JxlDecoderReleaseBoxBuffer(dec.get());
box.resize(box.size() - static_cast<int>(availOut));
QBuffer buf(&box);
if (boxType == exifTag) {
dbgFile << "Loading EXIF data. Size: " << box.size();
const auto *backend = KisMetadataBackendRegistry::instance()->value("exif");
backend->loadFrom(layer->metaData(), &buf);
} else if (boxType == xmpTag) {
dbgFile << "Loading XMP or IPTC data. Size: " << box.size();
const auto *xmpBackend = KisMetadataBackendRegistry::instance()->value("xmp");
const auto *iptcBackend = KisMetadataBackendRegistry::instance()->value("iptc");
if (!xmpBackend->loadFrom(layer->metaData(), &buf)) {
iptcBackend->loadFrom(layer->metaData(), &buf);
}
}
}
if (status == JXL_DEC_SUCCESS) {
// All decoding successfully finished.
// It's not required to call JxlDecoderReleaseInput(dec.get()) here since
// the decoder will be destroyed.
image->addNode(layer, image->rootLayer().data());
document->setCurrentImage(image);
return ImportExportCodes::OK;
} else {
if (JxlDecoderGetBoxType(dec.get(), boxType.data(), JXL_TRUE) != JXL_DEC_SUCCESS) {
errFile << "JxlDecoderGetBoxType failed";
return ImportExportCodes::ErrorWhileReading;
}
if (boxType == exifTag || boxType == xmpTag) {
if (JxlDecoderSetBoxBuffer(dec.get(),
reinterpret_cast<uint8_t *>(boxType.data()),
static_cast<size_t>(box.size()))
!= JXL_DEC_SUCCESS) {
errFile << "JxlDecoderSetBoxBuffer failed";
return ImportExportCodes::InternalError;
}
} else {
dbgFile << "Skipping box" << boxType.data();
}
}
} else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
box.resize(boxSize * 2);
if (JxlDecoderSetBoxBuffer(dec.get(),
reinterpret_cast<uint8_t *>(boxType.data() + boxSize),
static_cast<size_t>(box.size() - boxSize))
!= JXL_DEC_SUCCESS) {
errFile << "JxlDecoderGetBoxType failed";
return ImportExportCodes::ErrorWhileReading;
}
} else {
errFile << "Unknown decoder status" << status;
return ImportExportCodes::InternalError;
}
}