Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kimageformats for openSUSE:Factory checked in at 2021-12-13 20:40:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kimageformats (Old) and /work/SRC/openSUSE:Factory/.kimageformats.new.2520 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kimageformats" Mon Dec 13 20:40:57 2021 rev:101 rq:939219 version:5.89.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kimageformats/kimageformats.changes 2021-11-15 15:26:37.401827245 +0100 +++ /work/SRC/openSUSE:Factory/.kimageformats.new.2520/kimageformats.changes 2021-12-13 20:44:20.476484673 +0100 @@ -1,0 +2,12 @@ +Sat Dec 4 22:56:42 UTC 2021 - Christophe Giboudeaux <christo...@krop.fr> + +- Update to 5.89.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/frameworks/5/5.89.0 +- Changes since 5.88.0: + * avif: limit scope of variables + * Add JXL to the list of supported formats + * Add plugin for JPEG XL (JXL) + +------------------------------------------------------------------- Old: ---- kimageformats-5.88.0.tar.xz kimageformats-5.88.0.tar.xz.sig New: ---- kimageformats-5.89.0.tar.xz kimageformats-5.89.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kimageformats.spec ++++++ --- /var/tmp/diff_new_pack.ZDz8sY/_old 2021-12-13 20:44:21.268484771 +0100 +++ /var/tmp/diff_new_pack.ZDz8sY/_new 2021-12-13 20:44:21.280484772 +0100 @@ -22,7 +22,7 @@ %if 0%{?suse_version} > 1500 || (0%{?is_opensuse} && 0%{?sle_version} >= 150300) %define with_heif 1 %endif -%define _tar_path 5.88 +%define _tar_path 5.89 # Full KF5 version (e.g. 5.33.0) %{!?_kf5_version: %global _kf5_version %{version}} # Last major and minor KF5 version (e.g. 5.33) @@ -30,7 +30,7 @@ # Only needed for the package signature condition %bcond_without lang Name: kimageformats -Version: 5.88.0 +Version: 5.89.0 Release: 0 Summary: Image format plugins for Qt License: LGPL-2.1-or-later ++++++ kimageformats-5.88.0.tar.xz -> kimageformats-5.89.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/CMakeLists.txt new/kimageformats-5.89.0/CMakeLists.txt --- old/kimageformats-5.88.0/CMakeLists.txt 2021-10-07 00:28:35.000000000 +0200 +++ new/kimageformats-5.89.0/CMakeLists.txt 2021-12-04 18:00:59.000000000 +0100 @@ -3,7 +3,7 @@ project(KImageFormats) include(FeatureSummary) -find_package(ECM 5.87.0 NO_MODULE) +find_package(ECM 5.89.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) @@ -17,6 +17,7 @@ include(CheckIncludeFiles) +include(FindPkgConfig) set(REQUIRED_QT_VERSION 5.15.2) find_package(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) @@ -58,11 +59,17 @@ option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF) if(KIMAGEFORMATS_HEIF) - include(FindPkgConfig) pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0) endif() add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/HEIC images") +option(KIMAGEFORMATS_JXL "Enable plugin for JPEG XL format" ON) +if(KIMAGEFORMATS_JXL) + pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.6.1) + pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.6.1) +endif() +add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images") + # 050d00 (5.13) triggers a BIC in qimageiohandler.h, in Qt 5.13, so do not enable that until we can require 5.14 # https://codereview.qt-project.org/c/qt/qtbase/+/279215 add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f02) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/LICENSES/CC0-1.0.txt new/kimageformats-5.89.0/LICENSES/CC0-1.0.txt --- old/kimageformats-5.88.0/LICENSES/CC0-1.0.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.89.0/LICENSES/CC0-1.0.txt 2021-12-04 18:00:59.000000000 +0100 @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/README.md new/kimageformats-5.89.0/README.md --- old/kimageformats-5.88.0/README.md 2021-10-07 00:28:35.000000000 +0200 +++ new/kimageformats-5.89.0/README.md 2021-12-04 18:00:59.000000000 +0100 @@ -23,6 +23,7 @@ - AV1 Image File Format (AVIF) - Encapsulated PostScript (eps) +- JPEG XL (jxl) - Personal Computer Exchange (pcx) - SGI images (rgb, rgba, sgi, bw) - Softimage PIC (pic) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/autotests/CMakeLists.txt new/kimageformats-5.89.0/autotests/CMakeLists.txt --- old/kimageformats-5.88.0/autotests/CMakeLists.txt 2021-10-07 00:28:35.000000000 +0200 +++ new/kimageformats-5.89.0/autotests/CMakeLists.txt 2021-12-04 18:00:59.000000000 +0100 @@ -82,6 +82,12 @@ ) endif() +if (LibJXL_FOUND AND LibJXLThreads_FOUND) + kimageformats_read_tests( + jxl + ) +endif() + # Allow some fuzziness when reading this formats, to allow for # rounding errors (eg: in alpha blending). kimageformats_read_tests(FUZZ 1 Binary files old/kimageformats-5.88.0/autotests/read/jxl/rgb.jxl and new/kimageformats-5.89.0/autotests/read/jxl/rgb.jxl differ Binary files old/kimageformats-5.88.0/autotests/read/jxl/rgb.png and new/kimageformats-5.89.0/autotests/read/jxl/rgb.png differ Binary files old/kimageformats-5.88.0/autotests/read/jxl/rgba.jxl and new/kimageformats-5.89.0/autotests/read/jxl/rgba.jxl differ Binary files old/kimageformats-5.88.0/autotests/read/jxl/rgba.png and new/kimageformats-5.89.0/autotests/read/jxl/rgba.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/src/imageformats/CMakeLists.txt new/kimageformats-5.89.0/src/imageformats/CMakeLists.txt --- old/kimageformats-5.88.0/src/imageformats/CMakeLists.txt 2021-10-07 00:28:35.000000000 +0200 +++ new/kimageformats-5.89.0/src/imageformats/CMakeLists.txt 2021-12-04 18:00:59.000000000 +0100 @@ -83,6 +83,14 @@ ################################## +if (LibJXL_FOUND AND LibJXLThreads_FOUND) + kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp) + target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads) + install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) +endif() + +################################## + kimageformats_add_plugin(kimg_pcx SOURCES pcx.cpp) install(FILES pcx.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/src/imageformats/avif.cpp new/kimageformats-5.89.0/src/imageformats/avif.cpp --- old/kimageformats-5.88.0/src/imageformats/avif.cpp 2021-10-07 00:28:35.000000000 +0200 +++ new/kimageformats-5.89.0/src/imageformats/avif.cpp 2021-12-04 18:00:59.000000000 +0100 @@ -318,27 +318,27 @@ if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) { if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0) && (m_decoder->image->clap.vertOffD > 0)) { - int new_width, new_height, offx, offy; - - new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5); + int new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5); if (new_width > result.width()) { new_width = result.width(); } - new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5); + int new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5); if (new_height > result.height()) { new_height = result.height(); } if (new_width > 0 && new_height > 0) { - offx = ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5; + int offx = + ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5; if (offx < 0) { offx = 0; } else if (offx > (result.width() - new_width)) { offx = result.width() - new_width; } - offy = ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5; + int offy = + ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5; if (offy < 0) { offy = 0; } else if (offy > (result.height() - new_height)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/src/imageformats/jxl.cpp new/kimageformats-5.89.0/src/imageformats/jxl.cpp --- old/kimageformats-5.88.0/src/imageformats/jxl.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.89.0/src/imageformats/jxl.cpp 2021-12-04 18:00:59.000000000 +0100 @@ -0,0 +1,876 @@ +/* + JPEG XL (JXL) support for QImage. + + SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovome...@gmail.com> + + SPDX-License-Identifier: BSD-2-Clause +*/ + +#include <QThread> +#include <QtGlobal> + +#include "jxl_p.h" +#include <jxl/encode.h> +#include <jxl/thread_parallel_runner.h> + +QJpegXLHandler::QJpegXLHandler() + : m_parseState(ParseJpegXLNotParsed) + , m_quality(90) + , m_currentimage_index(0) + , m_previousimage_index(-1) + , 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"); + 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((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) { + 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) { + 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((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; + } + + 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, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) { + qWarning("ERROR: JxlDecoderSetInput failed"); + m_parseState = ParseJpegXLError; + return false; + } + + JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME); + if (status == JXL_DEC_ERROR) { + qWarning("ERROR: JxlDecoderSubscribeEvents failed"); + m_parseState = ParseJpegXLError; + return false; + } + + status = JxlDecoderProcessInput(m_decoder); + 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; + } + + 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 > 32768 || m_basicinfo.ysize > 32768) { + qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize); + m_parseState = ParseJpegXLError; + return false; + } else if (sizeof(void *) <= 4) { + /* On 32bit systems, there is limited address space. + * We skip imagess bigger than 8192 x 8192 pixels. + * If we don't do it, abort() in libjxl may close whole application */ + if ((m_basicinfo.xsize * m_basicinfo.ysize) > 67108864) { + qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", 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 = 4; + + if (m_basicinfo.bits_per_sample > 8) { // high bit depth + m_input_pixel_format.data_type = JXL_TYPE_UINT16; + m_buffer_size = 8 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize; + m_input_image_format = QImage::Format_RGBA64; + + if (loadalpha) { + m_target_image_format = QImage::Format_RGBA64; + } else { + m_target_image_format = QImage::Format_RGBX64; + } + } else { // 8bit depth + m_input_pixel_format.data_type = JXL_TYPE_UINT8; + m_buffer_size = 4 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize; + 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, &m_input_pixel_format, 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, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) { + if (icc_size > 0) { + QByteArray icc_data((int)icc_size, 0); + if (JxlDecoderGetColorAsICCProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, (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; + } + + 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 = QImage(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 (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; + } + } + } + + 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; + } +} + +bool QJpegXLHandler::write(const QImage &image) +{ + if (image.format() == QImage::Format_Invalid) { + qWarning("No image data to save"); + return false; + } + + if ((image.width() > 32768) || (image.height() > 32768)) { + qWarning("Image is too large"); + return false; + } + + JxlEncoder *encoder = JxlEncoderCreate(nullptr); + if (!encoder) { + qWarning("Failed to create Jxl encoder"); + return false; + } + + 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; + } + } + + JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr); + + if (m_quality > 100) { + m_quality = 100; + } else if (m_quality < 0) { + m_quality = 90; + } + + JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f); + + JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE); + + JxlBasicInfo output_info; + JxlEncoderInitBasicInfo(&output_info); + + JxlColorEncoding color_profile; + JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE); + + bool convert_color_profile; + QByteArray iccprofile; + + if (image.colorSpace().isValid()) { + if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) { + convert_color_profile = true; + } else { + convert_color_profile = false; + } + } else { // no profile or Qt-unsupported ICC profile + convert_color_profile = false; + iccprofile = image.colorSpace().iccProfile(); + if (iccprofile.size() > 0) { + output_info.uses_original_profile = 1; + } + } + + JxlPixelFormat pixel_format; + QImage::Format tmpformat; + JxlEncoderStatus status; + + pixel_format.data_type = JXL_TYPE_UINT16; + pixel_format.endianness = JXL_NATIVE_ENDIAN; + pixel_format.align = 0; + + if (image.hasAlphaChannel()) { + tmpformat = QImage::Format_RGBA64; + pixel_format.num_channels = 4; + output_info.alpha_bits = 16; + output_info.num_extra_channels = 1; + } else { + tmpformat = QImage::Format_RGBX64; + pixel_format.num_channels = 3; + output_info.alpha_bits = 0; + } + + const QImage tmpimage = + convert_color_profile ? image.convertToFormat(tmpformat).convertedToColorSpace(QColorSpace(QColorSpace::SRgb)) : image.convertToFormat(tmpformat); + + const size_t xsize = tmpimage.width(); + const size_t ysize = tmpimage.height(); + const size_t buffer_size = 2 * pixel_format.num_channels * xsize * ysize; + + 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(); + output_info.bits_per_sample = 16; + output_info.intensity_target = 255.0f; + output_info.orientation = JXL_ORIENT_IDENTITY; + output_info.num_color_channels = 3; + output_info.animation.tps_numerator = 10; + output_info.animation.tps_denominator = 1; + + status = JxlEncoderSetBasicInfo(encoder, &output_info); + if (status != JXL_ENC_SUCCESS) { + qWarning("JxlEncoderSetBasicInfo failed!"); + if (runner) { + JxlThreadParallelRunnerDestroy(runner); + } + JxlEncoderDestroy(encoder); + return false; + } + + if (!convert_color_profile && iccprofile.size() > 0) { + status = JxlEncoderSetICCProfile(encoder, (const uint8_t *)iccprofile.constData(), iccprofile.size()); + if (status != JXL_ENC_SUCCESS) { + qWarning("JxlEncoderSetICCProfile failed!"); + if (runner) { + JxlThreadParallelRunnerDestroy(runner); + } + JxlEncoderDestroy(encoder); + return false; + } + } else { + status = JxlEncoderSetColorEncoding(encoder, &color_profile); + if (status != JXL_ENC_SUCCESS) { + qWarning("JxlEncoderSetColorEncoding failed!"); + if (runner) { + JxlThreadParallelRunnerDestroy(runner); + } + JxlEncoderDestroy(encoder); + return false; + } + } + + if (image.hasAlphaChannel()) { + status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size); + } else { + uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize]; + if (!tmp_buffer) { + qWarning("Memory allocation error"); + if (runner) { + JxlThreadParallelRunnerDestroy(runner); + } + JxlEncoderDestroy(encoder); + return false; + } + + uint16_t *dest_pixels = tmp_buffer; + for (int y = 0; y < tmpimage.height(); y++) { + const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y)); + for (int x = 0; x < tmpimage.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 + } + } + status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size); + delete[] tmp_buffer; + } + + 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((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 (option == Quality) { + return m_quality; + } + + if (!supportsOption(option) || !ensureParsed()) { + 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; + } + default: + 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; + default: + break; + } + QImageIOHandler::setOption(option, value); +} + +bool QJpegXLHandler::supportsOption(ImageOption option) const +{ + return option == Quality || option == Size || option == Animation; +} + +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); + } + } + + 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) { + return true; + } + + if (imageNumber > m_currentimage_index) { + JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index); + m_currentimage_index = imageNumber; + return true; + } + + if (!rewind()) { + return false; + } + + if (imageNumber > 0) { + JxlDecoderSkipFrames(m_decoder, imageNumber); + } + m_currentimage_index = imageNumber; + 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 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, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) { + qWarning("ERROR: JxlDecoderSetInput failed"); + m_parseState = ParseJpegXLError; + return false; + } + + 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; +} + +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; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/src/imageformats/jxl.desktop new/kimageformats-5.89.0/src/imageformats/jxl.desktop --- old/kimageformats-5.88.0/src/imageformats/jxl.desktop 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.89.0/src/imageformats/jxl.desktop 2021-12-04 18:00:59.000000000 +0100 @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=QImageIOPlugins +X-KDE-ImageFormat=jxl +X-KDE-MimeType=image/jxl +X-KDE-Read=true +X-KDE-Write=true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/src/imageformats/jxl.json new/kimageformats-5.89.0/src/imageformats/jxl.json --- old/kimageformats-5.88.0/src/imageformats/jxl.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.89.0/src/imageformats/jxl.json 2021-12-04 18:00:59.000000000 +0100 @@ -0,0 +1,4 @@ +{ + "Keys": [ "jxl" ], + "MimeTypes": [ "image/jxl" ] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.88.0/src/imageformats/jxl_p.h new/kimageformats-5.89.0/src/imageformats/jxl_p.h --- old/kimageformats-5.88.0/src/imageformats/jxl_p.h 1970-01-01 01:00:00.000000000 +0100 +++ new/kimageformats-5.89.0/src/imageformats/jxl_p.h 2021-12-04 18:00:59.000000000 +0100 @@ -0,0 +1,96 @@ +/* + JPEG XL (JXL) support for QImage. + + SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovome...@gmail.com> + + SPDX-License-Identifier: BSD-2-Clause +*/ + +#ifndef KIMG_JXL_P_H +#define KIMG_JXL_P_H + +#include <QByteArray> +#include <QColorSpace> +#include <QImage> +#include <QImageIOHandler> +#include <QImageIOPlugin> +#include <QVariant> +#include <QVector> + +#include <jxl/decode.h> + +class QJpegXLHandler : public QImageIOHandler +{ +public: + QJpegXLHandler(); + ~QJpegXLHandler(); + + bool canRead() const override; + bool read(QImage *image) override; + bool write(const QImage &image) override; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const override; + void setOption(ImageOption option, const QVariant &value) override; + bool supportsOption(ImageOption option) const override; + + int imageCount() const override; + int currentImageNumber() const override; + bool jumpToNextImage() override; + bool jumpToImage(int imageNumber) override; + + int nextImageDelay() const override; + + int loopCount() const override; + +private: + bool ensureParsed() const; + bool ensureALLCounted() const; + bool ensureDecoder(); + bool countALLFrames(); + bool decode_one_frame(); + bool rewind(); + + enum ParseJpegXLState { + ParseJpegXLError = -1, + ParseJpegXLNotParsed = 0, + ParseJpegXLSuccess = 1, + ParseJpegXLBasicInfoParsed = 2, + }; + + ParseJpegXLState m_parseState; + int m_quality; + int m_currentimage_index; + int m_previousimage_index; + + QByteArray m_rawData; + + JxlDecoder *m_decoder; + void *m_runner; + JxlBasicInfo m_basicinfo; + + QVector<int> m_framedelays; + int m_next_image_delay; + + QImage m_current_image; + QColorSpace m_colorspace; + + QImage::Format m_input_image_format; + QImage::Format m_target_image_format; + + JxlPixelFormat m_input_pixel_format; + size_t m_buffer_size; +}; + +class QJpegXLPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "jxl.json") + +public: + Capabilities capabilities(QIODevice *device, const QByteArray &format) const override; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; +}; + +#endif // KIMG_JXL_P_H