[frameworks/kimageformats] /: KRA/ORA: merged in a single plugin and added metadata support

Mirco Miranda <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 2e199eea806a7cf627b4ac54acbde1ff041fa383 by Mirco Miranda.
Committed on 29/07/2026 at 05:42.
Pushed by mircomir into branch 'master'.

KRA/ORA: merged in a single plugin and added metadata support

- ORA/KRA: merged in a single plugin (KRA)
- ORA: removed standalone plugin
- KRA: use of logging category
- KRA: add metadata support by processing the embedded `documentinfo.xml`
- Improved signature lookup (see MR !17) and added test case

M  +3    -9    README.md
M  +0    -1    autotests/ossfuzz/build_fuzzers.sh
M  +2    -5    autotests/ossfuzz/kimgio_fuzzer.cc
A  +19   -0    autotests/read/kra/src.kra.json
A  +15   -0    autotests/read/kra/src443_32bit.json
A  +-    --    autotests/read/kra/src443_32bit.kra
M  +0    -3    src/imageformats/CMakeLists.txt
M  +81   -13   src/imageformats/kra.cpp
M  +2    -2    src/imageformats/kra.json
R  +0    -0    src/imageformats/kra_p.h [from: src/imageformats/kra.h - 100% similarity]
D  +0    -98   src/imageformats/ora.cpp
D  +0    -33   src/imageformats/ora.h
D  +0    -4    src/imageformats/ora.json

https://invent.kde.org/frameworks/kimageformats/-/commit/2e199eea806a7cf627b4ac54acbde1ff041fa383

diff --git a/README.md b/README.md
index 2991da5c..dc5ed6fc 100644
--- a/README.md
+++ b/README.md
@@ -493,16 +493,10 @@ plugin:
   this format an hack is activated to guarantee total compatibility of the 
   plugin with Windows.
 
-### The KRA plugin
+### The KRA and ORA plugin
 
-The KRA format is a ZIP archive containing image data. In particular, the
-rendered image in PNG format is saved in the root: the plugin reads this 
-image.
-
-### The ORA plugin
-
-The ORA format is a ZIP archive containing image data. In particular, the
-rendered image in PNG format is saved in the root: the plugin reads this 
+Both KRA and ORA formats are ZIP archives containing image data. Specifically,
+the rendered PNG image is saved in the root directory: the plugin reads this
 image.
 
 ### The PSD plugin
diff --git a/autotests/ossfuzz/build_fuzzers.sh b/autotests/ossfuzz/build_fuzzers.sh
index ccb333a9..a26b4ae9 100755
--- a/autotests/ossfuzz/build_fuzzers.sh
+++ b/autotests/ossfuzz/build_fuzzers.sh
@@ -165,7 +165,6 @@ HANDLER_TYPES="ani
         jxl
         jxr
         kra
-        ora
         pcx
         pfm
         pic
diff --git a/autotests/ossfuzz/kimgio_fuzzer.cc b/autotests/ossfuzz/kimgio_fuzzer.cc
index a3833c1d..e3ae96f7 100644
--- a/autotests/ossfuzz/kimgio_fuzzer.cc
+++ b/autotests/ossfuzz/kimgio_fuzzer.cc
@@ -23,7 +23,7 @@
   Usage:
     python infra/helper.py build_image kimageformats
     python infra/helper.py build_fuzzers --sanitizer undefined|address|memory kimageformats
-    python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|ff|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer
+    python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|ff|hdr|heif|iff|jp2|jxl|jxr|kra|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer
 */
 
 #include <QBuffer>
@@ -65,11 +65,8 @@
 #include "jxr_p.h"
 #define HANDLER JXRHandler
 #elif defined KIMG_FUZZER_kra
-#include "kra.h"
+#include "kra_p.h"
 #define HANDLER KraHandler
-#elif defined KIMG_FUZZER_ora
-#include "ora.h"
-#define HANDLER OraHandler
 #elif defined KIMG_FUZZER_pcx
 #include "pcx_p.h"
 #define HANDLER PCXHandler
diff --git a/autotests/read/kra/src.kra.json b/autotests/read/kra/src.kra.json
new file mode 100644
index 00000000..ffdce2e5
--- /dev/null
+++ b/autotests/read/kra/src.kra.json
@@ -0,0 +1,19 @@
+[
+    {
+        "fileName" : "src.png",
+        "metadata" : [
+            {
+                "key" : "CreationDate",
+                "value" : "2016-01-27T10:40:16"
+            },
+            {
+                "key" : "ModificationDate",
+                "value" : "2016-01-27T10:41:28"
+            },
+            {
+                "key" : "Author" ,
+                "value" : "Boudewijn Rempt"
+            }
+        ]
+    }
+]
diff --git a/autotests/read/kra/src443_32bit.json b/autotests/read/kra/src443_32bit.json
new file mode 100644
index 00000000..f2231580
--- /dev/null
+++ b/autotests/read/kra/src443_32bit.json
@@ -0,0 +1,15 @@
+[
+    {
+        "fileName" : "src.png",
+        "metadata" : [
+            {
+                "key" : "CreationDate",
+                "value" : "2016-01-27T10:40:16"
+            },
+            {
+                "key" : "ModificationDate",
+                "value" : "2026-07-16T07:00:36"
+            }
+        ]
+    }
+]
diff --git a/autotests/read/kra/src443_32bit.kra b/autotests/read/kra/src443_32bit.kra
new file mode 100644
index 00000000..2a475baa
Binary files /dev/null and b/autotests/read/kra/src443_32bit.kra differ
diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt
index 202681ec..2398f48e 100644
--- a/src/imageformats/CMakeLists.txt
+++ b/src/imageformats/CMakeLists.txt
@@ -182,7 +182,4 @@ if (KF6Archive_FOUND)
     kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
     target_link_libraries(kimg_kra PRIVATE KF6::Archive)
 
-    kimageformats_add_plugin(kimg_ora SOURCES ora.cpp)
-    target_link_libraries(kimg_ora PRIVATE KF6::Archive)
-
 endif()
diff --git a/src/imageformats/kra.cpp b/src/imageformats/kra.cpp
index ef587d7a..6dbfc244 100644
--- a/src/imageformats/kra.cpp
+++ b/src/imageformats/kra.cpp
@@ -1,6 +1,7 @@
 /*
     This file is part of the KDE project
     SPDX-FileCopyrightText: 2013 Boudewijn Rempt <[email protected]>
+    SPDX-FileCopyrightText: 2026 Mirco Miranda <[email protected]>
 
     SPDX-License-Identifier: LGPL-2.0-or-later
 
@@ -8,16 +9,23 @@
     on public domain. See: http://tulrich.com/geekstuff/
 */
 
-#include "kra.h"
+#include "kra_p.h"
+#include "util_p.h"
 
 #include <kzip.h>
 
+#include <QByteArrayView>
 #include <QFile>
 #include <QIODevice>
 #include <QImage>
+#include <QLoggingCategory>
+#include <QXmlStreamReader>
 
-static constexpr char s_magic[] = "application/x-krita";
-static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
+Q_DECLARE_LOGGING_CATEGORY(LOG_KRAPLUGIN)
+Q_LOGGING_CATEGORY(LOG_KRAPLUGIN, "kf.imageformats.plugins.kra", QtWarningMsg)
+
+#define ORA_MAGIC QByteArrayView("image/openraster")
+#define KRA_MAGIC QByteArrayView("application/x-krita")
 
 KraHandler::KraHandler()
 {
@@ -32,6 +40,55 @@ bool KraHandler::canRead() const
     return false;
 }
 
+static bool addMetadata(QImage *image, const QByteArray& rawXml)
+{
+    if (image == nullptr) {
+        return false;
+    }
+    QXmlStreamReader xml(rawXml);
+    for(QString key; !xml.atEnd();) {
+        auto tt = xml.readNext();
+        if (tt == QXmlStreamReader::StartElement) {
+            key = xml.name().toString().toLower();
+        }
+        else if (tt == QXmlStreamReader::EndElement) {
+            key.clear();
+        }
+        else if (tt == QXmlStreamReader::Characters) {
+            auto text = xml.text().toString().trimmed();
+            if (text.isEmpty() || key.isEmpty())
+                continue;
+            if (key == QStringLiteral("title")) {
+                image->setText(QStringLiteral(META_KEY_TITLE), text);
+            }
+            else if (key == QStringLiteral("abstract")) {
+                image->setText(QStringLiteral(META_KEY_DESCRIPTION), text);
+            }
+            else if (key == QStringLiteral("full-name")) {
+                image->setText(QStringLiteral(META_KEY_AUTHOR), text);
+            }
+            else if (key == QStringLiteral("date")) {
+                if (QDateTime::fromString(text, Qt::ISODate).isValid())
+                    image->setText(QStringLiteral(META_KEY_MODIFICATIONDATE), text);
+            }
+            else if (key == QStringLiteral("creation-date")) {
+                if (QDateTime::fromString(text, Qt::ISODate).isValid())
+                    image->setText(QStringLiteral(META_KEY_CREATIONDATE), text);
+            }
+            else if (key == QStringLiteral("keyword")) {
+                image->setText(QStringLiteral(META_KEY_KEYWORDS), text);
+            }
+            else if (key == QStringLiteral("license")) {
+                image->setText(QStringLiteral(META_KEY_COPYRIGHT), text);
+            }
+            else {
+                qCDebug(LOG_KRAPLUGIN) << "Unmanaged metadata:" << key << text;
+            }
+        }
+    }
+    return !xml.hasError();
+}
+
 bool KraHandler::read(QImage *image)
 {
     KZip zip(device());
@@ -39,14 +96,26 @@ bool KraHandler::read(QImage *image)
         return false;
     }
 
+    // reading the image
     const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
     if (!entry || !entry->isFile()) {
         return false;
     }
-
     const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
+    if (!image->loadFromData(fileZipEntry->data(), "PNG")) {
+        qCCritical(LOG_KRAPLUGIN) << "Invalid image.";
+        return false;
+    }
 
-    image->loadFromData(fileZipEntry->data(), "PNG");
+    // reading metadata
+    const KArchiveEntry *metaEntry = zip.directory()->entry(QStringLiteral("documentinfo.xml"));
+    if (!metaEntry || !metaEntry->isFile()) {
+        return true; // the image is still valid
+    }
+    const KZipFileEntry *metaZipEntry = static_cast<const KZipFileEntry *>(metaEntry);
+    if (!addMetadata(image, metaZipEntry->data())) {
+        qCWarning(LOG_KRAPLUGIN) << "XML metadat seems invalid.";
+    }
 
     return true;
 }
@@ -54,24 +123,23 @@ bool KraHandler::read(QImage *image)
 bool KraHandler::canRead(QIODevice *device)
 {
     if (!device) {
-        qWarning("KraHandler::canRead() called with no device");
+        qCWarning(LOG_KRAPLUGIN) << "KraHandler::canRead() called with no device";
         return false;
     }
     if (device->isSequential()) {
         return false;
     }
 
-    char buff[57];
-    if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
-        return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
+    auto head = device->peek(100);
+    if (!head.startsWith(QByteArrayView("PK"))) {
+        return false;
     }
-
-    return false;
+    return head.contains(KRA_MAGIC) || head.contains(ORA_MAGIC);
 }
 
 QImageIOPlugin::Capabilities KraPlugin::capabilities(QIODevice *device, const QByteArray &format) const
 {
-    if (format == "kra" || format == "KRA") {
+    if (format == "kra" || format == "ora") {
         return Capabilities(CanRead);
     }
     if (!format.isEmpty()) {
@@ -96,4 +164,4 @@ QImageIOHandler *KraPlugin::create(QIODevice *device, const QByteArray &format)
     return handler;
 }
 
-#include "moc_kra.cpp"
+#include "moc_kra_p.cpp"
diff --git a/src/imageformats/kra.json b/src/imageformats/kra.json
index 6307531f..eeefa990 100644
--- a/src/imageformats/kra.json
+++ b/src/imageformats/kra.json
@@ -1,4 +1,4 @@
 {
-    "Keys": [ "kra" ],
-    "MimeTypes": [ "application/x-krita" ]
+    "Keys": [ "kra", "ora" ],
+    "MimeTypes": [ "application/x-krita", "image/openraster" ]
 }
diff --git a/src/imageformats/kra.h b/src/imageformats/kra_p.h
similarity index 100%
rename from src/imageformats/kra.h
rename to src/imageformats/kra_p.h
diff --git a/src/imageformats/ora.cpp b/src/imageformats/ora.cpp
deleted file mode 100644
index 0b785b4f..00000000
--- a/src/imageformats/ora.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
-    This file is part of the KDE project
-    SPDX-FileCopyrightText: 2013 Boudewijn Rempt <[email protected]>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-
-    This code is based on Thacher Ulrich PSD loading code released
-    on public domain. See: http://tulrich.com/geekstuff/
-*/
-
-#include "ora.h"
-
-#include <QImage>
-#include <QScopedPointer>
-
-#include <kzip.h>
-
-static constexpr char s_magic[] = "image/openraster";
-static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0
-
-OraHandler::OraHandler()
-{
-}
-
-bool OraHandler::canRead() const
-{
-    if (canRead(device())) {
-        setFormat("ora");
-        return true;
-    }
-    return false;
-}
-
-bool OraHandler::read(QImage *image)
-{
-    KZip zip(device());
-    if (!zip.open(QIODevice::ReadOnly)) {
-        return false;
-    }
-
-    const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
-    if (!entry || !entry->isFile()) {
-        return false;
-    }
-
-    const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
-
-    image->loadFromData(fileZipEntry->data(), "PNG");
-
-    return true;
-}
-
-bool OraHandler::canRead(QIODevice *device)
-{
-    if (!device) {
-        qWarning("OraHandler::canRead() called with no device");
-        return false;
-    }
-    if (device->isSequential()) {
-        return false;
-    }
-
-    char buff[54];
-    if (device->peek(buff, sizeof(buff)) == sizeof(buff)) {
-        return memcmp(buff + 0x26, s_magic, s_magic_size) == 0;
-    }
-
-    return false;
-}
-
-QImageIOPlugin::Capabilities OraPlugin::capabilities(QIODevice *device, const QByteArray &format) const
-{
-    if (format == "ora" || format == "ORA") {
-        return Capabilities(CanRead);
-    }
-    if (!format.isEmpty()) {
-        return {};
-    }
-    if (!device->isOpen()) {
-        return {};
-    }
-
-    Capabilities cap;
-    if (device->isReadable() && OraHandler::canRead(device)) {
-        cap |= CanRead;
-    }
-    return cap;
-}
-
-QImageIOHandler *OraPlugin::create(QIODevice *device, const QByteArray &format) const
-{
-    QImageIOHandler *handler = new OraHandler;
-    handler->setDevice(device);
-    handler->setFormat(format);
-    return handler;
-}
-
-#include "moc_ora.cpp"
diff --git a/src/imageformats/ora.h b/src/imageformats/ora.h
deleted file mode 100644
index 58d134d3..00000000
--- a/src/imageformats/ora.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-    This file is part of the KDE project
-    SPDX-FileCopyrightText: 2013 Boudewijn Rempt <[email protected]>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#ifndef KIMG_ORA_H
-#define KIMG_ORA_H
-
-#include <QImageIOPlugin>
-
-class OraHandler : public QImageIOHandler
-{
-public:
-    OraHandler();
-
-    bool canRead() const override;
-    bool read(QImage *image) override;
-
-    static bool canRead(QIODevice *device);
-};
-
-class OraPlugin : public QImageIOPlugin
-{
-    Q_OBJECT
-    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "ora.json")
-public:
-    Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
-    QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
-};
-
-#endif
diff --git a/src/imageformats/ora.json b/src/imageformats/ora.json
deleted file mode 100644
index c12d7f85..00000000
--- a/src/imageformats/ora.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-    "Keys": [ "ora" ],
-    "MimeTypes": [ "image/openraster" ]
-}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.