Binary files 5.94.0-3/autotests/read/psd/cmyka-16bits.png and 5.96.0-1/autotests/read/psd/cmyka-16bits.png differ
Binary files 5.94.0-3/autotests/read/psd/cmyka-16bits.psd and 5.96.0-1/autotests/read/psd/cmyka-16bits.psd differ
Binary files 5.94.0-3/autotests/read/psd/cmyka-8bits.png and 5.96.0-1/autotests/read/psd/cmyka-8bits.png differ
Binary files 5.94.0-3/autotests/read/psd/cmyka-8bits.psd and 5.96.0-1/autotests/read/psd/cmyka-8bits.psd differ
diff -pruN 5.94.0-3/CMakeLists.txt 5.96.0-1/CMakeLists.txt
--- 5.94.0-3/CMakeLists.txt	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/CMakeLists.txt	2022-07-02 14:33:58.000000000 +0000
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
 project(KImageFormats)
 
 include(FeatureSummary)
-find_package(ECM 5.93.0  NO_MODULE)
+find_package(ECM 5.96.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)
 
@@ -13,9 +13,9 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}
 include(KDEInstallDirs)
 include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
 include(KDECMakeSettings)
-include(KDEGitCommitHooks)
-
 
+include(KDEGitCommitHooks)
+include(ECMDeprecationSettings)
 include(CheckIncludeFiles)
 include(FindPkgConfig)
 
@@ -70,8 +70,11 @@ if(KIMAGEFORMATS_JXL)
 endif()
 add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
 
-add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f02)
-add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x055900)
+ecm_set_disabled_deprecation_versions(
+    QT 5.15.2
+    KF 5.95
+)
+
 add_subdirectory(src)
 if (BUILD_TESTING)
     add_subdirectory(autotests)
diff -pruN 5.94.0-3/debian/changelog 5.96.0-1/debian/changelog
--- 5.94.0-3/debian/changelog	2022-06-01 05:53:34.000000000 +0000
+++ 5.96.0-1/debian/changelog	2022-07-31 11:33:02.000000000 +0000
@@ -1,3 +1,12 @@
+kimageformats (5.96.0-1) unstable; urgency=medium
+
+  [ Aurélien COUDERC ]
+  * New upstream release (5.96.0).
+  * Drop patch upstream_psd-Fix-segfault-on-architectures-where-char-is-
+    unsi.patch applied upstream.
+
+ -- Aurélien COUDERC <coucouf@debian.org>  Sun, 31 Jul 2022 13:33:02 +0200
+
 kimageformats (5.94.0-3) unstable; urgency=medium
 
   * Team upload.
diff -pruN 5.94.0-3/debian/control 5.96.0-1/debian/control
--- 5.94.0-3/debian/control	2022-06-01 05:43:30.000000000 +0000
+++ 5.96.0-1/debian/control	2022-07-31 11:33:02.000000000 +0000
@@ -2,11 +2,11 @@ Source: kimageformats
 Section: kde
 Priority: optional
 Maintainer: Debian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
-Uploaders: Aurélien COUDERC <coucouf@debian.org>,
+Uploaders: Aurélien COUDERC <coucouf@debian.org>
 Build-Depends: cmake (>= 3.16~),
                debhelper-compat (= 13),
                dh-sequence-kf5,
-               extra-cmake-modules (>= 5.93.0~),
+               extra-cmake-modules (>= 5.96.0~),
                libavif-dev (>= 0.8.2~),
                libheif-dev (>= 1.10.0~),
                libkf5archive-dev,
diff -pruN 5.94.0-3/debian/patches/series 5.96.0-1/debian/patches/series
--- 5.94.0-3/debian/patches/series	2022-06-01 05:40:44.000000000 +0000
+++ 5.96.0-1/debian/patches/series	1970-01-01 00:00:00.000000000 +0000
@@ -1 +0,0 @@
-upstream_psd-Fix-segfault-on-architectures-where-char-is-unsi.patch
diff -pruN 5.94.0-3/debian/patches/upstream_psd-Fix-segfault-on-architectures-where-char-is-unsi.patch 5.96.0-1/debian/patches/upstream_psd-Fix-segfault-on-architectures-where-char-is-unsi.patch
--- 5.94.0-3/debian/patches/upstream_psd-Fix-segfault-on-architectures-where-char-is-unsi.patch	2022-06-01 05:40:03.000000000 +0000
+++ 5.96.0-1/debian/patches/upstream_psd-Fix-segfault-on-architectures-where-char-is-unsi.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,32 +0,0 @@
-From 2a84dd677db53729be5862503ac56c5139086bb3 Mon Sep 17 00:00:00 2001
-From: Adrian Bunk <bunk@debian.org>
-Date: Fri, 27 May 2022 12:26:56 +0300
-Subject: [PATCH] psd: Fix segfault on architectures where char is unsigned
- (like ARM)
-
----
- src/imageformats/psd.cpp | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp
-index 49f2abd..e70c572 100644
---- a/src/imageformats/psd.cpp
-+++ b/src/imageformats/psd.cpp
-@@ -490,11 +490,11 @@ qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen)
- {
-     qint64  j = 0;
-     for (qint64 ip = 0, rr = 0, available = olen; j < olen && ip < ilen; available = olen - j) {
--        char n = input[ip++];
--        if (static_cast<signed char>(n) == -128)
-+        signed char n = static_cast<signed char>(input[ip++]);
-+        if (n == -128)
-             continue;
- 
--        if (static_cast<signed char>(n) >= 0) {
-+        if (n >= 0) {
-             rr = qint64(n) + 1;
-             if (available < rr) {
-                 ip--;
--- 
-2.35.1
-
diff -pruN 5.94.0-3/.kde-ci.yml 5.96.0-1/.kde-ci.yml
--- 5.94.0-3/.kde-ci.yml	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/.kde-ci.yml	2022-07-02 14:33:58.000000000 +0000
@@ -6,3 +6,4 @@ Dependencies:
 
 Options:
   test-before-installing: True
+  require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
diff -pruN 5.94.0-3/src/imageformats/avif.cpp 5.96.0-1/src/imageformats/avif.cpp
--- 5.94.0-3/src/imageformats/avif.cpp	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/src/imageformats/avif.cpp	2022-07-02 14:33:58.000000000 +0000
@@ -67,7 +67,7 @@ bool QAVIFHandler::canRead(QIODevice *de
 
 bool QAVIFHandler::ensureParsed() const
 {
-    if (m_parseState == ParseAvifSuccess) {
+    if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata) {
         return true;
     }
     if (m_parseState == ParseAvifError) {
@@ -79,6 +79,28 @@ bool QAVIFHandler::ensureParsed() const
     return that->ensureDecoder();
 }
 
+bool QAVIFHandler::ensureOpened() const
+{
+    if (m_parseState == ParseAvifSuccess) {
+        return true;
+    }
+    if (m_parseState == ParseAvifError) {
+        return false;
+    }
+
+    QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
+    if (ensureParsed()) {
+        if (m_parseState == ParseAvifMetadata) {
+            bool success = that->jumpToNextImage();
+            that->m_parseState = success ? ParseAvifSuccess : ParseAvifError;
+            return success;
+        }
+    }
+
+    that->m_parseState = ParseAvifError;
+    return false;
+}
+
 bool QAVIFHandler::ensureDecoder()
 {
     if (m_decoder) {
@@ -97,6 +119,9 @@ bool QAVIFHandler::ensureDecoder()
 
     m_decoder = avifDecoderCreate();
 
+    m_decoder->ignoreExif = AVIF_TRUE;
+    m_decoder->ignoreXMP = AVIF_TRUE;
+
 #if AVIF_VERSION >= 80400
     m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
 #endif
@@ -127,45 +152,58 @@ bool QAVIFHandler::ensureDecoder()
         return false;
     }
 
-    decodeResult = avifDecoderNextImage(m_decoder);
+    m_container_width = m_decoder->image->width;
+    m_container_height = m_decoder->image->height;
 
-    if (decodeResult == AVIF_RESULT_OK) {
-        m_container_width = m_decoder->image->width;
-        m_container_height = m_decoder->image->height;
-
-        if ((m_container_width > 65535) || (m_container_height > 65535)) {
-            qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
-            m_parseState = ParseAvifError;
-            return false;
-        }
+    if ((m_container_width > 65535) || (m_container_height > 65535)) {
+        qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
+        m_parseState = ParseAvifError;
+        return false;
+    }
 
-        if ((m_container_width == 0) || (m_container_height == 0)) {
-            qWarning("Empty image, nothing to decode");
-            m_parseState = ParseAvifError;
-            return false;
-        }
+    if ((m_container_width == 0) || (m_container_height == 0)) {
+        qWarning("Empty image, nothing to decode");
+        m_parseState = ParseAvifError;
+        return false;
+    }
 
-        if (m_container_width > ((16384 * 16384) / m_container_height)) {
-            qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
-            m_parseState = ParseAvifError;
-            return false;
+    if (m_container_width > ((16384 * 16384) / m_container_height)) {
+        qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
+        m_parseState = ParseAvifError;
+        return false;
+    }
+
+    // calculate final dimensions with crop and rotate operations applied
+    int new_width = m_container_width;
+    int new_height = m_container_height;
+
+    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 crop_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
+            if (crop_width < new_width && crop_width > 0) {
+                new_width = crop_width;
+            }
+            int crop_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
+            if (crop_height < new_height && crop_height > 0) {
+                new_height = crop_height;
+            }
         }
+    }
 
-        m_parseState = ParseAvifSuccess;
-        if (decode_one_frame()) {
-            return true;
-        } else {
-            m_parseState = ParseAvifError;
-            return false;
+    if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
+        if (m_decoder->image->irot.angle == 1 || m_decoder->image->irot.angle == 3) {
+            int tmp = new_width;
+            new_width = new_height;
+            new_height = tmp;
         }
-    } else {
-        qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
     }
 
-    avifDecoderDestroy(m_decoder);
-    m_decoder = nullptr;
-    m_parseState = ParseAvifError;
-    return false;
+    m_estimated_dimensions.setWidth(new_width);
+    m_estimated_dimensions.setHeight(new_height);
+
+    m_parseState = ParseAvifMetadata;
+    return true;
 }
 
 bool QAVIFHandler::decode_one_frame()
@@ -192,9 +230,9 @@ bool QAVIFHandler::decode_one_frame()
         }
     } else {
         if (loadalpha) {
-            resultformat = QImage::Format_RGBA8888;
+            resultformat = QImage::Format_ARGB32;
         } else {
-            resultformat = QImage::Format_RGBX8888;
+            resultformat = QImage::Format_RGB32;
         }
     }
     QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
@@ -285,14 +323,16 @@ bool QAVIFHandler::decode_one_frame()
         rgb.depth = 16;
         rgb.format = AVIF_RGB_FORMAT_RGBA;
 
-        if (!loadalpha) {
-            if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
-                resultformat = QImage::Format_Grayscale16;
-            }
+        if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
+            resultformat = QImage::Format_Grayscale16;
         }
     } else {
         rgb.depth = 8;
-        rgb.format = AVIF_RGB_FORMAT_RGBA;
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+        rgb.format = AVIF_RGB_FORMAT_BGRA;
+#else
+        rgb.format = AVIF_RGB_FORMAT_ARGB;
+#endif
 
 #if AVIF_VERSION >= 80400
         if (m_decoder->imageCount > 1) {
@@ -301,14 +341,8 @@ bool QAVIFHandler::decode_one_frame()
         }
 #endif
 
-        if (loadalpha) {
-            resultformat = QImage::Format_ARGB32;
-        } else {
-            if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
-                resultformat = QImage::Format_Grayscale8;
-            } else {
-                resultformat = QImage::Format_RGB32;
-            }
+        if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
+            resultformat = QImage::Format_Grayscale8;
         }
     }
 
@@ -399,13 +433,15 @@ bool QAVIFHandler::decode_one_frame()
         m_current_image = result.convertToFormat(resultformat);
     }
 
+    m_estimated_dimensions = m_current_image.size();
+
     m_must_jump_to_next_image = false;
     return true;
 }
 
 bool QAVIFHandler::read(QImage *image)
 {
-    if (!ensureParsed()) {
+    if (!ensureOpened()) {
         return false;
     }
 
@@ -792,7 +828,7 @@ QVariant QAVIFHandler::option(ImageOptio
 
     switch (option) {
     case Size:
-        return m_current_image.size();
+        return m_estimated_dimensions;
     case Animation:
         if (imageCount() >= 2) {
             return true;
@@ -848,6 +884,14 @@ int QAVIFHandler::currentImageNumber() c
         return 0;
     }
 
+    if (m_parseState == ParseAvifMetadata) {
+        if (m_decoder->imageCount >= 2) {
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
     return m_decoder->imageIndex;
 }
 
@@ -857,12 +901,14 @@ bool QAVIFHandler::jumpToNextImage()
         return false;
     }
 
-    if (m_decoder->imageCount < 2) {
-        return true;
-    }
+    if (m_decoder->imageIndex >= 0) {
+        if (m_decoder->imageCount < 2) {
+            return true;
+        }
 
-    if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
-        avifDecoderReset(m_decoder);
+        if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
+            avifDecoderReset(m_decoder);
+        }
     }
 
     avifResult decodeResult = avifDecoderNextImage(m_decoder);
@@ -885,6 +931,7 @@ bool QAVIFHandler::jumpToNextImage()
     }
 
     if (decode_one_frame()) {
+        m_parseState = ParseAvifSuccess;
         return true;
     } else {
         m_parseState = ParseAvifError;
@@ -900,7 +947,7 @@ bool QAVIFHandler::jumpToImage(int image
 
     if (m_decoder->imageCount < 2) { // not an animation
         if (imageNumber == 0) {
-            return true;
+            return ensureOpened();
         } else {
             return false;
         }
@@ -935,6 +982,7 @@ bool QAVIFHandler::jumpToImage(int image
     }
 
     if (decode_one_frame()) {
+        m_parseState = ParseAvifSuccess;
         return true;
     } else {
         m_parseState = ParseAvifError;
@@ -944,7 +992,7 @@ bool QAVIFHandler::jumpToImage(int image
 
 int QAVIFHandler::nextImageDelay() const
 {
-    if (!ensureParsed()) {
+    if (!ensureOpened()) {
         return 0;
     }
 
diff -pruN 5.94.0-3/src/imageformats/avif_p.h 5.96.0-1/src/imageformats/avif_p.h
--- 5.94.0-3/src/imageformats/avif_p.h	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/src/imageformats/avif_p.h	2022-07-02 14:33:58.000000000 +0000
@@ -13,6 +13,7 @@
 #include <QImage>
 #include <QImageIOPlugin>
 #include <QPointF>
+#include <QSize>
 #include <QVariant>
 #include <avif/avif.h>
 #include <qimageiohandler.h>
@@ -45,6 +46,7 @@ public:
 private:
     static QPointF CompatibleChromacity(qreal chrX, qreal chrY);
     bool ensureParsed() const;
+    bool ensureOpened() const;
     bool ensureDecoder();
     bool decode_one_frame();
 
@@ -52,6 +54,7 @@ private:
         ParseAvifError = -1,
         ParseAvifNotParsed = 0,
         ParseAvifSuccess = 1,
+        ParseAvifMetadata = 2,
     };
 
     ParseAvifState m_parseState;
@@ -59,6 +62,7 @@ private:
 
     uint32_t m_container_width;
     uint32_t m_container_height;
+    QSize m_estimated_dimensions;
 
     QByteArray m_rawData;
     avifROData m_rawAvifData;
diff -pruN 5.94.0-3/src/imageformats/CMakeLists.txt 5.96.0-1/src/imageformats/CMakeLists.txt
--- 5.94.0-3/src/imageformats/CMakeLists.txt	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/src/imageformats/CMakeLists.txt	2022-07-02 14:33:58.000000000 +0000
@@ -4,6 +4,7 @@
 
 function(kimageformats_add_plugin plugin)
     set(options)
+    set(oneValueArgs)
     set(multiValueArgs SOURCES)
     cmake_parse_arguments(KIF_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
     if(NOT KIF_ADD_PLUGIN_SOURCES)
@@ -86,6 +87,9 @@ endif()
 if (LibJXL_FOUND AND LibJXLThreads_FOUND)
     kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
     target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
+    if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
+        target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
+    endif()
     install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
 endif()
 
diff -pruN 5.94.0-3/src/imageformats/jxl.cpp 5.96.0-1/src/imageformats/jxl.cpp
--- 5.94.0-3/src/imageformats/jxl.cpp	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/src/imageformats/jxl.cpp	2022-07-02 14:33:58.000000000 +0000
@@ -143,6 +143,10 @@ bool QJpegXLHandler::ensureDecoder()
         return false;
     }
 
+#ifdef KIMG_JXL_API_VERSION
+    JxlDecoderCloseInput(m_decoder);
+#endif
+
     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");
@@ -482,37 +486,15 @@ bool QJpegXLHandler::write(const QImage
         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;
 
@@ -526,7 +508,28 @@ bool QJpegXLHandler::write(const QImage
         convert_color_profile = false;
         iccprofile = image.colorSpace().iccProfile();
         if (iccprofile.size() > 0 || m_quality == 100) {
-            output_info.uses_original_profile = 1;
+            output_info.uses_original_profile = JXL_TRUE;
+        }
+    }
+
+    if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
+        output_info.have_container = JXL_TRUE;
+        JxlEncoderUseContainer(encoder, JXL_TRUE);
+#ifdef KIMG_JXL_API_VERSION
+        JxlEncoderSetCodestreamLevel(encoder, 10);
+#endif
+    }
+
+    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;
         }
     }
 
@@ -537,7 +540,6 @@ bool QJpegXLHandler::write(const QImage
     pixel_format.endianness = JXL_NATIVE_ENDIAN;
     pixel_format.align = 0;
 
-    output_info.intensity_target = 255.0f;
     output_info.orientation = JXL_ORIENT_IDENTITY;
     output_info.num_color_channels = 3;
     output_info.animation.tps_numerator = 10;
@@ -615,6 +617,9 @@ bool QJpegXLHandler::write(const QImage
             return false;
         }
     } else {
+        JxlColorEncoding color_profile;
+        JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
+
         status = JxlEncoderSetColorEncoding(encoder, &color_profile);
         if (status != JXL_ENC_SUCCESS) {
             qWarning("JxlEncoderSetColorEncoding failed!");
@@ -626,6 +631,20 @@ bool QJpegXLHandler::write(const QImage
         }
     }
 
+#ifdef KIMG_JXL_API_VERSION
+    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);
+#else
+    JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
+
+    JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
+
+    JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
+#endif
+
     if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
         status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
     } else {
@@ -915,6 +934,10 @@ bool QJpegXLHandler::rewind()
         return false;
     }
 
+#ifdef KIMG_JXL_API_VERSION
+    JxlDecoderCloseInput(m_decoder);
+#endif
+
     if (m_basicinfo.uses_original_profile) {
         if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
             qWarning("ERROR: JxlDecoderSubscribeEvents failed");
diff -pruN 5.94.0-3/src/imageformats/psd.cpp 5.96.0-1/src/imageformats/psd.cpp
--- 5.94.0-3/src/imageformats/psd.cpp	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/src/imageformats/psd.cpp	2022-07-02 14:33:58.000000000 +0000
@@ -101,6 +101,10 @@ struct PSDColorModeDataSection {
 
 using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
 
+struct PSDLayerAndMaskSection {
+    qint16 layerCount = 0;
+};
+
 /*!
  * \brief fixedPointToDouble
  * Converts a fixed point number to floating point one.
@@ -112,6 +116,28 @@ static double fixedPointToDouble(qint32
     return (i+d);
 }
 
+static bool skip_section(QDataStream &s, bool psb = false)
+{
+    qint64 section_length;
+    if (!psb) {
+        quint32 tmp;
+        s >> tmp;
+        section_length = tmp;
+    }
+    else {
+        s >> section_length;
+    }
+
+    // Skip mode data.
+    for (qint32 i32 = 0; section_length; section_length -= i32) {
+        i32 = std::min(section_length, qint64(std::numeric_limits<qint32>::max()));
+        i32 = s.skipRawData(i32);
+        if (i32 < 1)
+            return false;
+    }
+    return true;
+}
+
 /*!
  * \brief readPascalString
  * Reads the Pascal string as defined in the PSD specification.
@@ -218,12 +244,12 @@ static PSDImageResourceSection readImage
         size -= sizeof(dataSize);
         // NOTE: Qt device::read() and QDataStream::readRawData() could read less data than specified.
         //       The read code should be improved.
-        if(auto dev = s.device())
+        if (auto dev = s.device())
             irb.data = dev->read(dataSize);
         auto read = irb.data.size();
         if (read > 0)
             size -= read;
-        if (read != dataSize) {
+        if (quint32(read) != dataSize) {
             qDebug() << "Image Resource Block Read Error!";
             *ok = false;
             break;
@@ -250,6 +276,46 @@ static PSDImageResourceSection readImage
     return irs;
 }
 
+
+PSDLayerAndMaskSection readLayerAndMaskSection(QDataStream &s, bool isPsb, bool *ok = nullptr)
+{
+    PSDLayerAndMaskSection lms;
+
+    bool tmp = true;
+    if (ok == nullptr)
+        ok = &tmp;
+    *ok = true;
+
+    // try to read layerCount: if less than zero, means that there is an alpha channel
+    if (auto device = s.device()) {
+        device->startTransaction();
+        qint64 size = 0;
+        if (isPsb) {
+            qint64 tmpSize;
+            s >> tmpSize; // global size
+            if (tmpSize >= 8)
+                s >> size; // layer info size
+        }
+        else {
+            quint32 tmpSize;
+            s >> tmpSize; // global size
+            if (tmpSize >= 4) {
+                s >> tmpSize; // layer info size
+                size = tmpSize;
+            }
+        }
+
+        if (s.status() == QDataStream::Ok) {
+            if (size >= 2)
+                s >> lms.layerCount;
+        }
+        device->rollbackTransaction();
+    }
+
+    *ok = skip_section(s, isPsb);
+    return lms;
+}
+
 /*!
  * \brief readColorModeDataSection
  * Read the color mode section
@@ -424,55 +490,65 @@ static QDataStream &operator>>(QDataStre
     return s;
 }
 
-// Check that the header is a valid PSD.
+// Check that the header is a valid PSD (as written in the PSD specification).
 static bool IsValid(const PSDHeader &header)
 {
     if (header.signature != 0x38425053) { // '8BPS'
+        //qDebug() << "PSD header: invalid signature" << header.signature;
         return false;
     }
-    return true;
-}
-
-// Check that the header is supported.
-static bool IsSupported(const PSDHeader &header)
-{
     if (header.version != 1 && header.version != 2) {
+        qDebug() << "PSD header: invalid version" << header.version;
         return false;
     }
     if (header.depth != 8 &&
         header.depth != 16 &&
         header.depth != 32 &&
         header.depth != 1) {
+        qDebug() << "PSD header: invalid depth" << header.depth;
         return false;
     }
     if (header.color_mode != CM_RGB &&
         header.color_mode != CM_GRAYSCALE &&
         header.color_mode != CM_INDEXED &&
         header.color_mode != CM_DUOTONE &&
+        header.color_mode != CM_CMYK &&
+        header.color_mode != CM_LABCOLOR &&
+        header.color_mode != CM_MULTICHANNEL &&
         header.color_mode != CM_BITMAP) {
+        qDebug() << "PSD header: invalid color mode" << header.color_mode;
+        return false;
+    }
+    if (header.channel_count < 1 || header.channel_count > 56) {
+        qDebug() << "PSD header: invalid number of channels" << header.channel_count;
+        return false;
+    }
+    if (header.width > 300000 || header.height > 300000) {
+        qDebug() << "PSD header: invalid image size" << header.width << "x" << header.height;
         return false;
     }
     return true;
 }
 
-static bool skip_section(QDataStream &s, bool psb = false)
+// Check that the header is supported by this plugin.
+static bool IsSupported(const PSDHeader &header)
 {
-    qint64 section_length;
-    if (!psb) {
-        quint32 tmp;
-        s >> tmp;
-        section_length = tmp;
+    if (header.version != 1 && header.version != 2) {
+        return false;
     }
-    else {
-        s >> section_length;
+    if (header.depth != 8 &&
+        header.depth != 16 &&
+        header.depth != 32 &&
+        header.depth != 1) {
+        return false;
     }
-
-    // Skip mode data.
-    for (qint32 i32 = 0; section_length; section_length -= i32) {
-        i32 = std::min(section_length, qint64(std::numeric_limits<qint32>::max()));
-        i32 = s.skipRawData(i32);
-        if (i32 < 1)
-            return false;
+    if (header.color_mode != CM_RGB &&
+        header.color_mode != CM_GRAYSCALE &&
+        header.color_mode != CM_INDEXED &&
+        header.color_mode != CM_DUOTONE &&
+        header.color_mode != CM_CMYK &&
+        header.color_mode != CM_BITMAP) {
+        return false;
     }
     return true;
 }
@@ -490,11 +566,11 @@ qint64 decompress(const char *input, qin
 {
     qint64  j = 0;
     for (qint64 ip = 0, rr = 0, available = olen; j < olen && ip < ilen; available = olen - j) {
-        char n = input[ip++];
-        if (static_cast<signed char>(n) == -128)
+        signed char n = static_cast<signed char>(input[ip++]);
+        if (n == -128)
             continue;
 
-        if (static_cast<signed char>(n) >= 0) {
+        if (n >= 0) {
             rr = qint64(n) + 1;
             if (available < rr) {
                 ip--;
@@ -525,7 +601,7 @@ qint64 decompress(const char *input, qin
  * \param header The PSD header.
  * \return The Qt image format.
  */
-static QImage::Format imageFormat(const PSDHeader &header)
+static QImage::Format imageFormat(const PSDHeader &header, qint32 alpha)
 {
     if (header.channel_count == 0) {
         return QImage::Format_Invalid;
@@ -539,6 +615,12 @@ static QImage::Format imageFormat(const
         else
             format = header.channel_count < 4 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
         break;
+    case CM_CMYK:   // PSD supports CMYK 8-bits and 16-bits only
+        if (header.depth == 16)
+            format = header.channel_count < 5 || alpha >= 0 ? QImage::Format_RGBX64 : QImage::Format_RGBA64;
+        else if (header.depth == 8)
+            format = header.channel_count < 5 || alpha >= 0 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
+        break;
     case CM_GRAYSCALE:
     case CM_DUOTONE:
         format = header.depth == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
@@ -597,24 +679,33 @@ inline quint32 xchg(quint32 v) {
 #endif
 }
 
+inline qint32 xchg(qint32 v) {
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+    return qint32( (quint32(v)>>24) | ((quint32(v) & 0x00FF0000)>>8) | ((quint32(v) & 0x0000FF00)<<8) | (quint32(v)<<24) );
+#else
+    return v;  // never tested
+#endif
+}
+
 template<class T>
-inline void planarToChunchy(uchar *target, const char* source, qint32 width, qint32 c, qint32 cn)
+inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
 {
     auto s = reinterpret_cast<const T*>(source);
     auto t = reinterpret_cast<T*>(target);
-    for (qint32 x = 0; x < width; ++x)
+    for (qint32 x = 0; x < width; ++x) {
         t[x*cn+c] = xchg(s[x]);
+    }
 }
 
-template<class T>
-inline void planarToChunchyFloat(uchar *target, const char* source, qint32 width, qint32 c, qint32 cn)
+template<class T, T min = 0, T max = 1>
+inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
 {
     auto s = reinterpret_cast<const T*>(source);
     auto t = reinterpret_cast<quint16*>(target);
     for (qint32 x = 0; x < width; ++x) {
         auto tmp = xchg(s[x]);
-        t[x*cn+c] = std::min(quint16(*reinterpret_cast<float*>(&tmp) * std::numeric_limits<quint16>::max() + 0.5),
-                             std::numeric_limits<quint16>::max());
+        auto ftmp = (*reinterpret_cast<float*>(&tmp) - double(min)) / (double(max) - double(min));
+        t[x*cn+c] = quint16(std::min(ftmp * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max())));
     }
 }
 
@@ -622,8 +713,60 @@ inline void monoInvert(uchar *target, co
 {
     auto s = reinterpret_cast<const quint8*>(source);
     auto t = reinterpret_cast<quint8*>(target);
-    for (qint32 x = 0; x < bytes; ++x)
+    for (qint32 x = 0; x < bytes; ++x) {
         t[x] = ~s[x];
+    }
+}
+
+template<class T>
+inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool noAlpha)
+{
+    auto s = reinterpret_cast<const T*>(source);
+    auto t = reinterpret_cast<T*>(target);
+    auto max = double(std::numeric_limits<T>::max());
+
+    if (sourceChannels < 4) {
+        qDebug() << "cmykToRgb: image is not a valid CMYK!";
+        return;
+    }
+
+    for (qint32 w = 0; w < width; ++w) {
+        auto ps = s + sourceChannels * w;
+        auto C = 1 - *(ps + 0) / double(max);
+        auto M = 1 - *(ps + 1) / double(max);
+        auto Y = 1 - *(ps + 2) / double(max);
+        auto K = 1 - *(ps + 3) / double(max);
+
+        auto pt = t + targetChannels * w;
+        *(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max));
+        *(pt + 1) = T(std::min(max - (M * (1 - K) + K) * max + 0.5, max));
+        *(pt + 2) = T(std::min(max - (Y * (1 - K) + K) * max + 0.5, max));
+        if (targetChannels == 4) {
+            if (sourceChannels >= 5 && !noAlpha)
+                *(pt + 3) = *(ps + 4);
+            else
+                *(pt + 3) = std::numeric_limits<T>::max();
+        }
+    }
+}
+
+bool readChannel(QByteArray& target, QDataStream &stream, quint32 compressedSize, quint16 compression)
+{
+    if (compression) {
+        QByteArray tmp;
+        tmp.resize(compressedSize);
+        if (stream.readRawData(tmp.data(), tmp.size()) != tmp.size()) {
+            return false;
+        }
+        if (decompress(tmp.data(), tmp.size(), target.data(), target.size()) < 0) {
+            return false;
+        }
+    }
+    else if (stream.readRawData(target.data(), target.size()) != target.size()) {
+        return false;
+    }
+
+    return stream.status() == QDataStream::Ok;
 }
 
 // Load the PSD image.
@@ -653,7 +796,8 @@ static bool LoadPSD(QDataStream &stream,
     }
 
     // Layer and Mask section
-    if (!skip_section(stream, isPsb)) {
+    auto lms = readLayerAndMaskSection(stream, isPsb, &ok);
+    if (!ok) {
         qDebug() << "Error while skipping Layer and Mask section";
         return false;
     }
@@ -669,7 +813,11 @@ static bool LoadPSD(QDataStream &stream,
         return false;
     }
 
-    const QImage::Format format = imageFormat(header);
+    // Try to identify the nature of spots: note that this is just one of many ways to identify the presence
+    // of alpha channels: should work in most cases where colorspaces != RGB/Gray
+    auto alpha = lms.layerCount; // < 0 alpha present, > 0 spots are not alpha, 0 does not decide
+
+    const QImage::Format format = imageFormat(header, alpha);
     if (format == QImage::Format_Invalid) {
         qWarning() << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count;
         return false;
@@ -697,7 +845,7 @@ static bool LoadPSD(QDataStream &stream,
 
     QVector<quint32> strides(header.height * header.channel_count, raw_count);
     // Read the compressed stride sizes
-    if (compression)
+    if (compression) {
         for (auto&& v : strides) {
             if (isPsb) {
                 stream >> v;
@@ -707,46 +855,89 @@ static bool LoadPSD(QDataStream &stream,
             stream >> tmp;
             v = tmp;
         }
+    }
+    // calculate the absolute file positions of each stride (required when a colorspace conversion should be done)
+    auto device = stream.device();
+    QVector<quint64> stridePositions(strides.size());
+    if (!stridePositions.isEmpty()) {
+        stridePositions[0] = device->pos();
+    }
+    for (qsizetype i = 1, n = stridePositions.size(); i < n; ++i) {
+        stridePositions[i] = stridePositions[i-1] + strides.at(i-1);
+    }
 
     // Read the image
     QByteArray rawStride;
     rawStride.resize(raw_count);
-    for (qint32 c = 0; c < channel_num; ++c) {
-        for(qint32 y = 0, h = header.height; y < h; ++y) {
-            auto&& strideSize = strides.at(c*qsizetype(h)+y);
-            if (compression) {
-                QByteArray tmp;
-                tmp.resize(strideSize);
-                if (stream.readRawData(tmp.data(), tmp.size()) != tmp.size()) {
-                    qDebug() << "Error while reading the stream of channel" << c << "line" << y;
+
+    if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) {
+        // In order to make a colorspace transformation, we need all channels of a scanline
+        QByteArray psdScanline;
+        psdScanline.resize(qsizetype(header.width * std::min(header.depth, quint16(16)) * header.channel_count + 7) / 8);
+        for (qint32 y = 0, h = header.height; y < h; ++y) {
+            for (qint32 c = 0; c < header.channel_count; ++c) {
+                auto strideNumber = c * qsizetype(h) + y;
+                if (!device->seek(stridePositions.at(strideNumber))) {
+                    qDebug() << "Error while seeking the stream of channel" << c << "line" << y;
                     return false;
                 }
-                if (decompress(tmp.data(), tmp.size(), rawStride.data(), rawStride.size()) < 0) {
-                    qDebug() << "Error while decompressing the channel" << c << "line" << y;
+                auto&& strideSize = strides.at(strideNumber);
+                if (!readChannel(rawStride, stream, strideSize, compression)) {
+                    qDebug() << "Error while reading the stream of channel" << c << "line" << y;
                     return false;
                 }
+
+                auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data());
+                if (header.depth == 8) {
+                    planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, header.channel_count);
+                }
+                else if (header.depth == 16) {
+                    planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
+                }
+                else if (header.depth == 32) { // Not currently used
+                    // LAB float uses LAB real values: L(0 to 100), a/b(-128 to 127)
+                    if (header.color_mode == CM_LABCOLOR && c == 0)
+                        planarToChunchyFloat<quint32, 0, 100>(scanLine, rawStride.data(), header.width, c, header.channel_count);
+                    else if (header.color_mode == CM_LABCOLOR && c < 3)
+                        planarToChunchyFloat<qint32, -128, 127>(scanLine, rawStride.data(), header.width, c, header.channel_count);
+                    else // RGB, gray, spots, etc...
+                        planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
+                }
+            }
+
+            // Conversion to RGB
+            if (header.color_mode == CM_CMYK) {
+                if (header.depth == 8)
+                    cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha >= 0);
+                else
+                    cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha >= 0);
             }
-            else {
-                if (stream.readRawData(rawStride.data(), rawStride.size()) != rawStride.size()) {
+        }
+    }
+    else {
+        // Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
+        for (qint32 c = 0; c < channel_num; ++c) {
+            for (qint32 y = 0, h = header.height; y < h; ++y) {
+                auto&& strideSize = strides.at(c * qsizetype(h) + y);
+                if (!readChannel(rawStride, stream, strideSize, compression)) {
                     qDebug() << "Error while reading the stream of channel" << c << "line" << y;
                     return false;
                 }
-            }
 
-            if (stream.status() != QDataStream::Ok) {
-                qDebug() << "Stream read error" << stream.status();
-                return false;
+                auto scanLine = img.scanLine(y);
+                if (header.depth == 1) {        // Bitmap
+                    monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
+                }
+                else if (header.depth == 8) {   // 8-bits images: Indexed, Grayscale, RGB/RGBA
+                    planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
+                }
+                else if (header.depth == 16) {  // 16-bits integer images: Grayscale, RGB/RGBA
+                    planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
+                }
+                else if (header.depth == 32) {  // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
+                    planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
+                }
             }
-
-            auto scanLine = img.scanLine(y);
-            if (header.depth == 1)          // Bitmap
-                monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine()));
-            else if (header.depth == 8)     // 8-bits images: Indexed, Grayscale, RGB/RGBA
-                planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
-            else if (header.depth == 16)    // 16-bits integer images: Grayscale, RGB/RGBA
-                planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
-            else if (header.depth == 32)    // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
-                planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
         }
     }
 
diff -pruN 5.94.0-3/src/imageformats/xcf.cpp 5.96.0-1/src/imageformats/xcf.cpp
--- 5.94.0-3/src/imageformats/xcf.cpp	2022-05-02 09:46:37.000000000 +0000
+++ 5.96.0-1/src/imageformats/xcf.cpp	2022-07-02 14:33:58.000000000 +0000
@@ -135,7 +135,7 @@ public:
         PROP_SAMPLE_POINTS = 39,
         MAX_SUPPORTED_PROPTYPE, // should always be at the end so its value is last + 1
     };
-    Q_ENUM(PropType);
+    Q_ENUM(PropType)
 
     //! Compression type used in layer tiles.
     enum XcfCompressionType {
@@ -145,7 +145,7 @@ public:
         COMPRESS_ZLIB = 2, /* unused */
         COMPRESS_FRACTAL = 3, /* unused */
     };
-    Q_ENUM(XcfCompressionType);
+    Q_ENUM(XcfCompressionType)
 
     enum LayerModeType {
         GIMP_LAYER_MODE_NORMAL_LEGACY,
@@ -212,7 +212,7 @@ public:
         GIMP_LAYER_MODE_PASS_THROUGH,
         GIMP_LAYER_MODE_COUNT,
     };
-    Q_ENUM(LayerModeType);
+    Q_ENUM(LayerModeType)
 
     //! Type of individual layers in an XCF file.
     enum GimpImageType {
@@ -223,7 +223,7 @@ public:
         INDEXED_GIMAGE,
         INDEXEDA_GIMAGE,
     };
-    Q_ENUM(GimpImageType);
+    Q_ENUM(GimpImageType)
 
     //! Type of individual layers in an XCF file.
     enum GimpColorSpace {
