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-08-15 19:57:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kimageformats (Old) and /work/SRC/openSUSE:Factory/.kimageformats.new.1521 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kimageformats" Mon Aug 15 19:57:25 2022 rev:109 rq:994980 version:5.97.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kimageformats/kimageformats.changes 2022-07-11 19:10:07.663672337 +0200 +++ /work/SRC/openSUSE:Factory/.kimageformats.new.1521/kimageformats.changes 2022-08-15 19:59:26.597264733 +0200 @@ -1,0 +2,12 @@ +Sun Aug 7 22:27:28 UTC 2022 - Christophe Giboudeaux <christo...@krop.fr> + +- Update to 5.97.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/frameworks/5/5.97.0 +- Changes since 5.96.0: + * Use right type on enums + * PSD: Improve alpha detection (kde#182496) + * PSD: LAB support + +------------------------------------------------------------------- Old: ---- kimageformats-5.96.0.tar.xz kimageformats-5.96.0.tar.xz.sig New: ---- kimageformats-5.97.0.tar.xz kimageformats-5.97.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kimageformats.spec ++++++ --- /var/tmp/diff_new_pack.YciIBq/_old 2022-08-15 19:59:27.057266015 +0200 +++ /var/tmp/diff_new_pack.YciIBq/_new 2022-08-15 19:59:27.061266027 +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.96 +%define _tar_path 5.97 # 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.96.0 +Version: 5.97.0 Release: 0 Summary: Image format plugins for Qt License: LGPL-2.1-or-later ++++++ kimageformats-5.96.0.tar.xz -> kimageformats-5.97.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.96.0/CMakeLists.txt new/kimageformats-5.97.0/CMakeLists.txt --- old/kimageformats-5.96.0/CMakeLists.txt 2022-07-02 16:33:58.000000000 +0200 +++ new/kimageformats-5.97.0/CMakeLists.txt 2022-08-07 14:18:50.000000000 +0200 @@ -3,7 +3,7 @@ project(KImageFormats) include(FeatureSummary) -find_package(ECM 5.96.0 NO_MODULE) +find_package(ECM 5.97.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) Binary files old/kimageformats-5.96.0/autotests/read/psd/ccbug182496.png and new/kimageformats-5.97.0/autotests/read/psd/ccbug182496.png differ Binary files old/kimageformats-5.96.0/autotests/read/psd/ccbug182496.psd and new/kimageformats-5.97.0/autotests/read/psd/ccbug182496.psd differ Binary files old/kimageformats-5.96.0/autotests/read/psd/laba_16bit.png and new/kimageformats-5.97.0/autotests/read/psd/laba_16bit.png differ Binary files old/kimageformats-5.96.0/autotests/read/psd/laba_16bit.psd and new/kimageformats-5.97.0/autotests/read/psd/laba_16bit.psd differ Binary files old/kimageformats-5.96.0/autotests/read/psd/laba_8bit.png and new/kimageformats-5.97.0/autotests/read/psd/laba_8bit.png differ Binary files old/kimageformats-5.96.0/autotests/read/psd/laba_8bit.psd and new/kimageformats-5.97.0/autotests/read/psd/laba_8bit.psd differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.96.0/src/imageformats/psd.cpp new/kimageformats-5.97.0/src/imageformats/psd.cpp --- old/kimageformats-5.96.0/src/imageformats/psd.cpp 2022-07-02 16:33:58.000000000 +0200 +++ new/kimageformats-5.97.0/src/imageformats/psd.cpp 2022-08-07 14:18:50.000000000 +0200 @@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2003 Ignacio Casta??o <cast...@ludicon.com> SPDX-FileCopyrightText: 2015 Alex Merry <alex.me...@kde.org> - SPDX-FileCopyrightText: 2022 Mirco Miranda <mirco.mira...@systemceramics.com> + SPDX-FileCopyrightText: 2022 Mirco Miranda <mirco...@outlook.com> SPDX-License-Identifier: LGPL-2.0-or-later */ @@ -22,13 +22,15 @@ * Limitations of the current code: * - 32-bit float image are converted to 16-bit integer image. * NOTE: Qt 6.2 allow 32-bit float images (RGB only) - * - Other color spaces cannot be read due to lack of QImage support for - * color spaces other than RGB (and Grayscale): a conversion to - * RGB must be done. - * - The best way to convert between different color spaces is to use a - * color management engine (e.g. LittleCMS). - * - An approximate way is to ignore the color information and use - * literature formulas (possible but not recommended). + * - Other color spaces cannot directly be read due to lack of QImage support for + * color spaces other than RGB (and Grayscale). Where possible, a conversion + * to RGB is done: + * - CMYK images are converted using an approximated way that ignores the color + * information (ICC profile). + * - LAB images are converted to sRGB using literature formulas. + * + * NOTE: The best way to convert between different color spaces is to use a + * color management engine (e.g. LittleCMS). */ #include "psd_p.h" @@ -40,13 +42,35 @@ #include <QImage> #include <QColorSpace> +#include <cmath> + typedef quint32 uint; typedef quint16 ushort; typedef quint8 uchar; +/* The fast LAB conversion converts the image to linear sRgb instead to sRgb. + * This should not be a problem because the Qt's QColorSpace supports the linear + * sRgb colorspace. + * + * Using linear conversion, the loading speed is improved by 4x. Anyway, if you are using + * an software that discard color info, you should comment it. + * + * At the time I'm writing (07/2022), Gwenview and Krita supports linear sRgb but KDE + * preview creator does not. This is the why, for now, it is disabled. + */ +//#define PSD_FAST_LAB_CONVERSION + namespace // Private. { -enum ColorMode { + +enum Signature : quint32 { + S_8BIM = 0x3842494D, // '8BIM' + S_8B64 = 0x38423634, // '8B64' + + S_MeSa = 0x4D655361 // 'MeSa' +}; + +enum ColorMode : quint16 { CM_BITMAP = 0, CM_GRAYSCALE = 1, CM_INDEXED = 2, @@ -65,6 +89,12 @@ IRI_XMPMETADATA = 0x0424 }; +enum LayerId : quint32 { + LI_MT16 = 0x4D743136, // 'Mt16', + LI_MT32 = 0x4D743332, // 'Mt32', + LI_MTRN = 0x4D74726E // 'Mtrn' +}; + struct PSDHeader { uint signature; ushort version; @@ -101,10 +131,57 @@ using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>; -struct PSDLayerAndMaskSection { +struct PSDLayerInfo { + qint64 size = -1; qint16 layerCount = 0; }; +struct PSDGlobalLayerMaskInfo { + qint64 size = -1; +}; + +struct PSDAdditionalLayerInfo { + Signature signature = Signature(); + LayerId id = LayerId(); + qint64 size = -1; +}; + +struct PSDLayerAndMaskSection { + qint64 size = -1; + PSDLayerInfo layerInfo; + PSDGlobalLayerMaskInfo globalLayerMaskInfo; + QHash<LayerId, PSDAdditionalLayerInfo> additionalLayerInfo; + + bool isNull() const { + return (size <= 0); + } + + bool hasAlpha() const { + return layerInfo.layerCount < 0 || + additionalLayerInfo.contains(LI_MT16) || + additionalLayerInfo.contains(LI_MT32) || + additionalLayerInfo.contains(LI_MTRN); + } + + bool atEnd(bool isPsb) const { + qint64 currentSize = 0; + if (layerInfo.size > -1) { + currentSize += layerInfo.size + 4; + if (isPsb) + currentSize += 4; + } + if (globalLayerMaskInfo.size > -1) { + currentSize += globalLayerMaskInfo.size + 4; + } + for (auto && v : additionalLayerInfo.values()) { + currentSize += (12 + v.size); + if (v.signature == S_8B64) + currentSize += 4; + } + return (size <= currentSize); + } +}; + /*! * \brief fixedPointToDouble * Converts a fixed point number to floating point one. @@ -116,21 +193,28 @@ return (i+d); } -static bool skip_section(QDataStream &s, bool psb = false) +static qint64 readSize(QDataStream &s, bool psb = false) { - qint64 section_length; + qint64 size = 0; if (!psb) { quint32 tmp; s >> tmp; - section_length = tmp; + size = tmp; } else { - s >> section_length; + s >> size; + } + if (s.status() != QDataStream::Ok) { + size = -1; } + return size; +} +static bool skip_data(QDataStream &s, qint64 size) +{ // Skip mode data. - for (qint32 i32 = 0; section_length; section_length -= i32) { - i32 = std::min(section_length, qint64(std::numeric_limits<qint32>::max())); + for (qint32 i32 = 0; size; size -= i32) { + i32 = std::min(size, qint64(std::numeric_limits<qint32>::max())); i32 = s.skipRawData(i32); if (i32 < 1) return false; @@ -138,6 +222,14 @@ return true; } +static bool skip_section(QDataStream &s, bool psb = false) +{ + auto section_length = readSize(s, psb); + if (section_length < 0) + return false; + return skip_data(s, section_length); +} + /*! * \brief readPascalString * Reads the Pascal string as defined in the PSD specification. @@ -219,7 +311,7 @@ s >> signature; size -= sizeof(signature); // NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD. - if (signature != 0x3842494D && signature != 0x4D655361) { // 8BIM and MeSa + if (signature != S_8BIM && signature != S_MeSa) { // 8BIM and MeSa qDebug() << "Invalid Image Resource Block Signature!"; *ok = false; break; @@ -276,6 +368,33 @@ return irs; } +PSDAdditionalLayerInfo readAdditionalLayer(QDataStream &s, bool *ok = nullptr) +{ + PSDAdditionalLayerInfo li; + + bool tmp = true; + if (ok == nullptr) + ok = &tmp; + + s >> li.signature; + *ok = li.signature == S_8BIM || li.signature == S_8B64; + if (!*ok) + return li; + + s >> li.id; + *ok = s.status() == QDataStream::Ok; + if (!*ok) + return li; + + li.size = readSize(s, li.signature == S_8B64); + *ok = li.size >= 0; + if (!*ok) + return li; + + *ok = skip_data(s, li.size); + + return li; +} PSDLayerAndMaskSection readLayerAndMaskSection(QDataStream &s, bool isPsb, bool *ok = nullptr) { @@ -286,32 +405,38 @@ 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; - } + auto device = s.device(); + device->startTransaction(); + + lms.size = readSize(s, isPsb); + + // read layer info + if (s.status() == QDataStream::Ok && !lms.atEnd(isPsb)) { + lms.layerInfo.size = readSize(s, isPsb); + if (lms.layerInfo.size > 0) { + s >> lms.layerInfo.layerCount; + skip_data(s, lms.layerInfo.size - sizeof(lms.layerInfo.layerCount)); } + } + + // read global layer mask info + if (s.status() == QDataStream::Ok && !lms.atEnd(isPsb)) { + lms.globalLayerMaskInfo.size = readSize(s, false); // always 32-bits + if (lms.globalLayerMaskInfo.size > 0) { + skip_data(s, lms.globalLayerMaskInfo.size); + } + } - if (s.status() == QDataStream::Ok) { - if (size >= 2) - s >> lms.layerCount; + // read additional layer info + if (s.status() == QDataStream::Ok) { + for (bool ok = true; ok && !lms.atEnd(isPsb);) { + auto al = readAdditionalLayer(s, &ok); + if (ok) + lms.additionalLayerInfo.insert(al.id, al); } - device->rollbackTransaction(); } + device->rollbackTransaction(); *ok = skip_section(s, isPsb); return lms; } @@ -547,6 +672,7 @@ header.color_mode != CM_INDEXED && header.color_mode != CM_DUOTONE && header.color_mode != CM_CMYK && + header.color_mode != CM_LABCOLOR && header.color_mode != CM_BITMAP) { return false; } @@ -573,7 +699,7 @@ if (n >= 0) { rr = qint64(n) + 1; if (available < rr) { - ip--; + --ip; break; } @@ -585,7 +711,7 @@ else if (ip < ilen) { rr = qint64(1-n); if (available < rr) { - ip--; + --ip; break; } memset(output + j, input[ip++], size_t(rr)); @@ -601,7 +727,7 @@ * \param header The PSD header. * \return The Qt image format. */ -static QImage::Format imageFormat(const PSDHeader &header, qint32 alpha) +static QImage::Format imageFormat(const PSDHeader &header, bool alpha) { if (header.channel_count == 0) { return QImage::Format_Invalid; @@ -611,15 +737,21 @@ switch(header.color_mode) { case CM_RGB: if (header.depth == 16 || header.depth == 32) - format = header.channel_count < 4 ? QImage::Format_RGBX64 : QImage::Format_RGBA64; + format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; else - format = header.channel_count < 4 ? QImage::Format_RGB888 : QImage::Format_RGBA8888; + format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; + break; + case CM_CMYK: // Photoshop supports CMYK 8-bits and 16-bits only + if (header.depth == 16) + format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; + else if (header.depth == 8) + format = header.channel_count < 5 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; break; - case CM_CMYK: // PSD supports CMYK 8-bits and 16-bits only + case CM_LABCOLOR: // Photoshop supports LAB 8-bits and 16-bits only if (header.depth == 16) - format = header.channel_count < 5 || alpha >= 0 ? QImage::Format_RGBX64 : QImage::Format_RGBA64; + format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; else if (header.depth == 8) - format = header.channel_count < 5 || alpha >= 0 ? QImage::Format_RGB888 : QImage::Format_RGBA8888; + format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; break; case CM_GRAYSCALE: case CM_DUOTONE: @@ -679,14 +811,6 @@ #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) { @@ -719,7 +843,7 @@ } template<class T> -inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool noAlpha) +inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false) { auto s = reinterpret_cast<const T*>(source); auto t = reinterpret_cast<T*>(target); @@ -732,17 +856,17 @@ 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 C = 1 - *(ps + 0) / max; + auto M = 1 - *(ps + 1) / max; + auto Y = 1 - *(ps + 2) / max; + auto K = 1 - *(ps + 3) / 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) + if (sourceChannels >= 5 && alpha) *(pt + 3) = *(ps + 4); else *(pt + 3) = std::numeric_limits<T>::max(); @@ -750,6 +874,67 @@ } } +inline double finv(double v) +{ + return (v > 6.0 / 29.0 ? v * v * v : (v - 16.0 / 116.0) / 7.787); +} + +inline double gammaCorrection(double linear) +{ +#ifdef PSD_FAST_LAB_CONVERSION + return linear; +#else + // NOTE: pow() slow down the performance by a 4 factor :( + return (linear > 0.0031308 ? 1.055 * std::pow(linear, 1.0 / 2.4) - 0.055 : 12.92 * linear); +#endif +} + +template<class T> +inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false) +{ + auto s = reinterpret_cast<const T*>(source); + auto t = reinterpret_cast<T*>(target); + auto max = double(std::numeric_limits<T>::max()); + + if (sourceChannels < 3) { + qDebug() << "labToRgb: image is not a valid LAB!"; + return; + } + + for (qint32 w = 0; w < width; ++w) { + auto ps = s + sourceChannels * w; + auto L = (*(ps + 0) / max) * 100.0; + auto A = (*(ps + 1) / max) * 255.0 - 128.0; + auto B = (*(ps + 2) / max) * 255.0 - 128.0; + + // converting LAB to XYZ (D65 illuminant) + auto Y = (L + 16.0) / 116.0; + auto X = A / 500.0 + Y; + auto Z = Y - B / 200.0; + + // NOTE: use the constants of the illuminant of the target RGB color space + X = finv(X) * 0.9504; // D50: * 0.9642 + Y = finv(Y) * 1.0000; // D50: * 1.0000 + Z = finv(Z) * 1.0888; // D50: * 0.8251 + + // converting XYZ to sRGB (sRGB illuminant is D65) + auto r = gammaCorrection( 3.24071 * X - 1.53726 * Y - 0.498571 * Z); + auto g = gammaCorrection(- 0.969258 * X + 1.87599 * Y + 0.0415557 * Z); + auto b = gammaCorrection( 0.0556352 * X - 0.203996 * Y + 1.05707 * Z); + + auto pt = t + targetChannels * w; + *(pt + 0) = T(std::max(std::min(r * max + 0.5, max), 0.0)); + *(pt + 1) = T(std::max(std::min(g * max + 0.5, max), 0.0)); + *(pt + 2) = T(std::max(std::min(b * max + 0.5, max), 0.0)); + if (targetChannels == 4) { + if (sourceChannels >= 4 && alpha) + *(pt + 3) = *(ps + 3); + else + *(pt + 3) = std::numeric_limits<T>::max(); + } + } +} + bool readChannel(QByteArray& target, QDataStream &stream, quint32 compressedSize, quint16 compression) { if (compression) { @@ -815,7 +1000,9 @@ // 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 + auto alpha = header.color_mode == CM_RGB; + if (!lms.isNull()) + alpha = lms.hasAlpha(); const QImage::Format format = imageFormat(header, alpha); if (format == QImage::Format_Invalid) { @@ -895,22 +1082,22 @@ 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); + 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); + cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); else - cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha >= 0); + cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); + } + if (header.color_mode == CM_LABCOLOR) { + if (header.depth == 8) + labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); + else + labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); } } } @@ -941,6 +1128,15 @@ } } + // LAB conversion generates a sRGB image + if (header.color_mode == CM_LABCOLOR) { +#ifdef PSD_FAST_LAB_CONVERSION + img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); +#else + img.setColorSpace(QColorSpace(QColorSpace::SRgb)); +#endif + } + // Resolution info if (!setResolution(img, irs)) { // qDebug() << "No resolution info found!"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kimageformats-5.96.0/src/imageformats/psd.desktop new/kimageformats-5.97.0/src/imageformats/psd.desktop --- old/kimageformats-5.96.0/src/imageformats/psd.desktop 2022-07-02 16:33:58.000000000 +0200 +++ new/kimageformats-5.97.0/src/imageformats/psd.desktop 2022-08-07 14:18:50.000000000 +0200 @@ -1,7 +1,7 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=QImageIOPlugins -X-KDE-ImageFormat=psd +X-KDE-ImageFormat=psd,psb,pdd,psdt X-KDE-MimeType=image/vnd.adobe.photoshop X-KDE-Read=true X-KDE-Write=false