Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kimageformats for openSUSE:Factory checked in at 2022-07-11 19:08:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kimageformats (Old) and /work/SRC/openSUSE:Factory/.kimageformats.new.1523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kimageformats" Mon Jul 11 19:08:47 2022 rev:108 rq:988144 version:5.96.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kimageformats/kimageformats.changes 2022-06-17 21:22:03.858758776 +0200 +++ /work/SRC/openSUSE:Factory/.kimageformats.new.1523/kimageformats.changes 2022-07-11 19:10:07.663672337 +0200 @@ -1,0 +2,20 @@ +Sun Jul 3 11:56:00 UTC 2022 - Christophe Giboudeaux <christo...@krop.fr> + +- Update to 5.96.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/frameworks/5/5.96.0 +- Changes since 5.95.0: + * PSD header checks according to specifications + * Improved detection of alpha channel on CMYK images + * Minor code optimization + * Minor code improvements (tested on all my MCYK PSD/PSB files) + * Fix Alpha + testcase images + * Fix regression + * Basic support to CMYK 8/16 bits (not fully tested) + * Require passing tests for the CI to pass + * jxl: support both old 0.6.1 and new 0.7.0 libjxl API + * Remove extra ';' + * avif: read performance improvements + +------------------------------------------------------------------- Old: ---- kimageformats-5.95.0.tar.xz kimageformats-5.95.0.tar.xz.sig New: ---- kimageformats-5.96.0.tar.xz kimageformats-5.96.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kimageformats.spec ++++++ --- /var/tmp/diff_new_pack.BeGfhV/_old 2022-07-11 19:10:08.555673632 +0200 +++ /var/tmp/diff_new_pack.BeGfhV/_new 2022-07-11 19:10:08.559673637 +0200 @@ -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.95 +%define _tar_path 5.96 # 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 released Name: kimageformats -Version: 5.95.0 +Version: 5.96.0 Release: 0 Summary: Image format plugins for Qt License: LGPL-2.1-or-later ++++++ kimageformats-5.95.0.tar.xz -> kimageformats-5.96.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/.kde-ci.yml new/kimageformats-5.96.0/.kde-ci.yml --- old/kimageformats-5.95.0/.kde-ci.yml 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/.kde-ci.yml 2022-07-02 16:33:58.000000000 +0200 @@ -6,3 +6,4 @@ Options: test-before-installing: True + require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/CMakeLists.txt new/kimageformats-5.96.0/CMakeLists.txt --- old/kimageformats-5.95.0/CMakeLists.txt 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/CMakeLists.txt 2022-07-02 16:33:58.000000000 +0200 @@ -3,7 +3,7 @@ project(KImageFormats) include(FeatureSummary) -find_package(ECM 5.95.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 @@ include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) -include(KDEGitCommitHooks) - +include(KDEGitCommitHooks) +include(ECMDeprecationSettings) include(CheckIncludeFiles) include(FindPkgConfig) @@ -70,8 +70,11 @@ 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) Binary files old/kimageformats-5.95.0/autotests/read/psd/cmyka-16bits.png and new/kimageformats-5.96.0/autotests/read/psd/cmyka-16bits.png differ Binary files old/kimageformats-5.95.0/autotests/read/psd/cmyka-16bits.psd and new/kimageformats-5.96.0/autotests/read/psd/cmyka-16bits.psd differ Binary files old/kimageformats-5.95.0/autotests/read/psd/cmyka-8bits.png and new/kimageformats-5.96.0/autotests/read/psd/cmyka-8bits.png differ Binary files old/kimageformats-5.95.0/autotests/read/psd/cmyka-8bits.psd and new/kimageformats-5.96.0/autotests/read/psd/cmyka-8bits.psd differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/src/imageformats/CMakeLists.txt new/kimageformats-5.96.0/src/imageformats/CMakeLists.txt --- old/kimageformats-5.95.0/src/imageformats/CMakeLists.txt 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/src/imageformats/CMakeLists.txt 2022-07-02 16:33:58.000000000 +0200 @@ -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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/src/imageformats/avif.cpp new/kimageformats-5.96.0/src/imageformats/avif.cpp --- old/kimageformats-5.95.0/src/imageformats/avif.cpp 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/src/imageformats/avif.cpp 2022-07-02 16:33:58.000000000 +0200 @@ -67,7 +67,7 @@ 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 @@ 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 @@ 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 @@ 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 @@ } } 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 @@ 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 @@ } #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 @@ 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 @@ switch (option) { case Size: - return m_current_image.size(); + return m_estimated_dimensions; case Animation: if (imageCount() >= 2) { return true; @@ -848,6 +884,14 @@ return 0; } + if (m_parseState == ParseAvifMetadata) { + if (m_decoder->imageCount >= 2) { + return -1; + } else { + return 0; + } + } + return m_decoder->imageIndex; } @@ -857,12 +901,14 @@ 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 @@ } if (decode_one_frame()) { + m_parseState = ParseAvifSuccess; return true; } else { m_parseState = ParseAvifError; @@ -900,7 +947,7 @@ if (m_decoder->imageCount < 2) { // not an animation if (imageNumber == 0) { - return true; + return ensureOpened(); } else { return false; } @@ -935,6 +982,7 @@ } if (decode_one_frame()) { + m_parseState = ParseAvifSuccess; return true; } else { m_parseState = ParseAvifError; @@ -944,7 +992,7 @@ int QAVIFHandler::nextImageDelay() const { - if (!ensureParsed()) { + if (!ensureOpened()) { return 0; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/src/imageformats/avif_p.h new/kimageformats-5.96.0/src/imageformats/avif_p.h --- old/kimageformats-5.95.0/src/imageformats/avif_p.h 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/src/imageformats/avif_p.h 2022-07-02 16:33:58.000000000 +0200 @@ -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 @@ private: static QPointF CompatibleChromacity(qreal chrX, qreal chrY); bool ensureParsed() const; + bool ensureOpened() const; bool ensureDecoder(); bool decode_one_frame(); @@ -52,6 +54,7 @@ ParseAvifError = -1, ParseAvifNotParsed = 0, ParseAvifSuccess = 1, + ParseAvifMetadata = 2, }; ParseAvifState m_parseState; @@ -59,6 +62,7 @@ uint32_t m_container_width; uint32_t m_container_height; + QSize m_estimated_dimensions; QByteArray m_rawData; avifROData m_rawAvifData; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/src/imageformats/jxl.cpp new/kimageformats-5.96.0/src/imageformats/jxl.cpp --- old/kimageformats-5.95.0/src/imageformats/jxl.cpp 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/src/imageformats/jxl.cpp 2022-07-02 16:33:58.000000000 +0200 @@ -143,6 +143,10 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } } +#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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/src/imageformats/psd.cpp new/kimageformats-5.96.0/src/imageformats/psd.cpp --- old/kimageformats-5.95.0/src/imageformats/psd.cpp 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/src/imageformats/psd.cpp 2022-07-02 16:33:58.000000000 +0200 @@ -101,6 +101,10 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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; } @@ -525,7 +601,7 @@ * \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 @@ 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 @@ #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 @@ { 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 @@ } // 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 @@ 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 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.95.0/src/imageformats/xcf.cpp new/kimageformats-5.96.0/src/imageformats/xcf.cpp --- old/kimageformats-5.95.0/src/imageformats/xcf.cpp 2022-06-04 10:19:33.000000000 +0200 +++ new/kimageformats-5.96.0/src/imageformats/xcf.cpp 2022-07-02 16:33:58.000000000 +0200 @@ -135,7 +135,7 @@ 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 @@ 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 @@ 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 @@ INDEXED_GIMAGE, INDEXEDA_GIMAGE, }; - Q_ENUM(GimpImageType); + Q_ENUM(GimpImageType) //! Type of individual layers in an XCF file. enum GimpColorSpace {