diff --git a/README.md b/README.md index 2991da5..dc5ed6f 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 ccb333a..a26b4ae 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 a3833c1..e3ae96f 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 @@ -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 0000000..ffdce2e --- /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 0000000..f223158 --- /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 0000000..2a475ba 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 202681e..2398f48 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 ef587d7..6dbfc24 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 + SPDX-FileCopyrightText: 2026 Mirco Miranda 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 +#include #include #include #include +#include +#include -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(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(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 6307531..eeefa99 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 0b785b4..0000000 --- a/src/imageformats/ora.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - This file is part of the KDE project - SPDX-FileCopyrightText: 2013 Boudewijn Rempt - - 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 -#include - -#include - -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(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 58d134d..0000000 --- a/src/imageformats/ora.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - This file is part of the KDE project - SPDX-FileCopyrightText: 2013 Boudewijn Rempt - - SPDX-License-Identifier: LGPL-2.0-or-later -*/ - -#ifndef KIMG_ORA_H -#define KIMG_ORA_H - -#include - -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 c12d7f8..0000000 --- a/src/imageformats/ora.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Keys": [ "ora" ], - "MimeTypes": [ "image/openraster" ] -}