Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kimageformats for openSUSE:Factory 
checked in at 2021-02-17 18:11:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kimageformats (Old)
 and      /work/SRC/openSUSE:Factory/.kimageformats.new.28504 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kimageformats"

Wed Feb 17 18:11:01 2021 rev:89 rq:871701 version:5.79.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/kimageformats/kimageformats.changes      
2021-01-13 18:30:25.729871661 +0100
+++ /work/SRC/openSUSE:Factory/.kimageformats.new.28504/kimageformats.changes   
2021-02-17 18:12:10.677971919 +0100
@@ -1,0 +2,15 @@
+Mon Feb  8 08:46:25 UTC 2021 - Christophe Giboudeaux <christo...@krop.fr>
+
+- Update to 5.79.0
+  * New feature release
+  * For more details please see:
+  * https://kde.org/announcements/kde-frameworks-5.79.0
+- Changes since 5.78.0:
+  * Simplify portion of NCLX color profile code
+  * [imagedump] Add "list MIME type" (-m) option
+  * Fix crash with malformed files
+  * ani: Make sure riffSizeData is of the correct size before doing the 
quint32_le cast dance
+  * Add missing includes
+  * Add plugin for animated Windows cursors (ANI)
+
+-------------------------------------------------------------------

Old:
----
  kimageformats-5.78.0.tar.xz
  kimageformats-5.78.0.tar.xz.sig

New:
----
  kimageformats-5.79.0.tar.xz
  kimageformats-5.79.0.tar.xz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kimageformats.spec ++++++
--- /var/tmp/diff_new_pack.N1CIOd/_old  2021-02-17 18:12:11.241972381 +0100
+++ /var/tmp/diff_new_pack.N1CIOd/_new  2021-02-17 18:12:11.245972384 +0100
@@ -19,7 +19,7 @@
 %if 0%{?suse_version} > 1500
 %define with_avif 1
 %endif
-%define _tar_path 5.78
+%define _tar_path 5.79
 # Full KF5 version (e.g. 5.33.0)
 %{!?_kf5_version: %global _kf5_version %{version}}
 # Last major and minor KF5 version (e.g. 5.33)
@@ -27,7 +27,7 @@
 # Only needed for the package signature condition
 %bcond_without lang
 Name:           kimageformats
-Version:        5.78.0
+Version:        5.79.0
 Release:        0
 Summary:        Image format plugins for Qt
 License:        LGPL-2.1-or-later
@@ -86,6 +86,7 @@
 %files
 %license LICENSES/*
 %dir %{_kf5_plugindir}/imageformats
+%{_kf5_plugindir}/imageformats/kimg_ani.so
 %if 0%{?with_avif}
 %{_kf5_plugindir}/imageformats/kimg_avif.so
 %endif
@@ -101,6 +102,7 @@
 %{_kf5_plugindir}/imageformats/kimg_tga.so
 %{_kf5_plugindir}/imageformats/kimg_xcf.so
 %dir %{_kf5_servicesdir}/qimageioplugins
+%{_kf5_servicesdir}/qimageioplugins/ani.desktop
 %if 0%{?with_avif}
 %{_kf5_servicesdir}/qimageioplugins/avif.desktop
 %endif


++++++ kimageformats-5.78.0.tar.xz -> kimageformats-5.79.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/.gitignore 
new/kimageformats-5.79.0/.gitignore
--- old/kimageformats-5.78.0/.gitignore 2021-01-02 13:27:55.000000000 +0100
+++ new/kimageformats-5.79.0/.gitignore 2021-02-02 09:28:32.000000000 +0100
@@ -20,3 +20,4 @@
 CMakeLists.txt.user*
 *.unc-backup*
 .cmake/
+/.clang-format
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/CMakeLists.txt 
new/kimageformats-5.79.0/CMakeLists.txt
--- old/kimageformats-5.78.0/CMakeLists.txt     2021-01-02 13:27:55.000000000 
+0100
+++ new/kimageformats-5.79.0/CMakeLists.txt     2021-02-02 09:28:32.000000000 
+0100
@@ -5,7 +5,7 @@
 set (CMAKE_CXX_STANDARD 14)
 
 include(FeatureSummary)
-find_package(ECM 5.78.0  NO_MODULE)
+find_package(ECM 5.79.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)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/README.md 
new/kimageformats-5.79.0/README.md
--- old/kimageformats-5.78.0/README.md  2021-01-02 13:27:55.000000000 +0100
+++ new/kimageformats-5.79.0/README.md  2021-02-02 09:28:32.000000000 +0100
@@ -13,6 +13,7 @@
 
 The following image formats have read-only support:
 
+- Animated Windows cursors (ani)
 - Gimp (xcf)
 - OpenEXR (exr)
 - Photoshop documents (psd)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/autotests/CMakeLists.txt 
new/kimageformats-5.79.0/autotests/CMakeLists.txt
--- old/kimageformats-5.78.0/autotests/CMakeLists.txt   2021-01-02 
13:27:55.000000000 +0100
+++ new/kimageformats-5.79.0/autotests/CMakeLists.txt   2021-02-02 
09:28:32.000000000 +0100
@@ -116,3 +116,8 @@
 target_link_libraries(pictest Qt5::Gui Qt5::Test)
 ecm_mark_as_test(pictest)
 add_test(NAME kimageformats-pic COMMAND pictest)
+
+add_executable(anitest anitest.cpp)
+target_link_libraries(anitest Qt5::Gui Qt5::Test)
+ecm_mark_as_test(anitest)
+add_test(NAME kimageformats-ani COMMAND anitest)
Binary files old/kimageformats-5.78.0/autotests/ani/test.ani and 
new/kimageformats-5.79.0/autotests/ani/test.ani differ
Binary files old/kimageformats-5.78.0/autotests/ani/test_1.png and 
new/kimageformats-5.79.0/autotests/ani/test_1.png differ
Binary files old/kimageformats-5.78.0/autotests/ani/test_2.png and 
new/kimageformats-5.79.0/autotests/ani/test_2.png differ
Binary files old/kimageformats-5.78.0/autotests/ani/test_3.png and 
new/kimageformats-5.79.0/autotests/ani/test_3.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/autotests/anitest.cpp 
new/kimageformats-5.79.0/autotests/anitest.cpp
--- old/kimageformats-5.78.0/autotests/anitest.cpp      1970-01-01 
01:00:00.000000000 +0100
+++ new/kimageformats-5.79.0/autotests/anitest.cpp      2021-02-02 
09:28:32.000000000 +0100
@@ -0,0 +1,119 @@
+/*
+    SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <k...@broulik.de>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR 
LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include <QImage>
+#include <QImageReader>
+#include <QTest>
+
+static bool imgEquals(const QImage &im1, const QImage &im2)
+{
+    const int height = im1.height();
+    const int width = im1.width();
+    for (int i = 0; i < height; ++i) {
+        const auto *line1 = reinterpret_cast<const quint8 *>(im1.scanLine(i));
+        const auto *line2 = reinterpret_cast<const quint8 *>(im2.scanLine(i));
+        for (int j = 0; j < width; ++j) {
+            if (line1[j] - line2[j] != 0) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+class AniTests : public QObject
+{
+    Q_OBJECT
+
+private Q_SLOTS:
+    void initTestCase()
+    {
+        QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
+    }
+
+    void testReadMetadata()
+    {
+        QImageReader reader(QFINDTESTDATA("ani/test.ani"));
+
+        QVERIFY(reader.canRead());
+
+        QCOMPARE(reader.imageCount(), 4);
+
+        QCOMPARE(reader.size(), QSize(32, 32));
+
+        QCOMPARE(reader.text(QStringLiteral("Title")), QStringLiteral("ANI 
Test"));
+        QCOMPARE(reader.text(QStringLiteral("Author")), QStringLiteral("KDE 
Community"));
+    }
+
+    void textRead()
+    {
+        QImageReader reader(QFINDTESTDATA("ani/test.ani"));
+        QVERIFY(reader.canRead());
+        QCOMPARE(reader.currentImageNumber(), 0);
+
+        QImage aniFrame;
+        QVERIFY(reader.read(&aniFrame));
+
+        QImage img1(QFINDTESTDATA("ani/test_1.png"));
+        img1.convertTo(aniFrame.format());
+
+        QVERIFY(imgEquals(aniFrame, img1));
+
+        QCOMPARE(reader.nextImageDelay(), 166); // 10 "jiffies"
+
+        QVERIFY(reader.canRead());
+        // that read() above should have advanced us to the next frame
+        QCOMPARE(reader.currentImageNumber(), 1);
+
+        QVERIFY(reader.read(&aniFrame));
+        QImage img2(QFINDTESTDATA("ani/test_2.png"));
+        img2.convertTo(aniFrame.format());
+
+        QVERIFY(imgEquals(aniFrame, img2));
+
+        // The "middle" frame has a longer delay than the others
+        QCOMPARE(reader.nextImageDelay(), 333); // 20 "jiffies"
+
+        QVERIFY(reader.canRead());
+        QCOMPARE(reader.currentImageNumber(), 2);
+
+        QVERIFY(reader.read(&aniFrame));
+        QImage img3(QFINDTESTDATA("ani/test_3.png"));
+        img3.convertTo(aniFrame.format());
+
+        QVERIFY(imgEquals(aniFrame, img3));
+
+        QCOMPARE(reader.nextImageDelay(), 166);
+
+        QVERIFY(reader.canRead());
+        QCOMPARE(reader.currentImageNumber(), 3);
+
+        QVERIFY(reader.read(&aniFrame));
+        // custom sequence in the ANI file should get us back to img2
+        QVERIFY(imgEquals(aniFrame, img2));
+
+        QCOMPARE(reader.nextImageDelay(), 166);
+
+        // We should have reached the end now
+        QVERIFY(!reader.canRead());
+        QVERIFY(!reader.read(&aniFrame));
+
+        // Jump back to the start
+        QVERIFY(reader.jumpToImage(0));
+
+        QVERIFY(reader.canRead());
+        QCOMPARE(reader.currentImageNumber(), 0);
+
+        QCOMPARE(reader.nextImageDelay(), 166);
+
+        QVERIFY(reader.read(&aniFrame));
+        QVERIFY(imgEquals(aniFrame, img1));
+    }
+};
+
+QTEST_MAIN(AniTests)
+
+#include "anitest.moc"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/src/imageformats/CMakeLists.txt 
new/kimageformats-5.79.0/src/imageformats/CMakeLists.txt
--- old/kimageformats-5.78.0/src/imageformats/CMakeLists.txt    2021-01-02 
13:27:55.000000000 +0100
+++ new/kimageformats-5.79.0/src/imageformats/CMakeLists.txt    2021-02-02 
09:28:32.000000000 +0100
@@ -24,6 +24,11 @@
 
 ##################################
 
+kimageformats_add_plugin(kimg_ani JSON "ani.json" SOURCES ani.cpp)
+install(FILES ani.desktop DESTINATION 
${KDE_INSTALL_KSERVICES5DIR}/qimageioplugins/)
+
+##################################
+
 if (TARGET avif)
     kimageformats_add_plugin(kimg_avif JSON "avif.json" SOURCES "avif.cpp")
     target_link_libraries(kimg_avif "avif")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/src/imageformats/ani.cpp 
new/kimageformats-5.79.0/src/imageformats/ani.cpp
--- old/kimageformats-5.78.0/src/imageformats/ani.cpp   1970-01-01 
01:00:00.000000000 +0100
+++ new/kimageformats-5.79.0/src/imageformats/ani.cpp   2021-02-02 
09:28:32.000000000 +0100
@@ -0,0 +1,571 @@
+/*
+    SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <k...@broulik.de>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "ani_p.h"
+
+#include <QDebug>
+#include <QImage>
+#include <QScopeGuard>
+#include <QtEndian>
+#include <QVariant>
+
+namespace
+{
+
+struct ChunkHeader {
+    char magic[4];
+    quint32_le size;
+};
+
+struct AniHeader {
+    quint32_le cbSize;
+    quint32_le nFrames; // number of actual frames in the file
+    quint32_le nSteps; // number of logical images
+    quint32_le iWidth;
+    quint32_le iHeight;
+    quint32_le iBitCount;
+    quint32_le nPlanes;
+    quint32_le iDispRate;
+    quint32_le bfAttributes; // attributes (0 = bitmap images, 1 = ico/cur, 3 
= "seq" block available)
+};
+
+struct CurHeader {
+   quint16_le wReserved; // always 0
+   quint16_le wResID; // always 2
+   quint16_le wNumImages;
+};
+
+struct CursorDirEntry {
+    quint8 bWidth;
+    quint8 bHeight;
+    quint8 bColorCount;
+    quint8 bReserved;  // always 0
+    quint16_le wHotspotX;
+    quint16_le wHotspotY;
+    quint32_le dwBytesInImage;
+    quint32_le dwImageOffset;
+};
+
+} // namespace
+
+ANIHandler::ANIHandler() = default;
+
+bool ANIHandler::canRead() const
+{
+    if (canRead(device())) {
+        setFormat("ani");
+        return true;
+    }
+
+    // Check if there's another frame coming
+    const QByteArray nextFrame = device()->peek(sizeof(ChunkHeader));
+    if (nextFrame.size() == sizeof(ChunkHeader)) {
+        const auto *header = reinterpret_cast<const ChunkHeader 
*>(nextFrame.data());
+        if (qstrncmp(header->magic, "icon", sizeof(header->magic)) == 0
+                && header->size > 0) {
+            setFormat("ani");
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool ANIHandler::read(QImage *outImage)
+{
+    if (!ensureScanned()) {
+        return false;
+    }
+
+    if (device()->pos() < m_firstFrameOffset) {
+        device()->seek(m_firstFrameOffset);
+    }
+
+    const QByteArray frameType = device()->read(4);
+    if (frameType != "icon") {
+        return false;
+    }
+
+    const QByteArray frameSizeData = device()->read(sizeof(quint32_le));
+    if (frameSizeData.count() != sizeof(quint32_le)) {
+        return false;
+    }
+
+    const auto frameSize = *(reinterpret_cast<const quint32_le 
*>(frameSizeData.data()));
+    if (!frameSize) {
+        return false;
+    }
+
+    const QByteArray frameData = device()->read(frameSize);
+
+    const bool ok = outImage->loadFromData(frameData, "cur");
+
+    ++m_currentImageNumber;
+
+    // When we have a custom image sequence, seek to before the frame that 
would follow
+    if (!m_imageSequence.isEmpty()) {
+        if (m_currentImageNumber < m_imageSequence.count()) {
+            const int nextFrame = m_imageSequence.at(m_currentImageNumber);
+            if (nextFrame < 0 || nextFrame >= m_frameOffsets.count()) {
+                return false;
+            }
+            const auto nextOffset = m_frameOffsets.at(nextFrame);
+            device()->seek(nextOffset);
+        } else if (m_currentImageNumber == m_imageSequence.count()) {
+            const auto endOffset = m_frameOffsets.last();
+            if (device()->pos() != endOffset) {
+                device()->seek(endOffset);
+            }
+        }
+    }
+
+    return ok;
+}
+
+int ANIHandler::currentImageNumber() const
+{
+    if (!ensureScanned()) {
+        return 0;
+    }
+    return m_currentImageNumber;
+}
+
+int ANIHandler::imageCount() const
+{
+    if (!ensureScanned()) {
+        return 0;
+    }
+    return m_imageCount;
+}
+
+bool ANIHandler::jumpToImage(int imageNumber)
+{
+    if (!ensureScanned()) {
+        return false;
+    }
+
+    if (imageNumber < 0) {
+        return false;
+    }
+
+    if (imageNumber == m_currentImageNumber) {
+        return true;
+    }
+
+    // If we have a custom image sequence we have a index of frames we can 
jump to
+    if (!m_imageSequence.isEmpty()) {
+        if (imageNumber >= m_imageSequence.count()) {
+            return false;
+        }
+
+        const int targetFrame = m_imageSequence.at(imageNumber);
+
+        const auto targetOffset = m_frameOffsets.value(targetFrame, -1);
+
+        if (device()->seek(targetOffset)) {
+            m_currentImageNumber = imageNumber;
+            return true;
+        }
+
+        return false;
+    }
+
+    if (imageNumber >= m_frameCount) {
+        return false;
+    }
+
+    // otherwise we need to jump from frame to frame
+    const auto oldPos = device()->pos();
+
+    if (imageNumber < m_currentImageNumber) {
+        // start from the beginning
+        if (!device()->seek(m_firstFrameOffset)) {
+            return false;
+        }
+    }
+
+    while (m_currentImageNumber < imageNumber) {
+        if (!jumpToNextImage()) {
+            device()->seek(oldPos);
+            return false;
+        }
+    }
+
+    m_currentImageNumber = imageNumber;
+    return true;
+}
+
+bool ANIHandler::jumpToNextImage()
+{
+    if (!ensureScanned()) {
+        return false;
+    }
+
+    // If we have a custom image sequence we have a index of frames we can 
jump to
+    // Delegate to jumpToImage
+    if (!m_imageSequence.isEmpty()) {
+        return jumpToImage(m_currentImageNumber + 1);
+    }
+
+    if (device()->pos() < m_firstFrameOffset) {
+        if (!device()->seek(m_firstFrameOffset)) {
+            return false;
+        }
+    }
+
+    const QByteArray nextFrame = device()->peek(sizeof(ChunkHeader));
+    if (nextFrame.size() != sizeof(ChunkHeader)) {
+        return false;
+    }
+
+    const auto *header = reinterpret_cast<const ChunkHeader 
*>(nextFrame.data());
+    if (qstrncmp(header->magic, "icon", sizeof(header->magic)) != 0) {
+        return false;
+    }
+
+    const qint64 seekBy = sizeof(ChunkHeader) + header->size;
+
+    if (!device()->seek(device()->pos() + seekBy)) {
+        return false;
+    }
+
+    ++m_currentImageNumber;
+    return true;
+}
+
+int ANIHandler::loopCount() const
+{
+    if (!ensureScanned()) {
+        return 0;
+    }
+    return -1;
+}
+
+int ANIHandler::nextImageDelay() const
+{
+    if (!ensureScanned()) {
+        return 0;
+    }
+
+    int rate = m_displayRate;
+
+    if (!m_displayRates.isEmpty()) {
+        int previousImage = m_currentImageNumber - 1;
+        if (previousImage < 0) {
+            previousImage = m_displayRates.count() - 1;
+        }
+        rate = m_displayRates.at(previousImage);
+    }
+
+    return rate * 1000 / 60;
+}
+
+bool ANIHandler::supportsOption(ImageOption option) const
+{
+    return option == Size || option == Name || option == Description || option 
== Animation;
+}
+
+QVariant ANIHandler::option(ImageOption option) const
+{
+    if (!supportsOption(option) || !ensureScanned()) {
+        return QVariant();
+    }
+
+    switch (option) {
+    case QImageIOHandler::Size:
+        return m_size;
+    // TODO QImageIOHandler::Format
+    // but both iBitCount in AniHeader and bColorCount are just zero most of 
the time
+    // so one would probably need to traverse even further down into IcoHeader 
and IconDirEntry...
+    // but Qt's ICO/CUR handler always seems to give us a ARB
+    case QImageIOHandler::Name:
+        return m_name;
+    case QImageIOHandler::Description: {
+        QString description;
+        if (!m_name.isEmpty()) {
+            description += QStringLiteral("Title: %1\n\n").arg(m_name);
+        }
+        if (!m_artist.isEmpty()) {
+            description += QStringLiteral("Author: %1\n\n").arg(m_artist);
+        }
+        return description;
+    }
+
+    case QImageIOHandler::Animation:
+        return true;
+    default:
+        break;
+    }
+
+    return QVariant();
+}
+
+bool ANIHandler::ensureScanned() const
+{
+    if (m_scanned) {
+        return true;
+    }
+
+    if (device()->isSequential()) {
+        return false;
+    }
+
+    auto *mutableThis = const_cast<ANIHandler *>(this);
+
+    const auto oldPos = device()->pos();
+    auto cleanup = qScopeGuard([this, oldPos] {
+        device()->seek(oldPos);
+    });
+
+    device()->seek(0);
+
+    const QByteArray riffIntro = device()->read(4);
+    if (riffIntro != "RIFF") {
+        return false;
+    }
+
+    const auto riffSizeData = device()->read(sizeof(quint32_le));
+    if (riffSizeData.size() != sizeof(quint32_le)) {
+        return false;
+    }
+    const auto riffSize = *(reinterpret_cast<const quint32_le 
*>(riffSizeData.data()));
+    // TODO do a basic sanity check if the size is enough to hold some 
metadata and a frame?
+    if (riffSize == 0) {
+        return false;
+    }
+
+    mutableThis->m_displayRates.clear();
+    mutableThis->m_imageSequence.clear();
+
+    while (device()->pos() < riffSize) {
+        const QByteArray chunkId = device()->read(4);
+        if (chunkId.length() != 4) {
+            return false;
+        }
+
+        if (chunkId == "ACON") {
+            continue;
+        }
+
+        const QByteArray chunkSizeData = device()->read(sizeof(quint32_le));
+        if (chunkSizeData.length() != sizeof(quint32_le)) {
+            return false;
+        }
+        auto chunkSize = *(reinterpret_cast<const quint32_le 
*>(chunkSizeData.data()));
+
+        if (chunkId == "anih") {
+            if (chunkSize != sizeof(AniHeader)) {
+                qWarning() << "anih chunk size does not match ANIHEADER size";
+                return false;
+            }
+
+            const QByteArray anihData = device()->read(sizeof(AniHeader));
+            if (anihData.size() != sizeof(AniHeader)) {
+                return false;
+            }
+
+            auto *aniHeader = reinterpret_cast<const AniHeader 
*>(anihData.data());
+
+            // The size in the ani header is usually 0 unfortunately,
+            // so we'll also check the first frame for its size further below
+            mutableThis->m_size = QSize(aniHeader->iWidth, aniHeader->iHeight);
+            mutableThis->m_frameCount = aniHeader->nFrames;
+            mutableThis->m_imageCount = aniHeader->nSteps;
+            mutableThis->m_displayRate = aniHeader->iDispRate;
+        } else if (chunkId == "rate" || chunkId == "seq ") {
+            const QByteArray data = device()->read(chunkSize);
+            if (static_cast<quint32_le>(data.size()) != chunkSize
+                    || data.size() % sizeof(quint32_le) != 0) {
+                return false;
+            }
+
+            // TODO should we check that the number of rate entries matches 
nSteps?
+            auto *dataPtr = data.data();
+            QVector<int> list;
+            for (int i = 0; i < data.count(); i += sizeof(quint32_le)) {
+                const auto entry = *(reinterpret_cast<const quint32_le 
*>(dataPtr + i));
+                list.append(entry);
+            }
+
+            if (chunkId == "rate") {
+                // should we check that the number of rate entries matches 
nSteps?
+                mutableThis->m_displayRates = list;
+            } else if (chunkId == "seq ") {
+                // Check if it's just an ascending sequence, don't bother with 
it then
+                bool isAscending = true;
+                for (int i = 0; i < list.count(); ++i) {
+                    if (list.at(i) != i) {
+                        isAscending = false;
+                        break;
+                    }
+                }
+
+                if (!isAscending) {
+                    mutableThis->m_imageSequence = list;
+                }
+            }
+        // IART and INAM are technically inside LIST->INFO but "INFO" is 
supposedly optional
+        // so just handle those two attributes wherever we encounter them
+        } else if (chunkId == "INAM" || chunkId == "IART") {
+            const QByteArray value = device()->read(chunkSize);
+
+            if (static_cast<quint32_le>(value.size()) != chunkSize) {
+                return false;
+            }
+
+            // DWORDs are aligned to even sizes
+            if (chunkSize % 2 != 0) {
+                device()->read(1);
+            }
+
+            // FIXME encoding
+            const QString stringValue = QString::fromLocal8Bit(value);
+            if (chunkId == "INAM") {
+                mutableThis->m_name = stringValue;
+            } else if (chunkId == "IART") {
+                mutableThis->m_artist = stringValue;
+            }
+        } else if (chunkId == "LIST") {
+            const QByteArray listType = device()->read(4);
+
+            if (listType == "INFO") {
+                // Technically would contain INAM and IART but we handle them 
anywhere above
+            } else if (listType == "fram") {
+                quint64 read = 0;
+                while (read < chunkSize) {
+                    const QByteArray chunkType = device()->read(4);
+                    read += 4;
+                    if (chunkType != "icon") {
+                        break;
+                    }
+
+                    if (!m_firstFrameOffset) {
+                        mutableThis->m_firstFrameOffset = device()->pos() - 4;
+                        mutableThis->m_currentImageNumber = 0;
+
+                        // If size in header isn't valid, use the first 
frame's size instead
+                        if (!m_size.isValid() || m_size.isEmpty()) {
+                            const auto oldPos = device()->pos();
+
+                            device()->read(sizeof(quint32_le));
+
+                            const QByteArray curHeaderData = 
device()->read(sizeof(CurHeader));
+                            const QByteArray cursorDirEntryData = 
device()->read(sizeof(CursorDirEntry));
+
+                            if (curHeaderData.length() == sizeof(CurHeader)
+                                    && cursorDirEntryData.length() == 
sizeof(CursorDirEntry)) {
+                                auto *cursorDirEntry = reinterpret_cast<const 
CursorDirEntry *>(cursorDirEntryData.data());
+                                mutableThis->m_size = 
QSize(cursorDirEntry->bWidth, cursorDirEntry->bHeight);
+                            }
+
+                            device()->seek(oldPos);
+                        }
+
+                        // If we don't have a custom image sequence we can 
stop scanning right here
+                        if (m_imageSequence.isEmpty()) {
+                            break;
+                        }
+                    }
+
+                    mutableThis->m_frameOffsets.append(device()->pos() - 4);
+
+                    const QByteArray frameSizeData = 
device()->read(sizeof(quint32_le));
+                    if (frameSizeData.size() != sizeof(quint32_le)) {
+                        return false;
+                    }
+
+                    const auto frameSize = *(reinterpret_cast<const quint32_le 
*>(frameSizeData.data()));
+                    device()->seek(device()->pos() + frameSize);
+
+                    read += frameSize;
+
+                    if (m_frameOffsets.count() == m_frameCount) {
+                        // Also record the end of frame data
+                        mutableThis->m_frameOffsets.append(device()->pos() - 
4);
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    if (m_imageCount != m_frameCount && m_imageSequence.isEmpty()) {
+        qWarning("ANIHandler: 'nSteps' is not equal to 'nFrames' but no 'seq' 
entries were provided");
+        return false;
+    }
+
+    if (!m_imageSequence.isEmpty() && m_imageSequence.count() != m_imageCount) 
{
+        qWarning("ANIHandler: count of entries in 'seq' does not match 
'nSteps' in anih");
+        return false;
+    }
+
+    if (!m_displayRates.isEmpty() && m_displayRates.count() != m_imageCount) {
+        qWarning("ANIHandler: count of entries in 'rate' does not match 
'nSteps' in anih");
+        return false;
+    }
+
+    if (!m_frameOffsets.isEmpty() && m_frameOffsets.count() != m_frameCount + 
1) {
+        qWarning("ANIHandler: number of actual frames does not match 'nFrames' 
in anih");
+        return false;
+    }
+
+    mutableThis->m_scanned = true;
+    return true;
+}
+
+bool ANIHandler::canRead(QIODevice *device)
+{
+    if (!device) {
+        qWarning("ANIHandler::canRead() called with no device");
+        return false;
+    }
+
+    const QByteArray riffIntro = device->peek(12);
+
+    if (riffIntro.length() != 12) {
+        return false;
+    }
+
+    if (!riffIntro.startsWith("RIFF")) {
+        return false;
+    }
+
+    // TODO sanity check chunk size?
+
+    if (riffIntro.mid(4 + 4, 4) != "ACON") {
+        return false;
+    }
+
+    return true;
+}
+
+QImageIOPlugin::Capabilities ANIPlugin::capabilities(QIODevice *device, const 
QByteArray &format) const
+{
+    if (format == "ani") {
+        return Capabilities(CanRead);
+    }
+    if (!format.isEmpty()) {
+        return {};
+    }
+    if (!device->isOpen()) {
+        return {};
+    }
+
+    Capabilities cap;
+    if (device->isReadable() && ANIHandler::canRead(device)) {
+        cap |= CanRead;
+    }
+    return cap;
+}
+
+QImageIOHandler *ANIPlugin::create(QIODevice *device, const QByteArray 
&format) const
+{
+    QImageIOHandler *handler = new ANIHandler;
+    handler->setDevice(device);
+    handler->setFormat(format);
+    return handler;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/src/imageformats/ani.desktop 
new/kimageformats-5.79.0/src/imageformats/ani.desktop
--- old/kimageformats-5.78.0/src/imageformats/ani.desktop       1970-01-01 
01:00:00.000000000 +0100
+++ new/kimageformats-5.79.0/src/imageformats/ani.desktop       2021-02-02 
09:28:32.000000000 +0100
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Type=Service
+X-KDE-ServiceTypes=QImageIOPlugins
+X-KDE-ImageFormat=ani
+X-KDE-MimeType=application/x-navi-animation
+X-KDE-Read=true
+X-KDE-Write=false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/src/imageformats/ani.json 
new/kimageformats-5.79.0/src/imageformats/ani.json
--- old/kimageformats-5.78.0/src/imageformats/ani.json  1970-01-01 
01:00:00.000000000 +0100
+++ new/kimageformats-5.79.0/src/imageformats/ani.json  2021-02-02 
09:28:32.000000000 +0100
@@ -0,0 +1,4 @@
+{
+    "Keys": [ "ani" ],
+    "MimeTypes": [ "application/x-navi-animation" ]
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/src/imageformats/ani_p.h 
new/kimageformats-5.79.0/src/imageformats/ani_p.h
--- old/kimageformats-5.78.0/src/imageformats/ani_p.h   1970-01-01 
01:00:00.000000000 +0100
+++ new/kimageformats-5.79.0/src/imageformats/ani_p.h   2021-02-02 
09:28:32.000000000 +0100
@@ -0,0 +1,69 @@
+/*
+    SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <k...@broulik.de>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KIMG_ANI_P_H
+#define KIMG_ANI_P_H
+
+#include <QImageIOPlugin>
+#include <QSize>
+
+class ANIHandler : public QImageIOHandler
+{
+public:
+    ANIHandler();
+
+    bool canRead() const override;
+    bool read(QImage *image) override;
+
+    int currentImageNumber() const override;
+    int imageCount() const override;
+    bool jumpToImage(int imageNumber) override;
+    bool jumpToNextImage() override;
+
+    int loopCount() const override;
+    int nextImageDelay() const override;
+
+    bool supportsOption(ImageOption option) const override;
+    QVariant option(ImageOption option) const override;
+
+    static bool canRead(QIODevice *device);
+
+private:
+    bool ensureScanned() const;
+
+    bool m_scanned = false;
+
+    int m_currentImageNumber = 0;
+
+    int m_frameCount = 0; // "physical" frames
+    int m_imageCount = 0; // logical images
+    // Stores a custom sequence of images
+    QVector<int> m_imageSequence;
+    // and the corresponding offsets where they are
+    // since we can't read the image data sequentally in this case then
+    QVector<qint64> m_frameOffsets;
+    qint64 m_firstFrameOffset = 0;
+
+    int m_displayRate = 0;
+    QVector<int> m_displayRates;
+
+    QString m_name;
+    QString m_artist;
+    QSize m_size;
+
+};
+
+class ANIPlugin : public QImageIOPlugin
+{
+    Q_OBJECT
+    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" 
FILE "ani.json")
+
+public:
+    Capabilities capabilities(QIODevice *device, const QByteArray &format) 
const override;
+    QImageIOHandler *create(QIODevice *device, const QByteArray &format = 
QByteArray()) const override;
+};
+
+#endif // KIMG_ANI_P_H
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/src/imageformats/avif.cpp 
new/kimageformats-5.79.0/src/imageformats/avif.cpp
--- old/kimageformats-5.78.0/src/imageformats/avif.cpp  2021-01-02 
13:27:55.000000000 +0100
+++ new/kimageformats-5.79.0/src/imageformats/avif.cpp  2021-02-02 
09:28:32.000000000 +0100
@@ -198,25 +198,8 @@
             qWarning("Invalid QColorSpace created from ICC!\n");
         }
     } else {
-
-        avifColorPrimaries primaries_to_load;
-        avifTransferCharacteristics trc_to_load;
-
-        if ((m_decoder->image->colorPrimaries == 2 /* 
AVIF_COLOR_PRIMARIES_UNSPECIFIED */) ||
-                (m_decoder->image->colorPrimaries == 0 /* 
AVIF_COLOR_PRIMARIES_UNKNOWN */)) {
-            primaries_to_load = (avifColorPrimaries) 1; // 
AVIF_COLOR_PRIMARIES_BT709
-        } else {
-            primaries_to_load = m_decoder->image->colorPrimaries;
-        }
-        if ((m_decoder->image->transferCharacteristics == 2 /* 
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */) ||
-                (m_decoder->image->transferCharacteristics == 0 /* 
AVIF_TRANSFER_CHARACTERISTICS_UNKNOWN */)) {
-            trc_to_load = (avifTransferCharacteristics) 13; // 
AVIF_TRANSFER_CHARACTERISTICS_SRGB
-        } else {
-            trc_to_load = m_decoder->image->transferCharacteristics;
-        }
-
         float prim[8]; // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY
-        avifColorPrimariesGetValues(primaries_to_load, prim);
+        avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim);
 
         QPointF redPoint(prim[0], prim[1]);
         QPointF greenPoint(prim[2], prim[3]);
@@ -227,7 +210,7 @@
         QColorSpace::TransferFunction q_trc = 
QColorSpace::TransferFunction::Custom;
         float q_trc_gamma = 0.0f;
 
-        switch (trc_to_load) {
+        switch (m_decoder->image->transferCharacteristics) {
         /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
         case 4:
             q_trc = QColorSpace::TransferFunction::Gamma;
@@ -243,6 +226,8 @@
             q_trc = QColorSpace::TransferFunction::Linear;
             break;
         /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
+        case 0:
+        case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
         case 13:
             q_trc =  QColorSpace::TransferFunction::SRgb;
             break;
@@ -254,9 +239,11 @@
         }
 
         if (q_trc != QColorSpace::TransferFunction::Custom) {   //we create 
new colorspace using Qt
-            switch (primaries_to_load) {
+            switch (m_decoder->image->colorPrimaries) {
             /* AVIF_COLOR_PRIMARIES_BT709 */
+            case 0:
             case 1:
+            case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
                 result.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 
q_trc, q_trc_gamma));
                 break;
             /* AVIF_COLOR_PRIMARIES_SMPTE432 */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kimageformats-5.78.0/tests/imagedump.cpp 
new/kimageformats-5.79.0/tests/imagedump.cpp
--- old/kimageformats-5.78.0/tests/imagedump.cpp        2021-01-02 
13:27:55.000000000 +0100
+++ new/kimageformats-5.79.0/tests/imagedump.cpp        2021-02-02 
09:28:32.000000000 +0100
@@ -45,6 +45,10 @@
         QStringList() << QStringLiteral("l") << 
QStringLiteral("list-file-formats"),
         QStringLiteral("List supported image file formats"));
     parser.addOption(listformats);
+    QCommandLineOption listmimetypes(
+        QStringList() << QStringLiteral("m") << 
QStringLiteral("list-mime-types"),
+        QStringLiteral("List supported image mime types"));
+    parser.addOption(listmimetypes);
     QCommandLineOption listqformats(
         QStringList() << QStringLiteral("p") << 
QStringLiteral("list-qimage-formats"),
         QStringLiteral("List supported QImage data formats"));
@@ -62,6 +66,15 @@
             out << "  " << fmt << '\n';
         }
         return 0;
+    }
+    if (parser.isSet(listmimetypes)) {
+        QTextStream out(stdout);
+        out << "MIME types:\n";
+        const auto lstSupportedMimeTypes = QImageReader::supportedMimeTypes();
+        for (const auto &fmt : lstSupportedMimeTypes) {
+            out << "  " << fmt << '\n';
+        }
+        return 0;
     }
     if (parser.isSet(listqformats)) {
         QTextStream out(stdout);

Reply via email to