mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-08-26 08:26:59 -04:00
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:
@@ -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
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ HANDLER_TYPES="ani
|
|||||||
jxl
|
jxl
|
||||||
jxr
|
jxr
|
||||||
kra
|
kra
|
||||||
ora
|
|
||||||
pcx
|
pcx
|
||||||
pfm
|
pfm
|
||||||
pic
|
pic
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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.
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"Keys": [ "kra" ],
|
"Keys": [ "kra", "ora" ],
|
||||||
"MimeTypes": [ "application/x-krita" ]
|
"MimeTypes": [ "application/x-krita", "image/openraster" ]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"Keys": [ "ora" ],
|
|
||||||
"MimeTypes": [ "image/openraster" ]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user