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
This commit is contained in:
Mirco Miranda
2026-07-29 07:42:42 +02:00
parent 516750e6fc
commit 2e199eea80
13 changed files with 122 additions and 168 deletions
+3 -9
View File
@@ -493,16 +493,10 @@ plugin:
this format an hack is activated to guarantee total compatibility of the this format an hack is activated to guarantee total compatibility of the
plugin with Windows. plugin with Windows.
### The KRA plugin ### The KRA and ORA plugin
The KRA format is a ZIP archive containing image data. In particular, the Both KRA and ORA formats are ZIP archives containing image data. Specifically,
rendered image in PNG format is saved in the root: the plugin reads this the rendered PNG image is saved in the root directory: 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
image. image.
### The PSD plugin ### The PSD plugin
-1
View File
@@ -165,7 +165,6 @@ HANDLER_TYPES="ani
jxl jxl
jxr jxr
kra kra
ora
pcx pcx
pfm pfm
pic pic
+2 -5
View File
@@ -23,7 +23,7 @@
Usage: Usage:
python infra/helper.py build_image kimageformats python infra/helper.py build_image kimageformats
python infra/helper.py build_fuzzers --sanitizer undefined|address|memory 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> #include <QBuffer>
@@ -65,11 +65,8 @@
#include "jxr_p.h" #include "jxr_p.h"
#define HANDLER JXRHandler #define HANDLER JXRHandler
#elif defined KIMG_FUZZER_kra #elif defined KIMG_FUZZER_kra
#include "kra.h" #include "kra_p.h"
#define HANDLER KraHandler #define HANDLER KraHandler
#elif defined KIMG_FUZZER_ora
#include "ora.h"
#define HANDLER OraHandler
#elif defined KIMG_FUZZER_pcx #elif defined KIMG_FUZZER_pcx
#include "pcx_p.h" #include "pcx_p.h"
#define HANDLER PCXHandler #define HANDLER PCXHandler
+19
View File
@@ -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"
}
]
}
]
+15
View File
@@ -0,0 +1,15 @@
[
{
"fileName" : "src.png",
"metadata" : [
{
"key" : "CreationDate",
"value" : "2016-01-27T10:40:16"
},
{
"key" : "ModificationDate",
"value" : "2026-07-16T07:00:36"
}
]
}
]
Binary file not shown.
-3
View File
@@ -182,7 +182,4 @@ if (KF6Archive_FOUND)
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp) kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
target_link_libraries(kimg_kra PRIVATE KF6::Archive) 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() endif()
+81 -13
View File
@@ -1,6 +1,7 @@
/* /*
This file is part of the KDE project This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Boudewijn Rempt <boud@valdyas.org> SPDX-FileCopyrightText: 2013 Boudewijn Rempt <boud@valdyas.org>
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later SPDX-License-Identifier: LGPL-2.0-or-later
@@ -8,16 +9,23 @@
on public domain. See: http://tulrich.com/geekstuff/ on public domain. See: http://tulrich.com/geekstuff/
*/ */
#include "kra.h" #include "kra_p.h"
#include "util_p.h"
#include <kzip.h> #include <kzip.h>
#include <QByteArrayView>
#include <QFile> #include <QFile>
#include <QIODevice> #include <QIODevice>
#include <QImage> #include <QImage>
#include <QLoggingCategory>
#include <QXmlStreamReader>
static constexpr char s_magic[] = "application/x-krita"; Q_DECLARE_LOGGING_CATEGORY(LOG_KRAPLUGIN)
static constexpr int s_magic_size = sizeof(s_magic) - 1; // -1 to remove the last \0 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() KraHandler::KraHandler()
{ {
@@ -32,6 +40,55 @@ bool KraHandler::canRead() const
return false; 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) bool KraHandler::read(QImage *image)
{ {
KZip zip(device()); KZip zip(device());
@@ -39,14 +96,26 @@ bool KraHandler::read(QImage *image)
return false; return false;
} }
// reading the image
const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png")); const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
if (!entry || !entry->isFile()) { if (!entry || !entry->isFile()) {
return false; return false;
} }
const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry); 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; return true;
} }
@@ -54,24 +123,23 @@ bool KraHandler::read(QImage *image)
bool KraHandler::canRead(QIODevice *device) bool KraHandler::canRead(QIODevice *device)
{ {
if (!device) { if (!device) {
qWarning("KraHandler::canRead() called with no device"); qCWarning(LOG_KRAPLUGIN) << "KraHandler::canRead() called with no device";
return false; return false;
} }
if (device->isSequential()) { if (device->isSequential()) {
return false; return false;
} }
char buff[57]; auto head = device->peek(100);
if (device->peek(buff, sizeof(buff)) == sizeof(buff)) { if (!head.startsWith(QByteArrayView("PK"))) {
return memcmp(buff + 0x26, s_magic, s_magic_size) == 0; return false;
} }
return head.contains(KRA_MAGIC) || head.contains(ORA_MAGIC);
return false;
} }
QImageIOPlugin::Capabilities KraPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities KraPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "kra" || format == "KRA") { if (format == "kra" || format == "ora") {
return Capabilities(CanRead); return Capabilities(CanRead);
} }
if (!format.isEmpty()) { if (!format.isEmpty()) {
@@ -96,4 +164,4 @@ QImageIOHandler *KraPlugin::create(QIODevice *device, const QByteArray &format)
return handler; return handler;
} }
#include "moc_kra.cpp" #include "moc_kra_p.cpp"
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"Keys": [ "kra" ], "Keys": [ "kra", "ora" ],
"MimeTypes": [ "application/x-krita" ] "MimeTypes": [ "application/x-krita", "image/openraster" ]
} }
-98
View File
@@ -1,98 +0,0 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Boudewijn Rempt <boud@valdyas.org>
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"
-33
View File
@@ -1,33 +0,0 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Boudewijn Rempt <boud@valdyas.org>
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
-4
View File
@@ -1,4 +0,0 @@
{
"Keys": [ "ora" ],
"MimeTypes": [ "image/openraster" ]
}